Merge branch 'RED-7669' into 'master'

RED-7669: optimize OCR-module performance

Closes RED-7669

See merge request redactmanager/ocr-service!24
This commit is contained in:
Kilian Schüttler 2023-11-28 12:35:22 +01:00
commit 65d818200f
5 changed files with 53 additions and 64 deletions

View File

@ -55,7 +55,7 @@ public class GhostScriptService {
Statistics stats) { Statistics stats) {
BlockingQueue<RenderedPageImageFile> imageFileCollectorQueue = new LinkedBlockingDeque<>(); BlockingQueue<RenderedPageImageFile> imageFileCollectorQueue = new LinkedBlockingDeque<>();
Thread asyncTransferThread = new BlockingQueueFiller(imageFileCollectorQueue, imageProcessingQueue); BlockingQueueFiller asyncTransferThread = new BlockingQueueFiller(imageFileCollectorQueue, imageProcessingQueue);
asyncTransferThread.start(); asyncTransferThread.start();
int numOfProcesses = Math.min(settings.getGsProcessCount(), stitchedPageNumbers.size()); int numOfProcesses = Math.min(settings.getGsProcessCount(), stitchedPageNumbers.size());
@ -86,7 +86,7 @@ public class GhostScriptService {
stats.increasePDF2ImgDuration(System.currentTimeMillis() - timestamp); stats.increasePDF2ImgDuration(System.currentTimeMillis() - timestamp);
log.info("Batch {}: Ghostscript processes finished with exit codes " + processExitCodes, batchIdx); log.info("Batch {}: Ghostscript processes finished with exit codes " + processExitCodes, batchIdx);
} }
asyncTransferThread.interrupt(); asyncTransferThread.setAllImagesQueued(true);
} }

View File

@ -94,8 +94,8 @@ public class OcrImageFactory {
if (!stitchedPageNumbers.isEmpty()) { if (!stitchedPageNumbers.isEmpty()) {
ghostScriptService.renderPagesAsImagesBatchedAndAddToQueue(stitchedPageNumbers, documentFile.toString(), tmpImageDir, document, imageProcessingQueue, stats); ghostScriptService.renderPagesAsImagesBatchedAndAddToQueue(stitchedPageNumbers, documentFile.toString(), tmpImageDir, document, imageProcessingQueue, stats);
} }
imageProcessingThread.interrupt();
log.info("All images extracted, interrupting processing thread."); imageProcessingThread.setAllImagesExtracted(true);
imageProcessingThread.join(); imageProcessingThread.join();

View File

@ -2,6 +2,7 @@ package com.knecon.fforesight.service.ocr.processor.service.threads;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import com.knecon.fforesight.service.ocr.processor.model.OcrImage; import com.knecon.fforesight.service.ocr.processor.model.OcrImage;
@ -10,6 +11,7 @@ import com.knecon.fforesight.service.ocr.processor.model.UnprocessedImage;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -21,41 +23,35 @@ This just moves the Elements from the GhostScriptOutputListener into the ImagePr
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class BlockingQueueFiller extends Thread { public class BlockingQueueFiller extends Thread {
BlockingQueue<RenderedPageImageFile> imageInputQueue; final BlockingQueue<RenderedPageImageFile> imageInputQueue;
BlockingQueue<UnprocessedImage> imageOutputQueue; final BlockingQueue<UnprocessedImage> imageOutputQueue;
@Setter
boolean allImagesQueued;
@SneakyThrows @SneakyThrows
@Override @Override
public void run() { public void run() {
// Interrupting signals that the image extraction has finished // Interrupting signals that the image extraction has finished
while (true) { while (!allImagesQueued) {
try {
final UnprocessedImage image = imageInputQueue.take(); final UnprocessedImage image = imageInputQueue.take();
try {
imageOutputQueue.put(image);
} catch (InterruptedException e) {
imageOutputQueue.put(image);
break;
}
} catch (InterruptedException e) {
break;
}
}
// empty the queue
List<UnprocessedImage> remainingImages = new ArrayList<>(imageInputQueue.size());
imageInputQueue.drainTo(remainingImages);
remainingImages.forEach(image -> {
try {
imageOutputQueue.put(image); imageOutputQueue.put(image);
} catch (InterruptedException e) { }
log.error(e.getMessage());
// empty the queue
try {
while (true) {
final UnprocessedImage image = imageInputQueue.remove();
imageOutputQueue.put(image);
} }
}); } catch (NoSuchElementException e) {
log.debug("No images left in queue, stopping.");
}
} }
} }

View File

@ -6,6 +6,7 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -24,6 +25,7 @@ import com.sun.jna.ptr.PointerByReference;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -37,48 +39,40 @@ import net.sourceforge.tess4j.TessAPI1;
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class ImageProcessingThread extends Thread { public class ImageProcessingThread extends Thread {
BlockingQueue<UnprocessedImage> imageInputQueue; final BlockingQueue<UnprocessedImage> imageInputQueue;
BlockingQueue<OcrImage> imageOutputQueue; final BlockingQueue<OcrImage> imageOutputQueue;
ITessAPI.TessBaseAPI detectionScriptHandle = initDetectionScriptHandle(); final ITessAPI.TessBaseAPI detectionScriptHandle = initDetectionScriptHandle();
Statistics stats; final Statistics stats;
OcrServiceSettings settings; final OcrServiceSettings settings;
PDDocument document; final PDDocument document;
@Setter
boolean allImagesExtracted;
@SneakyThrows @SneakyThrows
@Override @Override
public void run() { public void run() {
// Interrupting signals that the image extraction has finished while (!allImagesExtracted) {
while (true) { final UnprocessedImage image = imageInputQueue.take();
try { var ocrImage = this.process(image);
final UnprocessedImage image = imageInputQueue.take(); imageOutputQueue.put(ocrImage);
OcrImage extractedOcrImage = this.process(image);
try {
imageOutputQueue.put(extractedOcrImage);
} catch (InterruptedException e) {
imageOutputQueue.put(extractedOcrImage);
break;
}
} catch (InterruptedException e) {
break;
}
} }
// empty the queue
List<UnprocessedImage> remainingImages = new ArrayList<>(imageInputQueue.size());
imageInputQueue.drainTo(remainingImages); try {
remainingImages.forEach(image -> { while (true) {
OcrImage ocrImage = this.process(image); final UnprocessedImage image = imageInputQueue.remove();
try { OcrImage ocrImage = this.process(image);
imageOutputQueue.put(ocrImage); imageOutputQueue.put(ocrImage);
} catch (InterruptedException e) {
log.error(e.getMessage());
} }
}); } catch (NoSuchElementException e) {
log.debug("No images left in processing queue, stopping.");
}
TessAPI1.TessBaseAPIDelete(this.detectionScriptHandle); TessAPI1.TessBaseAPIDelete(this.detectionScriptHandle);
} }
@ -150,9 +144,7 @@ public class ImageProcessingThread extends Thread {
} }
public int detectOrientation(Pix pix, int dpi, ITessAPI.TessBaseAPI detectionScriptHandle) {
static public int detectOrientation(Pix pix, int dpi, ITessAPI.TessBaseAPI detectionScriptHandle) {
TessAPI1.TessBaseAPISetImage2(detectionScriptHandle, pix); TessAPI1.TessBaseAPISetImage2(detectionScriptHandle, pix);
TessAPI1.TessBaseAPISetSourceResolution(detectionScriptHandle, dpi); TessAPI1.TessBaseAPISetSourceResolution(detectionScriptHandle, dpi);
@ -173,7 +165,7 @@ public class ImageProcessingThread extends Thread {
orientationDegreeConfidenceBuffer, orientationDegreeConfidenceBuffer,
scriptureNameBuffer, scriptureNameBuffer,
scriptureConfidenceBuffer); scriptureConfidenceBuffer);
if (result == TRUE && orientationDegreeConfidenceBuffer.get() > 10) { if (result == TRUE && orientationDegreeConfidenceBuffer.get() > settings.getMinRotationConfidence()) {
orientationDegree = orientationDegreeResultBuffer.get(); orientationDegree = orientationDegreeResultBuffer.get();
} }
@ -198,7 +190,7 @@ public class ImageProcessingThread extends Thread {
ITessAPI.TessBaseAPI handle = TessAPI1.TessBaseAPICreate(); ITessAPI.TessBaseAPI handle = TessAPI1.TessBaseAPICreate();
String datapath = System.getenv("TESSDATA_PREFIX"); String datapath = System.getenv("TESSDATA_PREFIX");
TessAPI1.TessBaseAPIInit3(handle, datapath, "osd"); TessAPI1.TessBaseAPIInit3(handle, datapath, "osd");
TessAPI1.TessBaseAPISetVariable(handle, "debug_file", "/dev/null");
return handle; return handle;
} }

View File

@ -19,6 +19,7 @@ public class OcrServiceSettings {
int psmOverride = -1; // Overrides the page segmentation mode if > 0 int psmOverride = -1; // Overrides the page segmentation mode if > 0
int minImageHeight = 20; // Minimum height for images to be processed int minImageHeight = 20; // Minimum height for images to be processed
int minImageWidth = 20; // Minimum width for images to be processed int minImageWidth = 20; // Minimum width for images to be processed
float minRotationConfidence = 2; //
boolean debug; // If true, overlays OCR images with a grid and draws word bounding boxes boolean debug; // If true, overlays OCR images with a grid and draws word bounding boxes
boolean removeWatermark; // If true, watermarks will be removed boolean removeWatermark; // If true, watermarks will be removed
String languages = "deu+eng"; // Defines languages loaded into Tesseract as 3-char codes, additional languages must also be installed in the docker environment String languages = "deu+eng"; // Defines languages loaded into Tesseract as 3-char codes, additional languages must also be installed in the docker environment