Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] Content in edit mode is not updated...
### Description

Hello!
When I add pdf documents with an existing ocr on redo mode, it does the ocr job well but it does not update the text content up to date in edit mode. The system therefore does not search in the right content to automatically add the metatags.

### Steps to reproduce

- Put paperless in redo mode
- Upload with bad ocr pdf
- Enter in file edit mode and check content (bad ocr, not newer)

### Webserver logs

```bash
[2022-11-17 22:48:45,191] [INFO] [paperless.consumer] Consuming 2014.12.09-Romande-Energie-Acompte.pdf
[2022-11-17 22:48:45,192] [DEBUG] [paperless.consumer] Detected mime type: application/pdf
[2022-11-17 22:48:45,194] [DEBUG] [paperless.consumer] Parser: RasterisedDocumentParser
[2022-11-17 22:48:45,200] [DEBUG] [paperless.consumer] Parsing 2014.12.09-Romande-Energie-Acompte.pdf...
[2022-11-17 22:48:45,523] [DEBUG] [paperless.parsing.tesseract] Extracted text from PDF file /usr/src/paperless/consume/2014.12.09-Romande-Energie-Acompte.pdf
[2022-11-17 22:48:45,654] [DEBUG] [paperless.parsing.tesseract] Calling OCRmyPDF with args: {'input_file': PosixPath('/usr/src/paperless/consume/2014.12.09-Romande-Energie-Acompte.pdf'), 'output_file': '/tmp/paperless/paperless-2het787x/archive.pdf', 'use_threads': True, 'jobs': '2', 'language': 'fra', 'output_type': 'pdfa', 'progress_bar': False, 'redo_ocr': True, 'clean': True, 'rotate_pages': True, 'rotate_pages_threshold': 12.0, 'sidecar': '/tmp/paperless/paperless-2het787x/sidecar.txt'}
[2022-11-17 22:49:10,447] [DEBUG] [paperless.parsing.tesseract] Using text from sidecar file
[2022-11-17 22:49:10,448] [DEBUG] [paperless.consumer] Generating thumbnail for 2014.09.26-Romande-Energie-Décompte.pdf...
[2022-11-17 22:49:10,455] [DEBUG] [paperless.parsing] Execute: convert -density 300 -scale 500x5000> -alpha remove -strip -auto-orient /tmp/paperless/paperless-po_9yhfq/archive.pdf[0] /tmp/paperless/paperless-po_9yhfq/convert.webp
[2022-11-17 22:49:13,040] [DEBUG] [paperless.consumer] Saving record to database
[2022-11-17 22:49:13,042] [DEBUG] [paperless.consumer] Creation date from st_mtime: 2016-06-13 10:32:16+02:00
[2022-11-17 22:49:13,069] [INFO] [paperless.handlers] Assigning document type Facture to 2016-06-13 2014.09.26-Romande-Energie-Décompte
[2022-11-17 22:49:13,130] [DEBUG] [paperless.consumer] Deleting file /usr/src/paperless/consume/2014.09.26-Romande-Energie-Décompte.pdf
[2022-11-17 22:49:13,145] [DEBUG] [paperless.parsing.tesseract] Deleting directory /tmp/paperless/paperless-po_9yhfq
[2022-11-17 22:49:13,146] [INFO] [paperless.consumer] Document 2016-06-13 2014.09.26-Romande-Energie-Décompte consumption finished
```


### Browser logs

_No response_

### Paperless-ngx version

1.9.2

### Host OS

Linux 7a74269e0c63 5.15.0-52-generic #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 x86_64 GNU/Linux

### Installation method

Docker - official image

### Browser

Edge, Firefox

### Configuration changes

_No response_

### Other

paperless-ng to ngx migration maybe... ?


Discussion:
Based on that log file, OCRmyPDF was given `'redo_ocr': True` and the text was extracted from the output of OCRmyPDF.  That generally seems correct to me.  Can you explain what you mean by "bad ocr"?
> Based on that log file, OCRmyPDF was given `'redo_ocr': True` and the text was extracted from the output of OCRmyPDF.  That generally seems correct to me.  Can you explain what you mean by "bad ocr"?

This is the original file that contains the bad OCR
![Screenshot_2022-11-18-12-18-07-401_org.mozilla.klar.jpg](https://user-images.githubusercontent.com/1897591/202693425-21d0be4e-c3d5-469b-bf3e-3874073b664c.jpg)

Text content in edit mode

![Screenshot_2022-11-18-12-18-33-832_org.mozilla.klar.jpg](https://user-images.githubusercontent.com/1897591/202693527-d3453c35-cf65-4983-90a9-dcf4d38214d9.jpg)

In view mode, i can copy True text. 
It could be just not picked up by OCR I suppose.  Or something in ocrmypdf where not all text is found or not all text is written to the file.  Are you able to provide a sample file?  It's very difficult to debug without one
Have same issue with PAPERLESS_OCR_MODE=redo

Only the text from that image inside the PDF file is written into the content field. the plain text is not transfered.

![image](https://user-images.githubusercontent.com/32903413/203060521-0417beac-7c2b-42ee-a9b4-536671969d6f.png)

Here is another sample:
![image](https://user-images.githubusercontent.com/32903413/203062061-dc02147f-b26e-428e-930d-75bfb84b8b0c.png)

[11-2021 Schnelltest Zentrum.pdf](https://github.com/paperless-ngx/paperless-ngx/files/10056722/11-2021.Schnelltest.Zentrum.pdf)




Resolving patch:
diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py
--- a/src/paperless_tesseract/parsers.py
+++ b/src/paperless_tesseract/parsers.py
@@ -95,7 +95,13 @@ def calculate_a4_dpi(self, image):
             return None
 
     def extract_text(self, sidecar_file, pdf_file):
-        if sidecar_file and os.path.isfile(sidecar_file):
+        # When re-doing OCR, the sidecar contains ONLY the new text, not
+        # the whole text, so do not utilize it in that case
+        if (
+            sidecar_file is not None
+            and os.path.isfile(sidecar_file)
+            and settings.OCR_MODE != "redo"
+        ):
             with open(sidecar_file) as f:
                 text = f.read()
 
@@ -142,7 +148,7 @@ def construct_ocrmypdf_parameters(
             "input_file": input_file,
             "output_file": output_file,
             # need to use threads, since this will be run in daemonized
-            # processes by django-q.
+            # processes via the task library.
             "use_threads": True,
             "jobs": settings.THREADS_PER_WORKER,
             "language": settings.OCR_LANGUAGE,
@@ -165,9 +171,11 @@ def construct_ocrmypdf_parameters(
             if settings.OCR_MODE == "redo":
                 ocrmypdf_args["clean"] = True
             else:
+                # --clean-final is not compatible with --redo-ocr
                 ocrmypdf_args["clean_final"] = True
 
-        if settings.OCR_DESKEW and not settings.OCR_MODE == "redo":
+        if settings.OCR_DESKEW and settings.OCR_MODE != "redo":
+            # --deskew is not compatible with --redo-ocr
             ocrmypdf_args["deskew"] = True
 
         if settings.OCR_ROTATE_PAGES:
@@ -263,7 +271,7 @@ def parse(self, document_path, mime_type, file_name=None):
 
         # Either no text was in the original or there should be an archive
         # file created, so OCR the file and create an archive with any
-        # test located via OCR
+        # text located via OCR
 
         import ocrmypdf
         from ocrmypdf import InputFileError, EncryptedPdfError


Test patch:
diff --git a/src/paperless_tesseract/tests/samples/single-page-mixed.pdf b/src/paperless_tesseract/tests/samples/single-page-mixed.pdf
new file mode 100644
Binary files /dev/null and b/src/paperless_tesseract/tests/samples/single-page-mixed.pdf differ
diff --git a/src/paperless_tesseract/tests/test_parser.py b/src/paperless_tesseract/tests/test_parser.py
--- a/src/paperless_tesseract/tests/test_parser.py
+++ b/src/paperless_tesseract/tests/test_parser.py
@@ -37,6 +37,9 @@ def __enter__(self):
 
 
 class TestParser(DirectoriesMixin, TestCase):
+
+    SAMPLE_FILES = os.path.join(os.path.dirname(__file__), "samples")
+
     def assertContainsStrings(self, content, strings):
         # Asserts that all strings appear in content, in the given order.
         indices = []
@@ -47,14 +50,18 @@ def assertContainsStrings(self, content, strings):
                 self.fail(f"'{s}' is not in '{content}'")
         self.assertListEqual(indices, sorted(indices))
 
-    text_cases = [
-        ("simple     string", "simple string"),
-        ("simple    newline\n   testing string", "simple newline\ntesting string"),
-        ("utf-8   строка с пробелами в конце  ", "utf-8 строка с пробелами в конце"),
-    ]
-
     def test_post_process_text(self):
-        for source, result in self.text_cases:
+
+        text_cases = [
+            ("simple     string", "simple string"),
+            ("simple    newline\n   testing string", "simple newline\ntesting string"),
+            (
+                "utf-8   строка с пробелами в конце  ",
+                "utf-8 строка с пробелами в конце",
+            ),
+        ]
+
+        for source, result in text_cases:
             actual_result = post_process_text(source)
             self.assertEqual(
                 result,
@@ -66,8 +73,6 @@ def test_post_process_text(self):
                 ),
             )
 
-    SAMPLE_FILES = os.path.join(os.path.dirname(__file__), "samples")
-
     def test_get_text_from_pdf(self):
         parser = RasterisedDocumentParser(uuid.uuid4())
         text = parser.extract_text(
@@ -461,6 +466,45 @@ def test_multi_page_mixed(self):
 
         self.assertIn("[OCR skipped on page(s) 4-6]", sidecar)
 
+    @override_settings(OCR_MODE="redo")
+    def test_single_page_mixed(self):
+        """
+        GIVEN:
+            - File with some text contained in images and some in text layer
+            - Text and images are mixed on the same page
+            - OCR mode set to redo
+        WHEN:
+            - Document is parsed
+        THEN:
+            - Text from images is extracted
+            - Full content of the file is parsed (not just the image text)
+            - An archive file is created with the OCRd text and the original text
+        """
+        parser = RasterisedDocumentParser(None)
+        parser.parse(
+            os.path.join(self.SAMPLE_FILES, "single-page-mixed.pdf"),
+            "application/pdf",
+        )
+        self.assertIsNotNone(parser.archive_path)
+        self.assertTrue(os.path.isfile(parser.archive_path))
+        self.assertContainsStrings(
+            parser.get_text().lower(),
+            [
+                "this is some normal text, present on page 1 of the document.",
+                "this is some text, but in an image, also on page 1.",
+                "this is further text on page 1.",
+            ],
+        )
+
+        with open(os.path.join(parser.tempdir, "sidecar.txt")) as f:
+            sidecar = f.read().lower()
+
+        self.assertIn("this is some text, but in an image, also on page 1.", sidecar)
+        self.assertNotIn(
+            "this is some normal text, present on page 1 of the document.",
+            sidecar,
+        )
+
     @override_settings(OCR_MODE="skip_noarchive")
     def test_multi_page_mixed_no_archive(self):
         """