Repository navigation
TALIE-1651 new interfaces - #564
NastyBoget wants to merge 10 commits into
Conversation
14be5b0 to
01c0a9a
Compare
| structure: dict[AbstractNode, Iterable[AbstractNode]] | ||
|
|
||
|
|
||
| class AbstractProcessor(Generic[_Node, _Config, _DeployConfig], metaclass=ABCMeta): |
There was a problem hiding this comment.
TODO: возможно, стоит еще подумать в сторону subprocessors для составных обработчиков, которые разбиваются на подзадачи с нестандартным входом/выходом для запуска этих подзадач на CPU/GPU (пример: OCR+постпроцессинг)
There was a problem hiding this comment.
Описала в README тип обработчика configurator - составной обработчик
| from dedoc_future.datamodel.nodes.page import PageNode, PageNodeWrapper | ||
|
|
||
|
|
||
| class PopplerRender(AbstractProcessor[PageNode, PdfBaseConfig, ImmutableBaseModel]): |
There was a problem hiding this comment.
функционал dedoc.readers.pdf_reader.pdf_base_reader.PdfBaseReader._split_pdf2image (предполагается, что до этого в документ добавлены пустые page ноды с помощью PagesCreator)
впоследствии планируется добавить еще один способ рендеринга, предложенный Олегом
| from dedoc_future.datamodel.nodes.file import FileNode | ||
|
|
||
|
|
||
| class ContentMimeDetector(AbstractProcessor[FileNode, ImmutableBaseModel, ImmutableBaseModel]): |
There was a problem hiding this comment.
функционал dedoc.utils.utils.get_file_mime_by_content - предполагается запускать этот обработчик тогда, когда основная обработка упала с ошибкой
| from dedoc_future.datamodel.nodes.file import FileNode | ||
|
|
||
|
|
||
| class MetadataExtractor(AbstractProcessor[FileNode, ImmutableBaseModel, ImmutableBaseModel]): |
There was a problem hiding this comment.
функционал dedoc.metadata_extractors.concrete_metadata_extractors.base_metadata_extractor.BaseMetadataExtractor, предполагается использовать его в начале, чтобы впоследствии определять нужный парсер
Tenebroso-M
left a comment
There was a problem hiding this comment.
Кажется не хватает примера какого-то обработчика для всего документа. И после примеров для узлов мне начало казаться, что это некоторая вырожденная сущность и все различие состоит в том, работаем мы с узлом-файлом, или узлами-страницами
| """ | ||
| Predicate function that returns whether the processor needs to be executed. | ||
| """ | ||
| return lambda document, node, config: True |
There was a problem hiding this comment.
возможно лучше сразу заявить метод, который принимает документ, узел и конфиг, а не прятать его за полем
There was a problem hiding this comment.
определила метод can_process, который сразу принимает то, что ему нужно
| return lambda document, node, config: True | ||
|
|
||
| @abstractmethod | ||
| def process(self, document: TalismanDocument, nodes: Sequence[_Node], config: _Config) -> ProcessorResult[_Node]: |
There was a problem hiding this comment.
не очень понимаю, будет ли удобен такой метод сразу для двух видов обработчиков. Вроде у нас принципиально есть те, которые ждут на вход только отдельные узлы (зачем им документ?) и те, которые ждут целый документ (нужно ли для них всегда перечислять все узлы документа, чтобы они могли измениться?)
There was a problem hiding this comment.
согласна, сделала AbstractNodeProcessor и AbstractDocumentProcessor
| :param document: Document to process. | ||
| :param nodes: Nodes to process (only these nodes will be changed). The nodes should belong to the given document. | ||
| :param config: Processing configuration. | ||
| :return: Changed input nodes and new nodes with structure. |
There was a problem hiding this comment.
почему не возвращать в качестве результата документ + (если нужно) те узлы которые изменились, удалились и добавились?
There was a problem hiding this comment.
сделала что-то похожее в AbstractNodeProcessor, он не работает с документом, только с узлами
| Process the input document and return an enriched document. | ||
|
|
||
| :param document: Document to process. | ||
| :param nodes: Nodes to process (only these nodes will be changed). The nodes should belong to the given document. |
There was a problem hiding this comment.
а что с добавлением новых узлов, когда из картинки текст получаем, или что-то похожее?
There was a problem hiding this comment.
Может быть, я не совсем поняла вопрос, но сейчас мы на вход можем принять ImageNode (например) и записать в результат добавляемую структуру с новыми узлами new_nodes
06b841e to
4a2dd77
Compare
|
По поводу хранения картинок и рендеров страниц - нужно архитектурно предусмотреть 2 крайне важных вещи - отгрузку на диск и/или освобождение картинок когда не нужно, а также возможность передачи данных через shared memory. Возможно для этого нужно добавить хранение по ссылкам для штук типа PdfPageMarkup._orig_image, если мы хотим сделать передачу чисто примитивов типа ndarray(я в своём варианте именно так и делал, и рекомендую), либо передачу целых объектов со страницами. По моим замерам получалось что если хранить все страницы с их рендерами в памяти, в том числе те, которые не нужны, то получится потребление памяти на 300 страничном документе 16+ гб вместо 6.5гб(в которые входят всякие библиотеки загруженные в память каждого процесса, типа numpy/torch), так что отсутствие выгрузки из памяти ненужных страниц быстро убъет обработчики по памяти Shared memory необходим так как дефолтный метод для больших массивов данных, потому что дефолтная передача между процессами крайне неоптимальна, обычный путь это "pickle.dumps в байты, запись из пользовательской памяти в ядро, чтение из ядра в перекрывающийся буфер, оттуда в BytesIO, оттуда в bytes через getvalue, и наконец pickle.loads в новый массив", то есть 6 копирований. Если передавать через shared memory питоновские объекты, нужно их пиклить, это 4 копирования. Если передаём чисто ndarray и подобные объекты, а всё остальное оформлено как обёртка со ссылкой, и передается по обычному пути, то на основной кусок памяти в виде условного ndarray получаем 2 копирования. Если вспомнить что для 300 страничного документа получается 10+гб данных, то получается что это копирование 60гб против 20гб, с добавками на выделение буферов на это всё - у меня на замерах разница в десятки секунд только на этом моменте(напоминаю что в итоге я дожал общее время исполнения примерно до минуты) |
3c64efb to
46b8b6b
Compare
Согласна с тем, что об этом нужно подумать заранее, но думаю, что стоит обсудить. Я бы предложила сделать некоторую обертку (хотя бы для node) в ссылку на shared memory и обратно в np.array. Непосредственно на обработчики это не должно никак повлиять, структуры данных затронет, но может быть сделать это отдельной задачей? Upd: добавила комментарии в код и создала на это отдельную задачу. |
There was a problem hiding this comment.
Конфигурирование обработчиков еще не продумано до конца (буду делать это в отдельной задаче https://jira.intra.ispras.ru/browse/TALIE-1707)
Основные моменты:
- как (в последовательном случае) обработчики будут отрабатывать и передавать друг другу данные
- на любом уровне вложенности обработки может встретиться составной обработчик (configurator), но такую функциональность не надо использовать без необходимости
В рамках ПР:
Binarizer,OrientationClassification,SkewCorrection), вспомогательные модули для начала обработки (MetadataExtractor,PagesCreator,PopplerRender) иContentMimeDetector, обработчикDocxProcessorреализован не до конца (оставила для примера)