DEV Community

Cover image for Serverless App - Extração de Textos com Exibição de Layouts com Textract
Gustavo Mainchein
Gustavo Mainchein

Posted on

Serverless App - Extração de Textos com Exibição de Layouts com Textract

Entendendo o Textract:

O Amazon Textract é um serviço avançado de Machine Learning (ML) da AWS projetado para extrair automaticamente textos impressos ou manuscritos, além de identificar elementos de layout e dados estruturados a partir de documentos digitalizados. Ele é capaz de processar diversos tipos de documentos, como formulários, relatórios e recibos, facilitando a automação de tarefas que exigem a extração e organização de informações. A tecnologia é particularmente útil em cenários onde grandes volumes de documentos precisam ser analisados, permitindo uma leitura precisa e eficiente dos conteúdos, sejam eles simples ou complexos.

A base desse serviço é a tecnologia de reconhecimento óptico de caracteres (OCR), que utiliza algoritmos sofisticados de correspondência de padrões para analisar imagens de texto. O OCR realiza uma comparação detalhada, caractere por caractere, entre o conteúdo visualizado e um banco de dados interno, decodificando a imagem para gerar um texto digital legível. No entanto, o OCR convencional pode ser limitado quando se trata de interpretar variações complexas de escrita, especialmente manuscrita. Para superar esses desafios, o Amazon Textract adota o reconhecimento inteligente de caracteres (ICR), uma evolução do OCR. O ICR utiliza técnicas avançadas de machine learning que treinam o sistema para reconhecer caracteres da mesma forma que um humano faria, aprimorando a precisão na leitura de diferentes estilos de escrita, mesmo em formatos menos padronizados.

"Antes de prosseguirmos, é fundamental esclarecer o objetivo desta publicação. Vamos apresentar um exemplo prático de como desenvolver tanto o back-end quanto o front-end para integrar o Amazon Textract, com o foco específico em destacar informações importantes (highlights) em documentos PDF. Isso será feito utilizando o recurso de Layout do serviço, que permite identificar e manipular a estrutura visual dos documentos, como tabelas, parágrafos e outras áreas de interesse. Vale ressaltar que, neste conteúdo, não exploraremos outras funcionalidades do Textract, concentrando-se exclusivamente na extração de layout e destaques em PDFs.”

Como funciona a integração com o Textract:

A AWS possui diversos portais que contém documentações completas sobre o processo de integração com cada serviço, de acordo com sua linguagem. No nosso caso, iremos utilizar o Node.js, que é um software de código aberto, multiplataforma, baseado no interpretador V8 do Google e que permite a execução de códigos JavaScript fora de um navegador web.

No caso do Javascript, a AWS possui um hub grande de integrações, onde você pode realizar a integração com os serviços por meio de módulos. Pensando na integração com o Textract, você pode seguir a documentação e executar os seguintes comandos de instalação:

Image description

Nesta publicação, iremos utilizar o método "AnalyzeDocumentCommand” da API do Textract, cuja documentação deixo ao lado: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/textract/command/AnalyzeDocumentCommand/

Aplicação Back-End:

Para estruturarmos a aplicação back-end, iremos contar com a utilização do Serverless Framework como biblioteca e framework de projeto, pois essa solução irá nos apoiar nas configurações dos recursos da Infraestrutura e publicação das funções Lambda e API Gateway.

  • Começando pelo arquivo serverless.yml, temos:
# Nome da organização da conta to Serverless Framework org: publicacao # Nome da aplicação presente na organização app: documents-analyze # Nome do serviço pertencente à aplicação service: back-end provider: # Nome do provider de infraestrutura name: aws # Linguagem e versão aceita pelo Lambda runtime: nodejs20.x # Timeout default das funções Lambda timeout: 30 # Estrutura da role de IAM para permissionamento das funções Lambda iamRoleStatements: - Resource: "*" Effect: Allow Action: - s3:* - textract:* plugins: # Plugin (módulo NPM) para apoiar na execução em ambiente de desenvolvimento - serverless-offline # Estruturação das funções Lambda functions: # Nome único da função Lambda extractText: # Caminho de pastas que a função se encontra handler: src/extractText.handler # Eventos que serão o gatilho para triggar a função, no caso aqui é o API Gateway events: - httpApi: path: /{documentName} method: get 
Enter fullscreen mode Exit fullscreen mode
  • Seguindo para configuração do arquivo package.json (arquivo padrão para execução de aplicações Node.js):
{ "name": "back-end", "version": "1.0.0", "main": "src/extractText.mjs", "license": "ISC", "type": "module", "dependencies": { "@aws-sdk/client-s3": "^3.658.1", "@aws-sdk/client-textract": "^3.658.1", "@aws-sdk/s3-request-presigner": "^3.658.1" }, "devDependencies": { "serverless-offline": "^14.3.2" } } 
Enter fullscreen mode Exit fullscreen mode
  • Agora, no arquivo src/extractText.mjs, temos:

```import import {
TextractClient,
AnalyzeDocumentCommand,
} from "@aws-sdk/client-textract";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// Infrastructure Layer
const region = "us-east-1";
const textractClient = new TextractClient({ region });
const s3Client = new S3Client({ region });

const BUCKET_NAME = "";
const DOCUMENTS_FOLDER = "";
const SIGNED_URL_EXPIRATION = 3600;

// Service Layer: Handles Textract document analysis
const analyzeDocument = async (bucketName, documentPath) => {
const command = new AnalyzeDocumentCommand({
Document: {
S3Object: {
Bucket: bucketName,
Name: documentPath,
},
},
FeatureTypes: ["LAYOUT"],
});

const response = await textractClient.send(command);
return {
blocks: response.Blocks,
pages: response.DocumentMetadata?.Pages,
};
};

// Service Layer: Generates signed URL for S3 object
const generateSignedUrl = async (bucketName, documentPath) => {
const command = new GetObjectCommand({
Bucket: bucketName,
Key: documentPath,
});

return getSignedUrl(s3Client, command, { expiresIn: SIGNED_URL_EXPIRATION });
};

// Domain Layer: Main handler function
const handler = async (event) => {
try {
const { documentName } = event.pathParameters;
const documentParsedName = decodeURIComponent(documentName) + ".pdf";
const documentPath = ${DOCUMENTS_FOLDER}/${documentParsedName};

const documentData = await analyzeDocument(BUCKET_NAME, documentPath); const signedUrl = await generateSignedUrl(BUCKET_NAME, documentPath); return createSuccessResponse({ documentData, signedUrl }); 
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error("Error processing document:", error);
return createErrorResponse("Failed to process document.");
}
};

// Helper functions: Response formatting
const createSuccessResponse = (data) => ({
statusCode: 200,
body: JSON.stringify(data),
});

const createErrorResponse = (message) => ({
statusCode: 500,
body: JSON.stringify({ error: message }),
});

export { handler };

 Com isso, no back-end está devidamente estruturado e para você executá-lo localmente, siga os comandos abaixo: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al1kr2fth75x5i1jbsfa.png) A partir de então, você estará apto a fazer requisições na sua rota local a partir de qualquer navegador ou plataforma de API, como [Postman](https://www.postman.com/) ou [Apidog](https://apidog.com/). **Aplicação Front-End:** Para estruturação da aplicação Front-End, utilizamos o Tailwindcss + Vite + React TS, onde você pode encontrar o tutorial de inicialização do projeto na seguinte documentação: https://tailwindcss.com/docs/guides/vite Após o passo-a-passo acima executado, seu projeto precisará de algumas dependências para exibir os PDFs em tela, assim como os highlights nos textos. Pensando nisso, utilizaremos a biblioteca react-pdf para podermos fazer esse processo no Front-End. ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf7afqr4xwg7i6dda6ii.png) Lembre-se de instalar a biblioteca como super usuário, pois ela utiliza configurações de sistema para realizar a exibição do documento. Com isso feito, você precisará apenas criar um arquivo de componente e alterar o App.tsx, conforme orientações abaixo: - Começando pela criação do src/components/TextDetection.tsx, que será o responsável pela exibição do PDF e marcação das caixas de posição das extrações: ```import import React, { useEffect, useRef, useState } from "react"; import { Props } from "../@types/blocks"; import { pdfjs } from "react-pdf"; import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import "react-pdf/dist/esm/Page/TextLayer.css"; // Configuração do worker do PDF.js pdfjs.GlobalWorkerOptions.workerSrc = new URL( "pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url ).toString(); // Função para detectar cliques em caixas delimitadoras const handleBoxClick = ( e: MouseEvent, block: any, width: number, height: number, setModalText: (text: string) => void ) => { const canvas = e.target as HTMLCanvasElement; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const box = block.Geometry.BoundingBox; const left = width * box.Left; const top = height * box.Top; const boxWidth = width * box.Width; const boxHeight = height * box.Height; if (x >= left && x <= left + boxWidth && y >= top && y <= top + boxHeight) { setModalText(block.Text); } }; // Função para desenhar as caixas delimitadoras const drawBoundingBoxes = ( ctx: CanvasRenderingContext2D, width: number, height: number, canvas: HTMLCanvasElement, response: Props["response"], setModalText: (text: string) => void ) => { response.blocks.forEach((block) => { if (block.BlockType === "LINE") { const box = block.Geometry.BoundingBox; const left = width * box.Left; const top = height * box.Top; ctx.strokeStyle = "red"; ctx.lineWidth = 2; ctx.strokeRect(left, top, width * box.Width, height * box.Height); canvas.addEventListener("click", (e) => handleBoxClick(e, block, width, height, setModalText) ); } }); }; // Serviço para carregar o PDF e desenhar caixas delimitadoras const loadPdfAndDraw = async ( documentUrl: string, canvasRefs: React.MutableRefObject<HTMLCanvasElement[]>, response: Props["response"], setModalText: (text: string) => void ) => { try { const pdf = await pdfjs.getDocument(documentUrl).promise; for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) { const page = await pdf.getPage(pageNumber); const viewport = page.getViewport({ scale: 1 }); const canvas = canvasRefs.current[pageNumber - 1]; if (!canvas) continue; const ctx = canvas.getContext("2d"); if (!ctx) continue; canvas.width = viewport.width; canvas.height = viewport.height; await page.render({ canvasContext: ctx, viewport }).promise; drawBoundingBoxes( ctx, viewport.width, viewport.height, canvas, response, setModalText ); } } catch (error) { console.error("Erro ao carregar o PDF:", error); } }; // Componente principal const TextDetectionCanvas: React.FC<Props> = ({ response, documentUrl, qtdPages, }) => { const canvasRefs = useRef<HTMLCanvasElement[]>([]); const [modalText, setModalText] = useState<string | null>(null); const closeModal = () => setModalText(null); useEffect(() => { if (documentUrl && response) { loadPdfAndDraw(documentUrl, canvasRefs, response, setModalText); } }, [documentUrl, response]); return ( <div> {/* Renderizar os canvas */} {Array.from({ length: qtdPages }).map((_, index) => ( <canvas key={index} ref={(el) => (canvasRefs.current[index] = el!)} /> ))} {/* Modal de texto */} {modalText && <TextModal modalText={modalText} closeModal={closeModal} />} </div> ); }; // Componente para o modal de texto const TextModal: React.FC<{ modalText: string; closeModal: () => void }> = ({ modalText, closeModal, }) => ( <div style={{ position: "fixed", top: 0, left: 0, width: "100vw", height: "100vh", backgroundColor: "rgba(0, 0, 0, 0.5)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, }} > <div style={{ backgroundColor: "white", padding: "20px", borderRadius: "8px", boxShadow: "0 2px 10px rgba(0, 0, 0, 0.3)", }} > <div className="font-bold text-2xl flex gap-10"> <h1>Veja os Detalhes</h1> <button onClick={closeModal}>X</button> </div> <p className="mt-5">Texto: {modalText}</p> </div> </div> ); export default TextDetectionCanvas; 
Enter fullscreen mode Exit fullscreen mode
  • Agora finalizando com a alteração do App.tsx, que será o responsável por fazer a integração com o Back-End a partir de uma lista de documentos:

```import import React, { useEffect, useState } from "react";
import TextDetectionCanvas from "./components/TextDetection";
import { Response } from "./@types/blocks";

// Constants
const DOCUMENTS = [
"LISTA DE DOCUMENTOS"
];

// Service Layer: Handles document fetching
const fetchDocumentData = async (
documentName: string,
setDocumentData: (data: Response) => void,
setDocumentUrl: (url: string) => void,
setPageNumber: (page: number) => void
) => {
try {
const response = await fetch(http://localhost:3000/${documentName});
const data = await response.json();
setDocumentData(data.documentData);
setPageNumber(data.documentData.pages);
setDocumentUrl(data.signedUrl);
} catch (error) {
console.error("Failed to fetch document data", error);
}
};

// Domain Layer: Main Application Component
const App: React.FC = () => {
const [documentData, setDocumentData] = useState();
const [documentUrl, setDocumentUrl] = useState("");
const [pageNumber, setPageNumber] = useState(1);
const [selectedDocument, setSelectedDocument] = useState(
DOCUMENTS[0]
);

// Fetch document data whenever the selected document changes
useEffect(() => {
setPageNumber(0);
setDocumentUrl("");
fetchDocumentData(
selectedDocument,
setDocumentData,
setDocumentUrl,
setPageNumber
);
}, [selectedDocument]);

return (


documents={DOCUMENTS}
selectedDocument={selectedDocument}
onDocumentSelect={setSelectedDocument}
/>
{documentUrl === "" ? (

Carregando...


) : (
documentData={documentData}
documentUrl={documentUrl}
pageNumber={pageNumber}
/>
)}

);
};

// UI Layer: Document Selector Component
const DocumentSelector: React.FC<{
documents: string[];
selectedDocument: string;
onDocumentSelect: (doc: string) => void;
}> = ({ documents, selectedDocument, onDocumentSelect }) => (


value={selectedDocument}
onChange={(e) => onDocumentSelect(e.target.value)}
>
{documents.map((document, index) => (

{document}

))}


);

// UI Layer: Document Viewer Component
const DocumentViewer: React.FC<{
documentData: Response | undefined;
documentUrl: string;
pageNumber: number;
}> = ({ documentData, documentUrl, pageNumber }) => (


response={documentData as Response}
documentUrl={documentUrl}
qtdPages={pageNumber}
/>

);

export default App;

 Com as configurações realizadas, basta iniciar seu projeto por meio dos comandos abaixo: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rjgbynl3gs0trjzr0zq.png) **Resultado Final:** Após a execução de todos os passos acima, você irá ter uma aplicação funcional que estará analisando os documentos, extraindo os textos e informando as posições onde encontram-se cada marcação daquele texto no PDF. Exemplo do resultado final: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4efvwo0ageyidi2s1c3.png) ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a5k09uto60g27igemee.png) Pensando até mesmo em evoluções futuras, como trata-se de uma solução de OCR e sabemos que erros podem ocorrer, você pode contar com a utilização de um LLM e uma base de conhecimento para poder indexar esses textos, corrigi-los e gerar respostas inteligentes a partir de determinado assunto. Com isso, notamos que a aplicação *serverless* desenvolvida para extração de textos e exibição de layouts com Amazon Textract demonstrou uma solução eficaz para automatizar a análise de documentos em larga escala. Utilizando OCR avançado e ICR, a ferramenta não apenas extrai informações textuais, mas também identifica e organiza estruturas complexas, como tabelas e parágrafos, diretamente em PDFs. Com a integração entre o *back-end* (Node.js e Serverless Framework) e o *front-end* (React, Vite e TailwindCSS), a aplicação permite uma visualização intuitiva das marcações e dos textos extraídos. 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)