using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Tesseract.Internal; using Tesseract.Interop; namespace Tesseract { public sealed class Page : DisposableBase { private static readonly TraceSource trace = new TraceSource("Tesseract"); private bool runRecognitionPhase; private Rect regionOfInterest; public TesseractEngine Engine { get; private set; } /// /// Gets the that is being ocr'd. /// public Pix Image { get; private set; } /// /// Gets the name of the image being ocr'd. /// /// /// This is also used for some of the more advanced functionality such as identifying the associated UZN file if present. /// public string ImageName { get; private set; } /// /// Gets the page segmentation mode used to OCR the specified image. /// public PageSegMode PageSegmentMode { get; private set; } internal Page(TesseractEngine engine, Pix image, string imageName, Rect regionOfInterest, PageSegMode pageSegmentMode) { Engine = engine; Image = image; ImageName = imageName; RegionOfInterest = regionOfInterest; PageSegmentMode = pageSegmentMode; } /// /// The current region of interest being parsed. /// public Rect RegionOfInterest { get { return regionOfInterest; } set { if (value.X1 < 0 || value.Y1 < 0 || value.X2 > Image.Width || value.Y2 > Image.Height) throw new ArgumentException("The region of interest to be processed must be within the image bounds.", "value"); if (regionOfInterest != value) { regionOfInterest = value; // update region of interest in image Interop.TessApi.Native.BaseApiSetRectangle(Engine.Handle, regionOfInterest.X1, regionOfInterest.Y1, regionOfInterest.Width, regionOfInterest.Height); // request rerun of recognition on the next call that requires recognition runRecognitionPhase = false; } } } /// /// Gets the thresholded image that was OCR'd. /// /// public Pix GetThresholdedImage() { Recognize(); var pixHandle = Interop.TessApi.Native.BaseAPIGetThresholdedImage(Engine.Handle); if (pixHandle == IntPtr.Zero) { throw new TesseractException("Failed to get thresholded image."); } return Pix.Create(pixHandle); } /// /// Creates a object that is used to iterate over the page's layout as defined by the current . /// /// public PageIterator AnalyseLayout() { Guard.Verify(PageSegmentMode != PageSegMode.OsdOnly, "Cannot analyse image layout when using OSD only page segmentation, please use DetectBestOrientation instead."); var resultIteratorHandle = Interop.TessApi.Native.BaseAPIAnalyseLayout(Engine.Handle); return new PageIterator(this, resultIteratorHandle); } /// /// Creates a object that is used to iterate over the page as defined by the current . /// /// public ResultIterator GetIterator() { Recognize(); var resultIteratorHandle = Interop.TessApi.Native.BaseApiGetIterator(Engine.Handle); return new ResultIterator(this, resultIteratorHandle); } /// /// Gets the page's content as plain text. /// /// public string GetText() { Recognize(); return Interop.TessApi.BaseAPIGetUTF8Text(Engine.Handle); } /// /// Gets the page's content as an HOCR text. /// /// The page number (zero based). /// True to use XHTML Output, False to HTML Output /// The OCR'd output as an HOCR text string. public string GetHOCRText(int pageNum, bool useXHtml = false) { //Why Not Use 'nameof(pageNum)' instead of '"pageNum"' Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); if(useXHtml) return Interop.TessApi.BaseAPIGetHOCRText2(Engine.Handle, pageNum); else return Interop.TessApi.BaseAPIGetHOCRText(Engine.Handle, pageNum); } /// /// Gets the page's content as an Alto text. /// /// The page number (zero based). /// The OCR'd output as an Alto text string. public string GetAltoText(int pageNum) { Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); return Interop.TessApi.BaseAPIGetAltoText(Engine.Handle, pageNum); } /// /// Gets the page's content as a Tsv text. /// /// The page number (zero based). /// The OCR'd output as a Tsv text string. public string GetTsvText(int pageNum) { Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); return Interop.TessApi.BaseAPIGetTsvText(Engine.Handle, pageNum); } /// /// Gets the page's content as a Box text. /// /// The page number (zero based). /// The OCR'd output as a Box text string. public string GetBoxText(int pageNum) { Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); return Interop.TessApi.BaseAPIGetBoxText(Engine.Handle, pageNum); } /// /// Gets the page's content as a LSTMBox text. /// /// The page number (zero based). /// The OCR'd output as a LSTMBox text string. public string GetLSTMBoxText(int pageNum) { Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); return Interop.TessApi.BaseAPIGetLSTMBoxText(Engine.Handle, pageNum); } /// /// Gets the page's content as a WordStrBox text. /// /// The page number (zero based). /// The OCR'd output as a WordStrBox text string. public string GetWordStrBoxText(int pageNum) { Guard.Require("pageNum", pageNum >= 0, "Page number must be greater than or equal to zero (0)."); Recognize(); return Interop.TessApi.BaseAPIGetWordStrBoxText(Engine.Handle, pageNum); } /// /// Gets the page's content as an UNLV text. /// /// The page number (zero based). /// The OCR'd output as an UNLV text string. public string GetUNLVText() { Recognize(); return Interop.TessApi.BaseAPIGetUNLVText(Engine.Handle); } /// /// Get's the mean confidence that as a percentage of the recognized text. /// /// public float GetMeanConfidence() { Recognize(); return Interop.TessApi.Native.BaseAPIMeanTextConf(Engine.Handle) / 100.0f; } /// /// Get segmented regions at specified page iterator level. /// /// PageIteratorLevel enum /// public List GetSegmentedRegions(PageIteratorLevel pageIteratorLevel) { var boxArray = Interop.TessApi.Native.BaseAPIGetComponentImages(Engine.Handle, pageIteratorLevel, Interop.Constants.TRUE, IntPtr.Zero, IntPtr.Zero); int boxCount = Interop.LeptonicaApi.Native.boxaGetCount(new HandleRef(this, boxArray)); List boxList = new List(); for (int i = 0; i < boxCount; i++) { var box = Interop.LeptonicaApi.Native.boxaGetBox(new HandleRef(this, boxArray), i, PixArrayAccessType.Clone); if (box == IntPtr.Zero) { continue; } int px, py, pw, ph; Interop.LeptonicaApi.Native.boxGetGeometry(new HandleRef(this, box), out px, out py, out pw, out ph); boxList.Add(new Rectangle(px, py, pw, ph)); Interop.LeptonicaApi.Native.boxDestroy(ref box); } Interop.LeptonicaApi.Native.boxaDestroy(ref boxArray); return boxList; } /// /// Detects the page orientation, with corresponding confidence when using . /// /// /// If using full page segmentation mode (i.e. AutoOsd) then consider using instead as this also provides a /// deskew angle which isn't available when just performing orientation detection. /// /// The page orientation. /// The confidence level of the orientation (15 is reasonably confident). /// [Obsolete("Use DetectBestOrientation(int orientationDegrees, float confidence) that returns orientation in degrees instead.")] public void DetectBestOrientation(out Orientation orientation, out float confidence) { int orientationDegrees; float orientationConfidence; DetectBestOrientation(out orientationDegrees, out orientationConfidence); // convert angle to 0-360 (shouldn't be required but do it just o be safe). orientationDegrees = orientationDegrees % 360; if (orientationDegrees < 0) { orientationDegrees += 360; } if (orientationDegrees > 315 || orientationDegrees <= 45) { orientation = Orientation.PageUp; } else if (orientationDegrees > 45 && orientationDegrees <= 135) { orientation = Orientation.PageRight; } else if (orientationDegrees > 135 && orientationDegrees <= 225) { orientation = Orientation.PageDown; } else { orientation = Orientation.PageLeft; } confidence = orientationConfidence; } /// /// Detects the page orientation, with corresponding confidence when using . /// /// /// If using full page segmentation mode (i.e. AutoOsd) then consider using instead as this also provides a /// deskew angle which isn't available when just performing orientation detection. /// /// The detected clockwise page rotation in degrees (0, 90, 180, or 270). /// The confidence level of the orientation (15 is reasonably confident). public void DetectBestOrientation(out int orientation, out float confidence) { string scriptName; float scriptConfidence; DetectBestOrientationAndScript(out orientation, out confidence, out scriptName, out scriptConfidence); } /// /// Detects the page orientation, with corresponding confidence when using . /// /// /// If using full page segmentation mode (i.e. AutoOsd) then consider using instead as this also provides a /// deskew angle which isn't available when just performing orientation detection. /// /// The detected clockwise page rotation in degrees (0, 90, 180, or 270). /// The confidence level of the orientation (15 is reasonably confident). /// The name of the script (e.g. Latin) /// The confidence level in the script public void DetectBestOrientationAndScript(out int orientation, out float confidence, out string scriptName, out float scriptConfidence) { int orient_deg; float orient_conf; IntPtr script_nameHandle; float script_conf; if (Interop.TessApi.Native.TessBaseAPIDetectOrientationScript(Engine.Handle, out orient_deg, out orient_conf, out script_nameHandle, out script_conf) != 0) { orientation = orient_deg; confidence = orient_conf; if (script_nameHandle != IntPtr.Zero) { scriptName = MarshalHelper.PtrToString(script_nameHandle, Encoding.ASCII); // Don't delete script_nameHandle as it points to internal memory managed by Tesseract. } else { scriptName = null; } scriptConfidence = script_conf; } else { throw new TesseractException("Failed to detect image orientation."); } } internal void Recognize() { Guard.Verify(PageSegmentMode != PageSegMode.OsdOnly, "Cannot OCR image when using OSD only page segmentation, please use DetectBestOrientation instead."); if (!runRecognitionPhase) { if (Interop.TessApi.Native.BaseApiRecognize(Engine.Handle, new HandleRef(this, IntPtr.Zero)) != 0) { throw new InvalidOperationException("Recognition of image failed."); } runRecognitionPhase = true; // now write out the thresholded image if required to do so bool tesseditWriteImages; if (Engine.TryGetBoolVariable("tessedit_write_images", out tesseditWriteImages) && tesseditWriteImages) { using (Pix thresholdedImage = GetThresholdedImage()) { string filePath = Path.Combine(Environment.CurrentDirectory, "tessinput.tif"); try { thresholdedImage.Save(filePath, ImageFormat.TiffG4); trace.TraceEvent(TraceEventType.Information, 2, "Successfully saved the thresholded image to '{0}'", filePath); } catch (Exception error) { trace.TraceEvent(TraceEventType.Error, 2, "Failed to save the thresholded image to '{0}'.\nError: {1}", filePath, error.Message); } } } } } protected override void Dispose(bool disposing) { if (disposing) { Interop.TessApi.Native.BaseAPIClear(Engine.Handle); } } } }