Банк «Полибиус»: как мы делаем первый криптобанк с эстонско-швейцарскими корнями

Новости о новых криптостартапах и блокчейн-проектах появляются почти каждую неделю, но до сих пор у криптобизнеса не было собственного банка — что даже немного странно, учитывая финтек-направленность большинства таких проектов и важность банковских услуг для функционирования бизнеса.


Наступает «время первых» в мире банков

Эстонская компания Polybius Foundation решила восполнить этот пробел и учреждает первый банк, специализирующийся на финансовых услугах для криптостартапов и блокчейн-проектов по всему миру: банк «Полибиус» (Polybius Bank). О том, что из себя представляет этот банк, зачем он нужен, как устроен — и, главное, как в этой движухе поучаствовать, пока не поздно — наша первая статья на эту тему.

Немножко истории

Учредителями Polybius Foundation являются те же ребята, что основали производителя майнингового оборудования и блокчейн-разработчика HashCoins, первым профилем деятельности которого было производство и продажа оборудования для майнинга. Разумеется, как и любому бизнесу, им потребовался счёт в банке. Диалог с клерком выглядел примерно так:

— Что у вас за бизнес?
— Мы продаём оборудование для майнинга и сами добываем биткоины.
— Ой-ой, нам такого не надо!

Надо понимать, что процесс признания криптовалют регулирующими органам начался совсем недавно и пока что в самом разгаре. До этого, каких-то три-четыре года назад, придти в банк с заявлением «мы тут криптовалюту майним», было равносильно просьбе открыть счёт для хранения денег от продаж наркотиков и оружия. Чтобы найти банк, который согласится обслуживать счёт HashCoins, ребятам пришлось говорить, что они занимаются продажами компьютеров и комплектующих (что, по сути, тоже верно, но не содержит вызывающих тревогу слов «криптовалюта» или «биткоины») и согласиться на повышенную процентную ставку «за рисковость», которую банк снизил только годы стабильного и беспроблемного сотрудничества спустя.

Аналогичные проблемы возникают у криптостартапов по всему миру. В России, как мы помним, пару лет назад за криптовалюту собирались чуть ли не сажать. Ситуация изменилась — во многом благодаря лично Герману Грефу, который на западных конференциях услышал слова «блокчейн» и Agile и бросился внедрять их в Сбербанке и пропагандировать в стране — благодаря чему Россия стала сейчас чуть ли не одной из ведущих стран в разработке блокчейн-проектов и криптовалют. Но прогресс шагает по планете неравномерно, и описанные выше проблемы в той или иной степени ещё актуальны для многих стран.

Почему «Полибиус»?

Название банку придумал один из вас: хабраюзер andorro, предложивший использовать «квадрат Полибия» — один из древнейших методов криптографии — в одном из наших проектов: незаезженное коммерчески, при этом запоминающееся и узнаваемое название.

Ход запуска и ICO

Подписавшись на наш блог, вы получаете уникальную возможность наблюдать процесс создания банка в реальном времени — по возможности, мы будем комментировать происходящее максимально подробно.

  1. Регистрация учредительной организации, которая будет владеть 100% акций «Полибиус-банка» — Polybius Foundation. Done.
  2. Устав банка. Важный юридический документ, краеугольный камень любой организации. В нашем случае Устав будет не совсем обычный — а очень даже уникальный. Об этом ниже.
  3. Регистрация банка. Мы выбираем одну из локаций в пределах Европы. Сейчас выбор сузился до Великобритании, Швейцарии (понятный выбор для банка) и Литвы (не такой очевидный, но тоже крутой вариант, учитывая намерение Литвы стать передовым в области финтека государством).
  4. ICO. Initial Coin Offerings можно считать криптоаналогом знакомого многим понятия IPO (Initial Public Offering), когда вместо акций выпускаются токены — займ криптовалюты в обмен на фиксированную долю прибыли. Один токен стоит $10. Принципы ICO прописаны в Уставе Polibyus Foundation, учредителя банка «Полибиус». И об этом будет отдельный пост.

Зачем участвовать в ICO

С детства слово «банкир» было синонимом успеха и заработка. Но если открыть свой банк, вам не по силам — и дело не только в деньгах (а бюджет на открытие банка в ЕС составляет несколько миллионов евро), но и требуемом опыте, знаниях и затратах времени, а инвестировать в уже состоявшиеся банки не видите особого смысла, т.к. резкого роста там ждать уже негде, подумайте о том, чтобы инвестировать в создаваемый банк. Именно такую возможность предоставляет банк «Полибиус»: современный высокотехнологичный банк, в котором полное соответствие всем требованиям европейских финансовых регуляторов встречается с современным технологической начинки, гарантирующей скорость и безопасность транзакций уровня XXI века. Сейчас у вас есть возможность инвестировать в его развитие, гарантировав себе долю в прибыли банка, защищённую смарт-контрактом и законами Европейского союза. Чем больше вложения — тем больше прибыль.

Как это будет работать

В первую очередь это будет банк — т.е. просто банк, финансовое учреждение для всех. «Полибиус-банк» будет предоставлять традиционный набор финансовых услуг вроде депозитов, кредитования, выпуска банковских карт и т.д. не только криптостартапам, но и обычным бизнесам и, конечно же, физическим лицам.

При этом будут использоваться последние достижения технологий, как крипто — так и обычных.

Смысл в том, чтобы переизобрести то, что можно переизобрести и оптимизировать то, что можно оптимизировать в современном финансовом учреждении в тех рамках прокрустова ложа законодательства, что сейчас существуют. А европейское законодательство никогда лаконичностью не отличалось. С одной стороны, это плюс: сам факт расположения банка в европейской юрисдикции гарантирует европейский уровень соблюдения прав вкладчиков.

  • Устав со смарт-контрактом. По условиям ICO, за инвесторами будет закреплена и гарантирована смарт-контрактом выплата 20% прибыли «Полибиус-банка». Сам факт появления понятия смарт-контракт и ICO в таком консервативном документе, как Устав — небольшой прорыв и подлинный признак того, что перемены уже начинаются.
  • ИИ в кредитовании. Как и любой другой банк, мы будем заниматься кредитованием. И планируем использовать для скоринга — определения кредитоспособности клиента — нейросети. Возможно, это не звучит как супер-инновация, т.к. на самом деле банки уже внедряют похожие технологии по всему миру, но мы подкрепим искусственноинтеллектуальный скоринг ещё одной любопытной разработкой нашего авторства.
  • DigiPass. Здесь в дело вступает блокчейн. DigiPass будет универсальным децентрализованным хранилищем приватной информации о юзере — от кредитной истории до записей медкарт — в блокчейне, доступ к которому будет только у него. Роль «Полибиуса» заключается в том, чтобы выступать доверенным агентом по верификации вносимых пользователем данных. Заверенная банком информация будет валидна для всех кредитных, финансовых и многих государственных и частных организаций по всему миру. При этом пользователь сам будет решать, какую информацию предоставлять по запросу тому или иному учреждению. Захотели взять кредит в «Сити-банке»? Вместо кучи нудных справок просто даёте доступ к той части вашего «Дигипасса», которая содержит кредитную историю. Покупаете медицинскую страховку? Расшарьте доступ вашему агенту в ту часть хранилища, в которой ваша информация о здоровье. Децентрализованность хранилища гарантирует независимость сохранности вашей информации от состояния самого «Полибиус-банка».

Услуги для криптопроектов и криптофанатов

Не единственная, но важнейшая причина, по которой мы всё это затеяли: поддержка криптостартапов и криптоэнтузиастов по всему миру.

  • Легальным криптопроектам — т.е. не связанным ни с какой незаконной активностью — не составит никакого труда получить счёт и возможность претендовать на кредит в «Полибиус-банке» на общих, недискриминационных основаниях. В каком-то смысле, им с нами будет работать даже проще, потому что за банком стоит команда криптофанатиков и криптоэнтузиастов — мы прекрасно найдём общий язык.
  • Кредитование под залог криптовалют также может быть интересной опцией для многих криптопроектов и криптоюзеров. Просто представьте, что вы разработали новую крипту и пришли в «Сбербанк» за кредитом на её развитие при том, что никаких других активов у вас, возможно, и нет. Или не в «Сбер» — подставьте в этот мысленный эксперимент «Райффайзен» или «Сити» — результат всё равно будет одинаков. Polybius Bank же с удовольствием рассмотрит такое предложение. Наш опыт и экспертиза в майнинге, создании и развитии криптопроектов поможет оценить ваши перспективы и даже, возможно, подсказать в каком направлении вам развиваться.

Люди, которые стоят за нами

Такой проект, как «Полибиус», стал возможен, когда те, у кого есть опыт, знания и желание делать современные цифровые продукты, встретились с теми, у кого есть финансовая экспертиза, банковский опыт и понимание, что можно и нельзя улучшить в условиях существующих регуляций. Таким образом, у колыбели нашего банка собралась разношёрстная, но исключительно мощная команда экспертов в совершенно разных областях: сильная комманда программистов, эксперты по безопасности и криптографии, разработчики финансовых платформ, кредитные агенства, банкиры, юристы и знатоки европейских регуляций.В нашей команде одних только докторских степеней несколько штук, а совместного опыта в том, что мы хотим сделать у нас больше века. Увидеть в лицо нашу команду вы можете на сайте «Полибиуса». Мы гордимся нашими людьми и обязательно расскажем вам о них больше, но начать хотели бы с нескольких имён.

Уго Бекис. Стратегический советник банка «Полибиус». Когда технологическая компания запускает банк, то перед ней стоят две проблемы: с одной стороны, какие-то идеи будут просто нереализуемы в условиях современных законодательных ограничений, с другой — из-за нехватки банковской экспертизы можно пропустить действительно интересные направления и свободные ниши. С тем, чтобы этого не произошло с нами, нам помогает Уго: заслуженный финансист, построивший свою карьеру в ряде крупнейших банков, включая Deutsche Bank и Bank of America, член европейской оперативной группы по киберпреступности. Его многолетний опыт работы в финансовой сфере охватывает все стороны банковского бизнеса: от того, как устроены финансовые транзакции до строительства банка с нуля. Последние 14 лет Уго находится на передовой финансового мира, и может не только корректировать развитие компании, но и подсказывать новые направления для движения.

Иван Захаренко. В банке «Полибиус» Иван отвечает за создание и продвижение банковских продуктов и услуг, основываясь на знании потребностей и спроса рынка и своём десятилетнем опыте работы в таких банках, как Swedbank и Citadele Bank. Он занимал должность директора по корпоративному развитию в Expobank AS, старшего специалиста по работе с крупными клиентами в Baltikums Banks и специалиста по работе с крупными клиентами в Parex Bank.

Павел Кравченко. Доктор наук, криптограф, Кравченко отвечает за безопасность технологических решений. Павел — один из основателей компании, разработавшей бэкенд банковской системы openbankit.com Также, по заказу национального банка Украины, он писал софт с использованием блокчейна.

Сергей Потапенко: идейный вдохновитель банка «Полибиус» и сооснователь Polybius Foundation. Является соучредителем компании HashCoins, которая с 2013 года занимается производством оборудования для майнинга, а также разрабатывает криптопроекты, включая сервис облачного майнинга HashFlare, насчитывающего более 500 тысяч пользователей.

Олег Ховайко. Специалист по криптографии, Олег консультирует технических специалистов банка «Полибиус» по вопросам криптографии, безопасности и использования блокчейна. Олег — один из создателей и технический директор проекта Emercoin, о множестве технических разработок на основе блокчейна которого мы рассказывали в нашем блоге.

Как принять в этом участие

Старт ICO банка «Полибиус» состоится в конце апреля. Чтобы не пропустить это событие, и успеть приобрести токены в первые недели, пока будут бонусы, подпишитесь на рассылку с уведомлением на сайте polybius.io или следите за новостями в нашем блоге на «Гиктаймсе».
ссылка на оригинал статьи https://geektimes.ru/post/287540/

Скачиваем историю переписки со всеми пользователями ВКонтакте с помощью Python

Для лингвистического исследования мне понадобился корпус прямой речи, порожденной одним человеком. Я решил, что для начала удобнее всего использовать собственную переписку в ВК. Это статья о том, как скачать все сообщения, которые Вы когда-либо отправляли своим друзьям, используя программу на Python и API ВКонтакте. Для работы с API будем использовать библиотеку vk.

Для работы с сайтом нужно создать приложение и авторизоваться с помощью токена. Этот процесс не представляет из себя ничего сложного и описан здесь и здесь.

Итак, токен получен. Импортируем необходимые библиотеки (time и re понадобятся нам позже), подключимся к нашему приложению и начнем работу.

import vk import time import re  session = vk.Session(access_token='your_token') vkapi = vk.API(session)

Так как мы хотим получить переписку со всеми друзьями, начнем с получения списка друзей. Дальнейшая обработка полного списка друзей может оказаться довольно долгой, поэтому для тестирования можно написать id нескольких друзей вручную.

friends = vkapi('friends.get') # получение всего списка друзей для пользователя # friends = [1111111, 2222222, 33333333] # задаем друзей вручную 

Имея список друзей, можно сразу приступить к скачиванию диалогов с ними, но я хочу обрабатывать только те диалоги, в которых содержится более чем 200 сообщений, так как короткие беседы с малознакомыми людьми мне не очень интересны. Поэтому напишем функцию, которая вернет «шапки» диалогов.

def get_dialogs(user_id): 	dialogs = vkapi('messages.getDialogs', user_id=user_id) 	return dialogs

Такая функция возвращает «шапку» диалога с пользователем, id которого равен указанному user_id. Результат её работы выглядит приблизительно так:

[96, {'title': ' ... ', 'body': '', 'mid': 333333, 'read_state': 1, 'uid': 111111, 'date': 1490182267, 'fwd_messages': [{'date': 1490173134, 'body': 'Не, ну все это и так понятно, но нам же там жить.', 'uid': 222222}], 'out': 0}]

В полученном списке содержится количество сообщений (96) и данные последнего сообщения в диалоге. Теперь у нас есть всё необходимое, чтобы скачать нужные диалоги.

Основное неудобство состоит в том, что ВКонтакте позволяет делать максимум около трех запросов в секунду, поэтому после каждого запроса нужно какое-то время ждать. Для этого нам и нужна библиотека time. Самое маленькое время ожидания, которое мне удавалось поставить, чтобы не получить отказ через несколько операций — 0.3 секунды.

Другая сложность в том, что за один запрос можно скачать максимум 200 сообщений. С этим тоже придется бороться. Напишем функцию.

def get_history(friends, sleep_time=0.3): 	all_history = [] 	i = 0 	for friend in friends: 		friend_dialog = get_dialogs(friend) 		time.sleep(sleep_time) 		dialog_len = friend_dialog[0] 		friend_history = [] 		if dialog_len > 200: 			resid = dialog_len 			offset = 0 			while resid > 0: 				friend_history += vkapi('messages.getHistory',  					user_id=friend,  					count=200,  					offset=offset) 				time.sleep(sleep_time) 				resid -= 200 				offset += 200 				if resid > 0: 					print('--processing', friend, ':', resid,  						'of', dialog_len, 'messages left') 			all_history += friend_history 		i +=1 		print('processed', i, 'friends of', len(friends)) 	return all_history

Разберемся, что здесь происходит.

Мы проходим по списку друзей и получаем диалог с каждым из них. Рассматриваем длину диалога. Если диалог короче, чем 200 сообщений, просто переходим к следующему другу, если длиннее, то скачиваем первые 200 сообщений (аргумент count), добавляем их в историю сообщений для данного друга и рассчитываем, сколько еще сообщений осталось скачать (resid). До тех пор пока остаток больше 0, при каждой итерации увеличиваем аргумент offset, который позволяет задать отступ в количестве сообщений от конца диалога, на 200.

Из-за необходимости ожидания после каждого запроса программа работает довольно долго, поэтому я добавил вывод небольшого отчета о текущем шаге, чтобы понимать, что сейчас обрабатывается и сколько еще осталось.

N.B.: у метода messages.get есть аргумент out, с помощью которого можно попросить сервер отдавать только исходящие сообщения. Я решил не использовать его и выделить нужные мне сообщения уже после скачивания по следующим причинам: а) файл все равно придется очищать, т.к. сервер отдает каждое сообщение виде словаря, содержащего много технической информации и б) сообщения собеседников тоже могут представлять интерес для моего исследования.

Каждое скачанное сообщение является словарем и выглядит примерно вот так:
{'read_state': 1, 'date': 1354794668, 'body': 'Вот так!<br>Потому что тут модель вышла довольно непонятная.', 'uid': 111111, 'mid': 222222, 'from_id': 111111, 'out': 1}

Далее осталось только очистить результат и сохранить его в файл. Эта часть работы уже не относится к взаимодействию с VK API, поэтому я не буду останавливаться на ней подробно. Да и что тут рассказывать — просто выбираем нужные элементы (body) для нужного пользователя и с помощью re удаляем переносы строк, которые отмечены тегом <br>. Сохраняем все в файл.

Полностью код программы выглядит вот так:

import vk import time import re  session = vk.Session(access_token='your_token') vkapi = vk.API(session)  SELF_ID = 111111 SLEEP_TIME = 0.3  friends = vkapi('friends.get') # получение всего списка друзей для текущего пользователя  def get_dialogs(user_id): 	dialogs = vkapi('messages.getDialogs', user_id=user_id) 	return dialogs  def get_history(friends, sleep_time=0.3): 	all_history = [] 	i = 0 	for friend in friends: 		friend_dialog = get_dialogs(friend) 		time.sleep(sleep_time) 		dialog_len = friend_dialog[0] 		friend_history = [] 		if dialog_len > 200: 			resid = dialog_len 			offset = 0 			while resid > 0: 				friend_history += vkapi('messages.getHistory',  					user_id=friend,  					count=200,  					offset=offset) 				time.sleep(sleep_time) 				resid -= 200 				offset += 200 				if resid > 0: 					print('--processing', friend, ':', resid,  						'of', dialog_len, 'messages left') 			all_history += friend_history 		i +=1 		print('processed', i, 'friends of', len(friends)) 	return all_history  def get_messages_for_user(data, user_id): 	self_messages = [] 	for dialog in data: 		if type(dialog) == dict: 			if dialog['uid'] == user_id and dialog['from_id'] == user_id: 				m_text = re.sub("<br>", " ", dialog['body']) 				self_messages.append(m_text) 	print('Extracted', len(self_messages), 'messages in total') 	return self_messages  def save_to_file(data, file_name='output.txt'): 	with open(file_name, 'w', encoding='utf-8') as f: 	    print(data, file=f)  if __name__ == '__main__': 	all_history = get_history(friends, SLEEP_TIME) 	save_to_file(all_history, 'raw.txt')  	self_messages = get_messages_for_user(all_history, SELF_ID) 	save_to_file(self_messages, 'sm_corpus.txt')

На момент запуска программы у меня в ВК было 879 друзей. На их обработку потребовалось около 25 минут. Файл с необработанным результатом имел объем 74MB. После выделения текста только моих сообщений — 15MB. Всего сообщений в полученном корпусе — около 150 000, а их текст занимает 3707 страниц (в вордовском документе).

Надеюсь, моя статья окажется для кого-то полезной. Все методы, которые можно использовать для обращения к API ВК, детально описаны в разделе для разработчиков ВКонтакте.
ссылка на оригинал статьи https://habrahabr.ru/post/325368/

Нужно мыть окна? Стоит поручить это роботу

Если и есть на свете люди, которым нравится мыть окна, то их не очень много. Большинство относится к необходимости помыть окно как к неприятной обязанности, работе, которую делать нужно, но не очень хочется. И если раньше особого выбора не было, то сейчас он есть. Речь идет о том, что высокие технологии помогают нам жить, выполняя рутинную работу вместо человека. Уже давно появились роботы-пылесосы, а теперь есть и робот — мойщик окон.

Называется он Hobot 188. Его размер невелик, но робот может многое. Основные его достоинства — низкий уровень шума и работа с любыми гладкими поверхностями, хоть вертикальными, хоть горизонтальными. Следов на стекле не оставляет, использовать его просто. Как все это работает?

Строение робота

Корпус робота сделан из прочного ABS пластика. Внутри, как обычно — электронная начинка. Цвет робота белый. Работает он в автоматическом режиме, но если есть желание, то можно управлять и самостоятельно, при помощи пульта, который идет в комплекте.

Для очистки стекол в начале работы нужно установить две салфетки из микрофибры многоразового использования. Эти салфетки очень плотные и могут использоваться для борьбы как с обычной пылью, так и грязью или пятнами. Для того, чтобы салфетки было удобно надевать и снимать, предусмотрены специальные крепления. Внешняя сторона кольца ребристая, поэтому салфетка надевается без проблем. После того, как салфетки использованы, их можно стирать в машинке. Кстати, если окно или любая другая поверхность уж очень грязные, стоит заменять салфетки по мере процесса очистки, чтобы не было разводов. В комплекте идет 14 пар салфеток. Этого хватит на очистку любых поверхностей. Микрофибра, из которой сделаны салфетки, не оставляет после себя волокон, не линяет, не скатывается, впитывает гораздо больше воды и грязи, чем обычная ткань, легко отстирывается, быстро высыхает после стирки и служит долго.

Робот не автономный и питается от сети. К нему подключен шнур питания с блоком питания. Если этот кабель соединить с удлинителем (идет в комплекте, длина — 4 метра), то можно добиться большой свободы действий для робота, так что помыть можно окно любой величины.

К стеклу робот присасывается, втягивая воздух через два специальных канала. Воздух проходит через те самые салфетки из микрофибры. Забегая наперед, стоит сказать, что даже, если пропадет электричество (все мы знаем надежность работы электросетей), то робот не отвалится от стекла и не полетит вниз с 16-го этажа. Внутри установлен автономный аккумулятор, который позволяет роботу продержаться на окне достаточно долго, пока владелец не узнает о проблеме (при отключении электричества робот издает писк) и не снимет устройство. Заряда встроенного аккумулятора хватает примерно на 20 минут.

Кроме того, Hobot оснащается страховочным тросом, который рекомендуется пристегивать к надежной опоре (батарея отопления и т.п.). Длина троса — 4,5 метра.

Как это работает

Для начала работы нужно приложить робота к стеклу, включить тумблер, предварительно зарядив аккумулятор и отпустить. Робот должен зависнуть на одном месте. После этого уже на пульте нажимаем кнопку старта.

После того, как робот установлен и включен, его можно использовать для очистки любых гладких поверхностей. Это может быть зеркало, кафель, лакированное дерево, гранит или мрамор. Все, что угодно. После включения робот начинает двигаться, засасывая частички пыли и грязи. У него есть три общих режима уборки. Прибор можно использовать и для режима сухого режима уборки, когда он вытирает поверхность от пыли, и для влажной уборки — в этом случае на очищаемую поверхность нужно самостоятельно нанести средство для очистки. Нужно также и смачивать салфетки, причем не стоит делать это со слишком большим энтузиазмом — они должны быть влажными, но не сочиться влагой. В противном случае робот не сможет забраться по вертикальной поверхности наверх.

Разработчик рекомендует использовать сразу три режима:
1. Сухой, когда робот проходится по стеклу сухими салфетками, убирая пыль и другие типы загрязнений;
2. Режим влажной очистки, с использованием увлажения и чистящих средств;
3. Удаление разводов, которые могли остаться на стекле или зеркале. Для этого нужно поменять салфетки на чистые, добавить немного воды и отправить робота снова на окно.

Чаще всего, для уборки хватает и одного режима. Однако, все зависит от степени загрязненности покрытия, которое придется чистить. Сухая очистка занимает около 25 минут (окно площадью в 6 квадратных метров). Влажная — около 35 минут.

Иногда робот пропускает какие-то места. В этом случае нужен пульт управления, при помощи которого устройство можно направить на очистку пропущенного места.

При очистке стола робот не упадет, дойдя до края. У него есть специальные датчики, которые улавливают конец рабочей поверхности, благодаря чему робот начинает двигаться в другом направлении. После завершения работы он издает писк, который сигнализирует о завершении работы.

Технические характеристики Hobot 188

  • Цвет: белый;
  • Мощность: 80 Вт;
  • Длина шнура адаптера: 1 м;
  • Длина шнура робота: 4 м;
  • Длина страховочного троса: 4,5 м;
  • Время работы от аварийной батареи: 20 мин;
  • Скорость чистки: 0,25 кв.м./мин (с учетом двойного прохода);
  • Максимальная ширина убираемой поверхности: 5,0 м;
  • Максимальная высота убираемой поверхности: 6,0 м;
  • Минимальная ширина убираемой поверхности: 0,32 м;
  • Минимальная высота убираемой поверхности: 0,5 м;
  • Размеры: 295 х 148 х 120 мм;
  • Вес: 0,94 кг;
  • Вес в упаковке: 2,15 кг.

Комплектация

  • Робот для мойки окон Hobot-188
  • Пульт управления;
  • Адаптер со шнуром 1 м;
  • Удлинитель 4 м;
  • Салфетки из микрофибры (14 шт.);
  • Страховочный трос 4,5 м;
  • Документация.

Где купить?

В России робот Hobot-188 продается у нас, в компании Madrobots.




ссылка на оригинал статьи https://geektimes.ru/post/287534/

The Ultimate GMO: дрожжи с полностью искусственными хромосомами

image

Продолжается самый масштабный (количественно и качественно) эксперимент генной модификации организма-эукариота.

Международная группа из более чем 200 ученых опубликовала в журнале Science последние результаты проекта Synthetic Yeast Project (Sc2.0), конечной целью которого является создание пекарских дрожжей с полностью синтетическим геномом. На данный момент синтезированы 6 хромосом, которыми заменили их природные аналоги. Организмы Saccharomyces cerevisiae, полученные в результате, вполне жизнеспособны и обладают несколькими заранее заданными сложными дизайнерскими свойствами.

Эукариоты – «нормальноядерные» – организмы, у которых генетический материал заключен в отдельную органеллу, окруженную мембраной-оболочкой, – ядро. Упрощенно можно сказать, что все живое, не являющееся бактериями (у них нет ядра), – эукариоты. Дрожжи – образцовый организм-эукариот как для обычного человека (достаточно вспомнить, как получаются хлеб и пиво), так и для биотехнолога. Будучи так называемым модельным организмом, дрожжи обладают наилучшими свойствами для изучения базовых процессов жизни клеток эукариот. Неудивителен, соответственно, и выбор организма командой Sc2.0 для столь беспрецедентных вмешательств.

Базовый принцип дизайна нового генома, декларируемый командой, – достичь баланса между сохранением фенотипа «дикого вида» (внешних характеристик природного варианта подвида S288C), введением индуцированной геномной «подвижности» и удалением источников геномной нестабильности.

image

Хромосомы, назначенные для синтеза подкомандам

Сохранение фенотипа достигается за счет сохранения собственно генов. Их порядок и количество на хромосомах решили не менять, за исключением отдельных важных специфических групп.
Индуцирование геномной «подвижности» достигается за счет рекомбинационной системы SCRaMbLE, которая тасует участки хромосом при подаче определенного сигнала на специально введенные в геном на стратегические места участки loxPsym. Этим достигается возможность включения симуляции одного из глобальных, «больших» механизмов эволюции на масштабе всего генома. В то же время, ученые попытались удалить такой масштабный источник геномной эволюции, как мобильные элементы, которые при определенных условиях сами себя «копипастят» или «катпастят» с непредсказуемым результатом. Можно сказать, что ученые взяли макроэволюцию генома дрожжей из рук случайности в свои.

Другие изменения самого кода включают выделение генов трансферных РНК на отдельную неохромосому, удаление множества интронов, включение в геном специальных тегов для удобства различения в клетках синтетических и естественных хромосом, а также включение спецучастков для упрощения сборки хромосом. Среднее расстояние между участками, отличающими синтетический геном от естественного, мало – около 400 базовых пар ДНК.

Синтез, при всей своей революционности, все же происходил не с нуля, а путем удаления и присоединения новых участков к естественным хромосомам (иерархическая рекомбинация). Для ускорения процесса разные хромосомы были заданы для синтеза подкомандам в разных странах. Сборка нескольких искусственных хромосом в одной клетке также происходит иерархически.

Используя методы фенотипирования, структурной и функциональной геномики, ученые убедились в нормальном функционировании синтетических дрожжей. Один из таких методов контроля успешного хода проекта — контактный анализ хромосом. Современный метод Hi-C позволяет анализировать внутреннюю структуру ядра путем вычисления вероятностей контактов хромосом самих с собой и между собой на разных участках. Трехмерная версия такой визуализации наглядно показала, что искусственные хромосомы, несмотря на свою укороченность (удалены интроны) и дизайнерские вставки, занимают положения в ядре, сходные со своими природными аналогами. На иллюстрации ниже каждая хромосома представляет собой пару ветвей разной длины, свисающих из определенной точки на своем протяжении – центромера (белые кружки вверху на A). Выделенные цветом искусственные хромосомы на B, C и D однозначно в целом соответствуют своим аналогам на A по положению и ориентации. Следует заметить, что эта визуализация – именно вероятностно-усредненная по миллиардам клеток, так что показаны не «положения» ветвей, а нечто отдаленно ассоциирующееся с квадратом модуля пси-функции в квантовой механике.

image

Работа по сборке велась и ведется «снизу вверх», с полным компьютерным моделированием, пошаговым синтезом участков с последующим дебаггингом. На данный момент команда не столкнулась с серьезными принципиальными багами, которые бы поставили под вопрос возможность реализации проекта или теоретическую базу дизайна.

Synthetic Yeast Project — наиболее значительный «акт творения» живого человеком на сегодняшний день. Дрожжи теперь станут не только самым изученным эукариотом, но и точкой опоры, благодаря которой можно учиться путем создания. Книгу жизни вряд ли можно понять, не попытавшись ее написать.

Команда планирует закончить сборку организма к концу 2017 года.
ссылка на оригинал статьи https://geektimes.ru/post/287536/

Гайд по созданию простого фоторедактора

Сегодня мы предлагаем читателям подробное руководство по созданию простого фоторедактора на iOS. Для опытных разработчиков задача несложная, но новичкам подобный пошаговый разбор всего процесса, возможно, окажется полезен. Мы отдали предпочтение классической среде разработки для выбранной операционной системы – Xcode version 8.2.1. Разработку будем вести, опять же, на классическом объектно-ориентированном языке программирования Objective-C.

Прежде чем приступать к собственно разработке, предлагаем сначала разбить задачу на подзадачи.

  1. Загрузить фотографию из галереи
  2. Создать коллекцию с фильтрами
  3. Реализовать возможность применять любой из фильтров на выбранную нами фотографию
  4. Сохранить результат в галерею.

Итак, приступим. Открываем Xcode и выбираем шаблон для разработки под iOS во вкладке Application -> Single View Application. Назовем наш проект My First Photo Editor. Далее укажем директорию, куда хотим сохранить проект.

Выберем механизм разработки интерфейса программы main.storyboard.

Нам придется переходить и на другие сцены, которые будут добавляться позже. Для этого перетащим Navigation Controller на storyboard.

Удалим Root View Controller Scene – он нам не понадобится. Теперь осталось связать Navigation Controller c исходным View Controller.

Выбираем Navigation Controller, ставим галочку напротив «Is Initial View Controller» (Navigation Controller > Attributes Inspector > View Controller > Is Initial View Controller; на картинках можно увидеть, как это сделать) и связываем Navigation Controller с View Controller.

На View Controller нам понадобится кнопка для открытия окна галереи. Перенесем ее из Object Library.

Создадим событие нажатия на кнопку и добавим его в свойства контроллера.

Так как мы будем использовать делегатные методы, добавим в интерфейс контроллера ViewConroller.h протоколы делагатов UIImagePickerControllerDelegate,UINavigationControllerDelegate

Получается:

@interface ViewController : UIViewController<UIImagePickerControllerDelegate,UINavigationControllerDelegate>

Пропишем в действии кнопки следующие строки:

 - (IBAction)btnOpen_pressed:(id)sender { UIImagePickerController *picker = [[UIImagePickerControlleralloc] init]; picker.delegate = self;     [pickersetSourceType:UIImagePickerControllerSourceTypePhotoLibrary];     [selfpresentViewController:picker animated:YEScompletion:nil];// вызов окна галереи }  


Запускаем проект и видим на симуляторе нашу кнопку. Попробуем нажать и… получим краш. Почему? Мы забыли добавить свойство в info.plist NSPhotoLibraryUsageDescription. Исправим эту ошибку.

Теперь нужно добавить на storyboard еще один View Controller. Сделаем связь по схеме, представленной на скриншотах.

Пропишем Indetifier ключ «toFilters». На иллюстрации показано, как это можно сделать. Выберем связь на Storyboard > Attributes Inspector >Storyboard > identifier > toFilters.

Теперь добавим во ViewController.m методы выбора фото и перехода на новый контроллер.

 // делагатный метод, который вызывается после выбора изображения -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {     [pickerdismissViewControllerAnimated:YEScompletion:nil]; self.btnOpenPhoto.enabled = NO;   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  self.selectedImage = image; dispatch_async(dispatch_get_main_queue(), ^{             [selfperformSegueWithIdentifier:kFiltersSegueIdsender:self]; self.btnOpenPhoto.enabled = YES;          });     });  } // делагатный метод, который вызывается при отмене выбора изображения -(void) imagePickerControllerDidCancel:(UIImagePickerController *)picker {     [pickerdismissViewControllerAnimated:YEScompletion:nil]; }   #pragma mark - segue // Этот метод будет вызван у контроллера, из которого был начат переход  -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {//kFiltersSegueId – это константа типа String с идентификатором перехода «toFilters» if ([segue.identifierisEqualToString:kFiltersSegueId])     { FiltersViewController *destinationController = (FiltersViewController *)segue.destinationViewController; destinationController.image = self.selectedImage;     } }

Для нового контроллера нам нужно создать особый класс. Для этого в верхней панели выбираем File->New->File->Cocoa Touch Class->Next. Назовем его FiltersViewController и обязательно выберем в графе Subclass UIViewController. В Indetity inspector прописываем наш класс.

Добавляем на контроллер Scroll View (или какую-нибудь другую View, на ваше усмотрение), в нем будет отображаться изображение с применненым фильтром. Создаем связь с FiltersViewController.

Добавим Scroll View в свойства контролера, как делали ранее. Добавим свойства UIImageView и UIimage.

При этом свойство UIImage нужно добавить в FiltersViewController.h, так как мы планируем использовать его в другом классе.

Теперь создадим коллекцию с фильтрами. Находим в Object Library объект Collection View.

Создаем новый класс для коллекции. Заходим в xib файл, добавляем UIimageView и Label.
Длаее зададим размеры UIImageView. Самый простой способ это сделать – зажать правой клавишей объект и перетащить его на тот, к которому хотим привязать. Выбираем связи, как показано на скриншоте. Затем переходим в Size inspector этого объекта и изменяем настройки. Выставляем все constraints в 0.

Устанавливаем связь между элементами.

Импортируем новый класс в FiltersViewController

и добавляем FiltersViewControllerCell в этот класс вот таким образом:

Теперь добавим в свойства FiltersViewController массив NSMutableArray и оператор NSOperationQueue – они нам пригодятся чуть позже.

Жизненный цикл UIViewController начинается с метода loadView. А значит, в этом методе нам нужно передать картинку, которую мы выбрали на прошлой сцене, и добавить превью в коллекцию.

В результате loadView будет выглядеть так:

- (void)loadView{      [super loadView];     self.automaticallyAdjustsScrollViewInsets = NO;     [[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotificationobject:[UIDevice currentDevice]];          if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {         self.navigationController.interactivePopGestureRecognizer.enabled = NO;     }          _imageView = [[UIImageView alloc] init];     [_scrollView addSubview:_imageView];     _imageView.image = _image;     _imageView.frame = CGRectMake(0, 0, _image.size.width, _image.size.height);          _filteringQueue = [NSOperationQueue new];     _filteringQueue.maxConcurrentOperationCount = 1;     _filteringQueue.qualityOfService = NSQualityOfServiceUserInitiated;   [self.filtersCollection registerNib:[UINib nibWithNibName:@"FiltersCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"filtersCollectionCell»];//Регистрируем  xib файл для коллекции.     // Do any additional setup after loading the view. } 


Чтобы не перезагружать контроллер, создадим класс Extension и добавим в него методы, которые будут выполнять функцию наложения фильтра на изображение:

#import "Extension.h"  @implementation CIImage (Extension)  -(CIImage *)applyFilter:(long)i {     CIFilter *filter;          switch (i) {         case 0:             return [self copy];             break;         case 1:             filter = [CIFilter filterWithName:@"CISepiaTone"];             break;         case 2:             filter = [CIFilter filterWithName:@"CIColorMonochrome"];             break;         case 3:             filter = [CIFilter filterWithName:@"CIPhotoEffectMono"];             break;         case 4:             filter = [CIFilter filterWithName:@"CIPhotoEffectInstant"];             break;         case 5:             filter = [CIFilter filterWithName:@"CIHueAdjust"];             [filter setDefaults];             [filter setValue: [NSNumber numberWithFloat: M_PI] forKey: kCIInputAngleKey];             break;         case 6:             filter = [CIFilter filterWithName:@"CIHueAdjust"];             [filter setDefaults];             [filter setValue: [NSNumber numberWithFloat: M_PI_2] forKey: kCIInputAngleKey];             break;         case 7:             filter = [CIFilter filterWithName:@"CIColorInvert"];             break;         case 8:             filter = [CIFilter filterWithName:@"CIFalseColor"];             break;         case 9:             filter = [CIFilter filterWithName:@"CIPhotoEffectTonal"];             break;         case 10:             filter= [CIFilter filterWithName:@"CIPhotoEffectTransfer"];             break;         case 11:             filter= [CIFilter filterWithName:@"CIPhotoEffectProcess"];             break;         case 12:             filter= [CIFilter filterWithName:@"CIPhotoEffectChrome"];             break;         case 13:             filter = [CIFilter filterWithName:@"CIGaussianBlur"];             [filter setDefaults];             [filter setValue: [NSNumber numberWithFloat:(self.extent.size.width + self.extent.size.height)/30.] forKey:@"inputRadius"];         default:             break;     }          [filter setValue:self forKey:kCIInputImageKey];     CIImage *result = [filter valueForKey:kCIOutputImageKey];     return result; } @end   @implementation UIImage (Extension)   +(NSArray *)filterNames {     static NSArray *names;     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         names = @[@"Original",@"Sepia", @"Old Photo", @"Mono", @"Instant", @"Shift", @"Hue", @"Invert", @"Falce", @"Tonal", @"Transfer", @"Process", @"Chrome"];     });     return names; } - (UIImage *)applyFilter:(long)i {     CIImage *ciImage = [[CIImage alloc] initWithImage: self];     CIImage *result = [ciImage applyFilter:i];     CGRect extent1 = [result extent];     CIContext *context = [CIContext contextWithOptions:nil];     CGImageRef cgImage1 = [context createCGImage:result fromRect:extent1];     UIImage *img = [UIImage imageWithCGImage:cgImage1];     CGImageRelease(cgImage1);     return img; }    + (instancetype)imageWithCIImageImproved:(CIImage *)img {     CGRect extent = [img extent];     CIContext *context = [CIContext contextWithOptions:nil];     CGImageRef cgImage = [context createCGImage:img fromRect:extent];     UIImage *image = [UIImage imageWithCGImage:cgImage];     CGImageRelease(cgImage);     return image; }  @end

далее пропишем интерфейс вызовов в Extension.h:

#import <UIKit/UIKit.h>  @interface CIImage (Extension) -(CIImage *)applyFilter:(long)i; @end  @interface UIImage (Extension)  @property (class, readonly) NSArray * filterNames;  + (NSArray *)filterNames; - (UIImage *)applyFilter:(long)i; + (instancetype)imageWithCIImageImproved:(CIImage *)img; @end

Добавляем следующие методы для коллекции и для Scroll View в класс FiltersViewController.m:

#pragma mark - Collection // Возвращает количество ячеек для секции, обязательный метод  -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { returnself.filterPreviews.count; }
 // Возвращает размер ячейки  - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat sz = collectionView.frame.size.height - 6; returnCGSizeMake(sz, sz); }
 //Обязательный метод, в реализации которого мы возвращаем созданный и сконфигурированный объект UICollectionViewCell -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { long index = [indexPath item]; FiltersCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"filtersCollectionCell"forIndexPath:indexPath];  UIImageView *ivPreview = cell.imageFilter; ivPreview.image = self.filterPreviews[index]; ivPreview.clipsToBounds = YES; ivPreview.layer.cornerRadius = 3.; UILabel *labelName = cell.nameFilter; labelName.text = [UIImagefilterNames][index];    return cell; } // метод, который вызывается, если ячейка была успешно выбрана - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {      [self.filteringQueuecancelAllOperations];  __blockUIImage *filtered = nil; NSBlockOperation *operation1 = [NSBlockOperationblockOperationWithBlock:^{ filtered = [self.imageapplyFilter:[indexPath item]];     }]; NSBlockOperation *operation2 = [NSBlockOperationblockOperationWithBlock:^{         [CATransactionbegin]; dispatch_sync(dispatch_get_main_queue(), ^{ if (filtered) { _imageView.image = filtered;             } 		   });     }];     [operation2addDependency:operation1];     [self.filteringQueueaddOperation:operation1];     [self.filteringQueueaddOperation:operation2]; }
 #pragma mark - Scroll View // метод возвращает объект UIView, используемый при масштабировании содержимого -(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { returnself.imageView; } // делегатный метод, вызываемый во время изменения масштаба содержимого - (void)scrollViewDidZoom:(UIScrollView *)scrollView { CGSize sz = scrollView.contentSize;  float xInsets = MAX(0, scrollView.frame.size.width/2.-sz.width/2.); float yInsets = MAX(0, scrollView.frame.size.height/2.-sz.height/2.);     [scrollViewsetContentInset:UIEdgeInsetsMake(yInsets,xInsets,yInsets,xInsets)]; }
 // метод, который вызывается при смене ориентации - (void) orientationChanged:(NSNotification *)note {     [self.viewlayoutSubviews];     [selfupdateScaleInScrollView:self.scrollView]; } // метод, который корректно выводит содережимое ScrollView после изменения масштаба -(void)updateScaleInScrollView:(UIScrollView *)scrollView { UIImage *image = _image; float minScale = sizeFit(image.size,scrollView.frame.size).width/image.size.width; scrollView.maximumZoomScale = MAX(1,minScale); scrollView.minimumZoomScale = minScale; scrollView.zoomScale = minScale;  if (scrollView.zoomScale> scrollView.maximumZoomScale) scrollView.zoomScale = scrollView.maximumZoomScale; elseif (scrollView.zoomScale< scrollView.minimumZoomScale) scrollView.zoomScale = scrollView.minimumZoomScale;      [selfscrollViewDidZoom:scrollView]; } #pragma mark - other // далее приведен набор методов для масштабирования  CGSizesizeFill(CGSize size, CGSize sizeToFill) { CGSize newSize; if (size.width / sizeToFill.width< size.height / sizeToFill.height) newSize = CGSizeMake(sizeToFill.width, sizeToFill.width*size.height/size.width); else newSize = CGSizeMake(sizeToFill.height*size.width/size.height, sizeToFill.height);  return newSize; }  CGSizesizeFit(CGSize size, CGSize sizeToFit) { float w = size.width/sizeToFit.width; float h = size.height/sizeToFit.height; return w > h ? CGSizeMake(sizeToFit.width, size.height/w) : CGSizeMake(size.width/h, sizeToFit.height); } CGRectframeFill(CGSize size, CGSize sizeToFill) { CGSize szFill = sizeFill(size, sizeToFill); CGPoint pntFill = CGPointMake(sizeToFill.width/2.-szFill.width/2., sizeToFill.height/2.-szFill.height/2.); returnCGRectMake(pntFill.x, pntFill.y, szFill.width, szFill.height); }

Следующий метод жизненного цикла ViewController – это viewDidLoad. viewDidLoad является хорошим местом для продолжения инициализации контроллера. Нам он не понадобится по причине того, что инициализация всех элементов дизайна уже определена. Однако размеры view не заданы, поэтому добавим обработку ScrollView в следующим этапе жизненного цикла viewWillAppear:

-(void)viewWillAppear:(BOOL)animated {     [superviewWillAppear:animated];     dispatch_async(dispatch_get_main_queue(), ^{         [selfupdateScaleInScrollView:self.scrollView];     });  } 

Создадим метод для вывода превью фильтров:

-(void)makeFilterPreviews {     self.filterPreviews = [[NSMutableArrayalloc] init];          float size = 90 * [UIScreenmainScreen].scale; 
                  CGRect cropRect = frameFill(self.image.size,CGSizeMake(size, size));                  UIGraphicsBeginImageContext(CGSizeMake(size, size));         [self.imagedrawInRect:cropRect];         UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();         UIGraphicsEndImageContext();                 CIImage *ciimg = [[CIImagealloc] initWithImage:cropped]; 
                  for (int i = 0; i < [UIImagefilterNames].count; i++)         {                          CIImage *ciFiltered = [ciimg applyFilter:i];             UIImage *filtered = [UIImageimageWithCIImageImproved:ciFiltered];                              [self.filterPreviewsaddObject:filtered]; 
                      }       } 

и пропишем вызов этого метода в loadView

[self makeFilterPreviews]; 

Запустим наш проект. После выбора изображения приложение зависает на некоторое время. Это происходит потому, что на применение фильтров к изображению требуется время. После применения всех фильтров загрузится контроллер с изображением и коллекция с фильтрами.

Чтобы обеспечить отзывчивость интерфейса, добавим асинхронность при выполнении метода.

-(void)makeFilterPreviews {     self.filterPreviews = [[NSMutableArrayalloc] init]; 
 //Заполним массив пустыми элементами для предварительного отображения в коллекцию ->     for (long i = 0; i < [UIImagefilterNames].count; i++)     {         [self.filterPreviews addObject:[[UIImage alloc]init]];     }          float size = 90 * [UIScreenmainScreen].scale;               CGRect cropRect = frameFill(self.image.size,CGSizeMake(size, size));          UIGraphicsBeginImageContext(CGSizeMake(size, size));     [self.imagedrawInRect:cropRect];     UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();          CIImage *ciimg = [[CIImagealloc] initWithImage:cropped]; //создадим очередь для добавления превью изображения с примененным фильтром     NSOperationQueue *queue = [[NSOperationQueuealloc]init];     queue.maxConcurrentOperationCount = 2;          for (int i = 0; i < [UIImagefilterNames].count; i++)     { 
         [queueaddOperationWithBlock:^{             CIImage *ciFiltered = [ciimg applyFilter:i];             UIImage *filtered = [UIImageimageWithCIImageImproved:ciFiltered];                          @synchronized (self.filterPreviews) {                 [self.filterPreviewsreplaceObjectAtIndex:i withObject:filtered];                 dispatch_sync(dispatch_get_main_queue(), ^{                     [self.filtersCollectionreloadData];                 });             }         }];     }       } 
 


@synchronized (self.filterPreviews) блокирует редактирование массива filterPreviews для всех элементов, кроме текущего.
Чтобы корректно обновить коллекцию, нужно это делать в потоке dispatch_get_main_queue().

Оставшиеся методы жизненного цикла ViewController:

  • viewDidAppear:(BOOL)animated — вызывается после отрисовки всех элементов интерфейса
  • viewWillDisappear:(BOOL)animated — вызывается перед удалением всех элементов интерфейса
  • viewDidDisappear:(BOOL)animated – вызвается после удаления
  • viewDidUnload – вызывается, когда view выгружен из памяти

нами не использовались.

Теперь создадим кнопку Save.

Добавим Navigation Item

добавим кнопку на Navigation Item

и пропишем в действии кнопки:

- (IBAction)saveBtn:(id)sender {     UIImageWriteToSavedPhotosAlbum(_imageView.image, nil, nil, nil); }

Запустим приложение, выберем изображение и фильтр. Когда фильтр будет применен, нажмем Save. На симуляторе мы можем использовать комбинацию клавиш cmd+shift+h (это тоже самое что и кнопка “Домой” на iPhone), открыть Галерею – и найти там то самое изображение.
ссылка на оригинал статьи https://habrahabr.ru/post/325352/