Примеры расширений для работы с оптическим распознаванием символов (OCR)¶
В данном разделе рассматриваются распространённые сценарии, в которых требуется изменить или настроить логику работы модуля OCR.
Tip
Все расширения, как клиентские, так и серверные, доступны в открытой части типового решения.
Создание карточки операции OCR без связи с исходной карточкой документа¶
Такая задача может возникнуть, когда карточка исходного документа вместе с приложенным распознаваемым файлом отсутствуют в системе на момент выполнения процесса OCR. В этом случае, после верификации и сохранения данных, карточка документа будет создана, а распознанный файл будет приложен к документу.
Для создания карточки операции OCR необходимо указать тип карточки документа, приложить распознаваемый файл к карточке операции OCR и создать запрос на распознавание файла с необходимыми параметрами.
Note
Если карточка исходного документа использует типы документов, рекомендуется указать тип документа при создании карточки операции OCR. Этот тип документа может быть изменён или установлен верификатором при сохранении результата OCR.
Tip
Используйте метод Tessa.Extensions.Default.Server.TextRecognition.OcrExtensionHelper.CreateOcrOperationCardAsync
, который инкапсулирует описанную выше логику.
Переопределение действий для пунктов контекстного меню и кнопок тулбара в карточке операции OCR¶
Чтобы переопределить действие, выполняемое при нажатии на пункт Распознавание текста в контекстном меню файла исходной карточки документа, необходимо в расширении типа FileExtension
найти пункт контекстного меню с именем TextRecognition
и присвоить ему новое действие:
const textRecognition = context.actions.find(a => a.name === 'TextRecognition');
if (textRecognition) {
textRecognition.action = async _event => { await showMessage('My text recognition action'); }
}
Tip
Пункт меню Распознавание текста добавляется в расширении /defaultExtensions/default/textRecognition/ocrSourceFileMenuExtension.ts
.
Чтобы переопределить действие, выполняемое при нажатии на кнопку тулбара Сохранить результат OCR , необходимо в расширении CardUIExtension
найти кнопку с именем OcrResultSave
и присвоить ей новое действие:
const ocrResultSave = context.uiContext.cardEditor?.toolbar.items.find(i => i.name === 'OcrResultSave');
if (ocrResultSave) {
ocrResultSave.setCommand(async _item => { await showMessage('My result save command'); });
}
Tip
Кнопка тулбара Сохранить результат OCR добавляется в расширении /defaultExtensions/default/textRecognition/ocrToolbarUIExtension.ts
.
Подписка на событие выбора поля и распознанного элемента в карточке операции OCR¶
В карточке операции OCR возможна двусторонняя реакция на изменение выбранного элемента.
Для подписки на событие изменения поля в области верификации необходимо в расширении CardUIExtension
найти в карточке контрол обозревателя свойств с именем OcrGrid
и отслеживать текущее выбранное свойство:
const ocrGrid = context.model.controlsBag.find(c => c.name === 'OcrGrid') as OcrGridViewModel;
if (ocrGrid) {
this.disposeList.add(
reaction(
() => ocrGrid.control.selectedProperty,
async (property: OcrProperty) => { await showMessage(property.caption.localized); }
)
);
}
Для подписки на событие изменения распознанного элемента в области предпросмотра следует воспользоваться методом loaded
в расширении FilePreviewExtension
:
const { previewerViewModel, cardModel } = context;
if (cardModel && previewerViewModel instanceof PdfPreviewerViewModel) {
this.disposer = previewerViewModel.recognizedCollection?.recognizedBoxSelected.add(async box => {
await showMessage(box?.text || 'EMPTY');
});
}
Аналогичную логику можно реализовать с помощью расширения CardUIExtension
:
const previewer = context.model.controlsBag.find(c => c.name === 'Preview') as FilePreviewViewModel;
if (previewer) {
this.disposeList.add(
previewer.fileControlManager.onPreviewerLoaded.add(({ previewerViewModel }) => {
if (previewerViewModel instanceof PdfPreviewerViewModel) {
this.disposer = previewerViewModel.recognizedCollection?.recognizedBoxSelected.add(async box => {
await showMessage(box?.text || 'EMPTY');
});
}
})
);
}
Tip
Подписки на события выбора поля и распознанного элемента осуществляется в расширениях /defaultExtensions/default/textRecognition/ocrVerificationUIExtension.ts
и /defaultExtensions/default/examples/32_ocrPreviewerUIExtension.ts
.
Добавление или переопределение валидатора поля в карточке операции OCR¶
Чтобы создать собственный валидатор, необходимо унаследоваться от класса OcrValidator
или его дочернего класса и реализовать логику валидации:
export class OcrGuidValidator extends OcrStringValidator {
//#region base overrides
protected get patterns(): ReadonlyArray<RegExp> {
return [/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i];
}
protected get error(): string {
return 'UUID value is invalid';
}
protected compileValue(match: RegExpExecArray): string {
return match[0];
}
//#endregion
//#region public methods
validate(value: string): OcrValidationResult {
let result = super.validate(value);
if (!result.validationResult || result.validationResult.isSuccessful) {
for (const pattern of this.patterns) {
const match = pattern.exec(value);
if (match && match.length > 0) {
const compiledValue = this.compileValue(match);
const parsedValue = Guid.parse(compiledValue);
const formattedValue = localize(FormattingHelper.formatValue(parsedValue));
let validationResult: ValidationResult | null = null;
if (formattedValue != value) {
validationResult = ValidationResult.fromText(formattedValue, ValidationResultType.Info);
}
return new OcrValidationResult(validationResult, compiledValue, formattedValue);
}
}
validationResult = ValidationResult.fromText(localize(this.error), ValidationResultType.Error);
result = new OcrValidationResult(validationResult);
}
return result;
}
//#endregion
}
Реализованный валидатор возвращает результат проверки, а также скомпилированное и отформатированное значение. Для использования созданного валидатора необходимо в расширении CardUIExtension
найти контрол в обозревателе свойств для соответствующего поля и добавить ему логику проверки:
const ocrGrid = context.model.controlsBag.find(c => c.name === 'OcrGrid') as OcrGridViewModel;
if (ocrGrid) {
const validator = new OcrGuidValidator();
const propertyAlias = OcrGridDataProvider.serializeKey(sectionName, fieldName);
const property = ocrGrid.control.findProperty(propertyAlias) as OcrProperty;
property?.control.validationContainer.add(context => {
if (!property.control.inProgress && context.value) {
const result = validator.validate(context.value);
if (result?.validationResult) {
context.validationResult.add(result.validationResult);
context.handled = result.validationResult.hasErrors;
}
}
});
}
Tip
В решении существует набор типовых валидаторов для стандартных типов, которые находятся в папке /defaultExtensions/default/textRecognition/validators
. Они используются в контроле OcrTextFieldViewModel
. Эти валидаторы можно переопределить, если для них был задан идентификатор (см. пример в файле /defaultExtensions/default/textRecognition/components/grid/properties/ocrReferenceProperty.ts
).
Изменение цветов распознанных элементов в карточке операции OCR¶
По умолчанию используется базовый набор цветов для распознанных элементов, отображаемых в области предпросмотра. Для переопределения цвета распознанного элемента можно воспользоваться методом loaded
в расширении FilePreviewExtension
:
const { previewerViewModel, cardModel } = context;
if (cardModel && previewerViewModel instanceof PdfPreviewerViewModel) {
previewerViewModel.recognizedCollection?.pages
.flatMap(p => p.items.flatMap(i => i.boxes))
.forEach(box => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
box.color.main = `rgba(${r},${g},${b},0.5)`;
box.color.additional = `rgba(${r},${g},${b},0.3)`;
// box.color = new OcrColor(...)
});
}
Переопределение сервиса распознавания текста в файле¶
На серверной стороне существует объект IOcrService
, который представляет собой сервис распознавания текста в файле. Этот объект функционирует в двух режимах:
- Синхронный режим, представленный объектом
IOcrSyncService
, который отвечает за непосредственное распознавание текста в файле. - Асинхронный режим, представленный объектом
IOcrAsyncService
, который предназначен для создания и управления операциями OCR. Этот режим позволяет инициировать распознавание текста в фоне и получать результат по завершении операции.
Когда создаётся запрос на распознавание файла, в карточке операции OCR используется IOcrAsyncService
для инициации операции OCR. Фоновая служба веб-сервиса web
отслеживает новые запросы на распознавание текста в файлах и с помощью объекта IOcrOperationProcessor
выполняет их обработку. IOcrOperationProcessor
взаимодействует с синхронным сервисом IOcrSyncService
, чтобы завершить обработку операции.
Для адаптации и кастомизации процесса распознавания текста в файле, можно написать собственные реализации следующих компонентов:
IOcrSyncService
- для определения логики синхронного распознавания текста,IOcrAsyncService
- для управления асинхронными операциями OCR и их сохранением в системе,IOcrOperationProcessor
- для обработки и завершения операций OCR, инициированных в асинхронном режиме.
Реализации сервисов необходимо зарегистрировать в системе, чтобы они использовались вместо стандартных:
[Registrator]
public sealed class Registrator : RegistratorBase
{
public override void RegisterUnity() =>
NotNullOrThrow(this.UnityContainer)
.RegisterSingleton<IOcrSyncService, CustomOcrSyncService>()
.RegisterSingleton<IOcrAsyncService, CustomOcrAsyncService>()
.RegisterSingleton<IOcrOperationProcessor, CustomOcrOperationProcessor>();
}
Note
В сервисах IOcrSyncService
и IOcrAsyncService
можно пренебречь токеном балансировки JinniBalancingToken
, передавая и возвращая значение null
, если не предполагается взаимодействия с внешним сервисом OCR, который может использовать этот токен для балансировки нагрузки или других целей.
Форматы json
-структур, используемых в карточке операции OCR¶
Формат json
-файла с метаданными по распознанным элементам имеет следующий вид:
[
{
"index": 0, // номер страницы (нумерация с 0)
"width": 100.5, // ширина страницы
"height": 100.5, // высота страницы
"items": [
{
"layout": 2, // способ отображения элементов (2-6)
"boxes": [
{
"id": 1, // уникальный идентификатор элемента
"x": 10.5, // позиция элемента по оси X
"y": 10.5, // позиция элемента по оси Y
"width": 100, // ширина элемента
"height": 10, // высота элемента
"text": "Item", // распознанный текст
"confidence": 90.5, // уверенность (точность) распознавания
"rotation": 90, // угол наклона элемента
"language": "Russian" // язык распознанного текста
}
]
}
]
}
]
Important
Способ отображения элементов ("layout"
) должен быть уникален в рамках одной страницы. Значения описаны в Tessa.TextRecognition.Enums.OcrRecognizedLayout
.
Important
Уникальный идентификатор элемента ("id"
) должен быть уникален среди всех распознанных элементов на всех страницах.
Формат данных в формате json
, которые используются для верификации и переноса значений из карточки операции OCR в карточку документа имеет следующий вид:
{
"DocumentCommonInfo.Partner": { // Ключ поля
"value": { // Фактическое значение поля (объект или примитивный тип)
"PartnerID": "00000000-0000-0000-0000-000000000000",
"PartnerName": "ПОЛЕТ"
},
"displayed": "ПОЛЕТ", // Отображаемый текст в поле
"modified": true, // Признак наличия изменений в поле по отношению к исходной карточке
"refs": null // Ссылка на связанное(-ые) поле(-я) (уникальный идентификатор элемента)
},
"DocumentCommonInfo.Amount": {
"value": "1000000.00",
"displayed": "1 0 0 0 0 0 0",
"modified": true,
"refs": 1
},
"DocumentCommonInfo.Currency": {
"value": {
"CurrencyID": "acaabdfc-2c88-472f-a861-1813bfafe49d",
"CurrencyName": "RUB"
},
"displayed": "rub",
"modified": true,
"refs": 2
},
"DocumentCommonInfo.DocDate": {
"value": "2024-01-01T00:00:00Z",
"displayed": "01/01/2024",
"modified": true,
"refs": [10, 11]
},
"DocumentCommonInfo.FullNumber": {
"value": "Договор-0001",
"displayed": "Договор-0001",
"modified": false,
"refs": null
},
"DocumentCommonInfo.Subject": {
"value": null,
"displayed": null,
"modified": true,
"refs": null
}
}
Important
Ключ поля формируется из имён секции и поля карточки документа, в которую будет перенесено верифицированное поле.
Important
Если фактическое значение поля представлено объектом (используется для ссылочных значений), то поля объекта соответствуют полям ссылочной колонки из представления.