diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/service/BlockificationService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/service/BlockificationService.java index 3be9f5f0..c9fee6bc 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/service/BlockificationService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/service/BlockificationService.java @@ -3,6 +3,7 @@ package com.iqser.red.service.redaction.v1.server.classification.service; import static java.util.stream.Collectors.toSet; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -15,6 +16,7 @@ import com.iqser.red.service.redaction.v1.server.classification.model.Page; import com.iqser.red.service.redaction.v1.server.classification.model.StringFrequencyCounter; import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock; import com.iqser.red.service.redaction.v1.server.classification.utils.RulingTextDirAdjustUtil; +import com.iqser.red.service.redaction.v1.server.classification.utils.TextBlockComparator; import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence; import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer; import com.iqser.red.service.redaction.v1.server.tableextraction.model.Ruling; @@ -38,7 +40,7 @@ public class BlockificationService { public Page blockify(List textPositions, List horizontalRulingLines, List verticalRulingLines) { List chunkWords = new ArrayList<>(); - List chunkBlockList1 = new ArrayList<>(); + List chunkBlockList1 = new ArrayList<>(); float minX = 1000, maxX = 0, minY = 1000, maxY = 0; TextPositionSequence prev = null; @@ -50,13 +52,14 @@ public class BlockificationService { boolean lineSeparation = word.getMinYDirAdj() - maxY > word.getHeight() * 1.25; boolean startFromTop = prev != null && word.getMinYDirAdj() < prev.getMinYDirAdj() - prev.getTextHeight(); boolean splitByX = prev != null && maxX + 50 < word.getMinXDirAdj() && prev.getMinYDirAdj() == word.getMinYDirAdj(); - boolean xIsBeforeFirstX = prev != null && word.getMinXDirAdj() < minX; + boolean xIsBeforeFirstX = prev != null && word.getMinXDirAdj() < minX - 10; boolean newLineAfterSplit = prev != null && word.getMinYDirAdj() != prev.getMinYDirAdj() && wasSplitted && splitX1 != word.getMinXDirAdj(); boolean isSplitByRuling = isSplitByRuling(minX, minY, maxX, maxY, word, horizontalRulingLines, verticalRulingLines); boolean splitByDir = prev != null && !prev.getDir().equals(word.getDir()); boolean splitByOtherFontAndOtherY = prev != null && prev.getMaxYDirAdj() != word.getMaxYDirAdj() && (word.getFontStyle().contains("bold") && !prev.getFontStyle().contains("bold") || prev.getFontStyle().contains("bold") && !word.getFontStyle().contains("bold")); + boolean isOtherFont = prev != null && prev.getMaxYDirAdj() != word.getMaxYDirAdj() && Math.abs(Math.floor(word.getFontSize()) - Math.floor(prev.getFontSize())) > 1; - if (prev != null && (lineSeparation || startFromTop || splitByDir || isSplitByRuling || splitByOtherFontAndOtherY)) { + if (prev != null && (isOtherFont || lineSeparation || startFromTop || splitByDir || isSplitByRuling || splitByOtherFontAndOtherY || splitByX || xIsBeforeFirstX)) { Orientation prevOrientation = null; if (!chunkBlockList1.isEmpty()) { @@ -108,53 +111,61 @@ public class BlockificationService { chunkBlockList1.add(cb1); } -// Iterator itty = chunkBlockList1.iterator(); -// -// TextBlock previousLeft = null; -// TextBlock previousRight = null; -// while (itty.hasNext()) { -// TextBlock block = (TextBlock) itty.next(); -// -// if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) { -// if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()) { -// previousLeft.add(block); -// itty.remove(); -// continue; -// } -// } -// -// if (previousRight != null && block.getOrientation().equals(Orientation.RIGHT)) { -// if (previousRight.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousRight.getMinY()) { -// previousRight.add(block); -// itty.remove(); -// continue; -// } -// } -// -// if (block.getOrientation().equals(Orientation.LEFT)) { -// previousLeft = block; -// } else if (block.getOrientation().equals(Orientation.RIGHT)) { -// previousRight = block; -// } -// } -// -// itty = chunkBlockList1.iterator(); -// TextBlock previous = null; -// while (itty.hasNext()) { -// TextBlock block = (TextBlock) itty.next(); -// -// if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(), -// previous.getMaxY()) || previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation() -// .equals(Orientation.RIGHT) && equalsWithThreshold(block.getMaxY(), previous.getMaxY())) { -// previous.add(block); -// itty.remove(); -// continue; -// } -// -// previous = block; -// } + Iterator itty = chunkBlockList1.iterator(); - return new Page(chunkBlockList1); + TextBlock previousLeft = null; + TextBlock previousRight = null; + while (itty.hasNext()) { + TextBlock block = (TextBlock) itty.next(); + + if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) { + if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()) { + previousLeft.add(block); + itty.remove(); + continue; + } + } + + if (previousRight != null && block.getOrientation().equals(Orientation.RIGHT)) { + if (previousRight.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousRight.getMinY()) { + previousRight.add(block); + itty.remove(); + continue; + } + } + + if (block.getOrientation().equals(Orientation.LEFT)) { + previousLeft = block; + } else if (block.getOrientation().equals(Orientation.RIGHT)) { + previousRight = block; + } + } + + itty = chunkBlockList1.iterator(); + TextBlock previous = null; + while (itty.hasNext()) { + TextBlock block = (TextBlock) itty.next(); + + if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(), + previous.getMaxY()) || previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation() + .equals(Orientation.RIGHT) && equalsWithThreshold(block.getMaxY(), previous.getMaxY())) { + previous.add(block); + itty.remove(); + continue; + } + + previous = block; + } + + //----- + +// Collections.sort(chunkBlockList1, new TextBlockComparator()); + + List pl = new ArrayList<>(); + + pl.addAll(chunkBlockList1); + + return new Page(pl); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextBlockComparator.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextBlockComparator.java new file mode 100644 index 00000000..3b1c8d54 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextBlockComparator.java @@ -0,0 +1,40 @@ +package com.iqser.red.service.redaction.v1.server.classification.utils; + +import java.util.Comparator; + +import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock; + +public class TextBlockComparator implements Comparator { + + @Override + public int compare(TextBlock pos1, TextBlock pos2) { + // only compare text that is in the same direction + int cmp1 = Float.compare(pos1.getDir().getDegrees(), pos2.getDir().getDegrees()); + if (cmp1 != 0) { + return cmp1; + } + + // get the text direction adjusted coordinates + float x1 = pos1.getMinX(); + float x2 = pos2.getMinX(); + + float pos1YBottom = pos1.getPdfMaxY(); + float pos2YBottom = pos2.getPdfMaxY(); + + // note that the coordinates have been adjusted so 0,0 is in upper left + float pos1YTop = pos1YBottom - pos1.getHeight(); + float pos2YTop = pos2YBottom - pos2.getHeight(); + + float yDifference = Math.abs(pos1YBottom - pos2YBottom); + + // we will do a simple tolerance comparison + if (yDifference < .1 || pos2YBottom >= pos1YTop && pos2YBottom <= pos1YBottom || pos1YBottom >= pos2YTop && pos1YBottom <= pos2YBottom) { + return Float.compare(x1, x2); + } else if (pos1YBottom < pos2YBottom) { + return -1; + } else { + return 1; + } + } + +} diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextPositionSequenceComparator.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextPositionSequenceComparator.java new file mode 100644 index 00000000..ea08fb97 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/classification/utils/TextPositionSequenceComparator.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.iqser.red.service.redaction.v1.server.classification.utils; + +import java.util.Comparator; +import java.util.regex.Matcher; + +import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence; +import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns; + +public class TextPositionSequenceComparator implements Comparator +{ + @Override + public int compare(TextPositionSequence pos1, TextPositionSequence pos2) + { + // only compare text that is in the same direction + int cmp1 = Float.compare(pos1.getDir().getDegrees(), pos2.getDir().getDegrees()); + if (cmp1 != 0) + { + return cmp1; + } + + // get the text direction adjusted coordinates + float x1 = pos1.getMinXDirAdj(); + float x2 = pos2.getMinXDirAdj(); + + float pos1YBottom = pos1.getMaxYDirAdj(); + float pos2YBottom = pos2.getMaxYDirAdj(); + + // note that the coordinates have been adjusted so 0,0 is in upper left + float pos1YTop = pos1YBottom - pos1.getHeight(); + float pos2YTop = pos2YBottom - pos2.getHeight(); + + float yDifference = Math.abs(pos1YBottom - pos2YBottom); + + // we will do a simple tolerance comparison + if (yDifference < .1 || + pos2YBottom >= pos1YTop && pos2YBottom <= pos1YBottom || + pos1YBottom >= pos2YTop && pos1YBottom <= pos2YBottom) + { + return Float.compare(x1, x2); + } + else if (pos1YBottom < pos2YBottom) + { + return -1; + } + else + { + return 1; + } + } +} diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/segmentation/PdfSegmentationService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/segmentation/PdfSegmentationService.java index 35672ddc..1a029680 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/segmentation/PdfSegmentationService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/segmentation/PdfSegmentationService.java @@ -9,10 +9,13 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; @@ -27,10 +30,12 @@ import com.iqser.red.service.redaction.v1.server.classification.model.Page; import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock; import com.iqser.red.service.redaction.v1.server.classification.service.BlockificationService; import com.iqser.red.service.redaction.v1.server.classification.service.ClassificationService; +import com.iqser.red.service.redaction.v1.server.classification.utils.TextPositionSequenceComparator; import com.iqser.red.service.redaction.v1.server.parsing.PDFLinesTextStripper; import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence; import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage; import com.iqser.red.service.redaction.v1.server.redaction.model.PdfTableCell; +import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns; import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings; import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer; import com.iqser.red.service.redaction.v1.server.tableextraction.model.CleanRulings; @@ -108,6 +113,7 @@ public class PdfSegmentationService { stripper.getRulings(), stripper.getMinCharWidth(), stripper.getMaxCharHeight()); +// Collections.sort(stripper.getTextPositionSequences(), new TextPositionSequenceComparator()); Page page = blockificationService.blockify(stripper.getTextPositionSequences(), cleanRulings.getHorizontal(), cleanRulings.getVertical()); page.setRotation(rotation); @@ -125,6 +131,28 @@ public class PdfSegmentationService { imageService.findOcr(page); } + +// var pattern = Patterns.getCompiledPattern("^\\d[\\d.]{0,5}", true); +// page.getTextBlocks().sort(Comparator.comparing(a -> a.getMinY(), Comparator.reverseOrder())); +// +// AbstractTextContainer prev = null; +// List toRemove = new ArrayList<>(); +// for (AbstractTextContainer current : page.getTextBlocks()) { +// +// if(prev != null && prev instanceof TextBlock && current instanceof TextBlock && Math.abs(prev.getMinY() - current.getMinY()) <=1){ +// Matcher matcher = pattern.matcher(prev.getText()); +// if (matcher.matches()) { +// ((TextBlock) current).getSequences().add(0, ((TextBlock) prev).getSequences().get(0)); +// toRemove.add(prev); +// current.setMinX(prev.getMinX()); +// } +// } +// prev = current; +// } +// +// page.getTextBlocks().removeAll(toRemove); + + pages.add(page); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/visualization/service/PdfVisualisationService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/visualization/service/PdfVisualisationService.java index a51ea73a..d2f12b30 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/visualization/service/PdfVisualisationService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/visualization/service/PdfVisualisationService.java @@ -48,7 +48,7 @@ public class PdfVisualisationService { } if (textBlock instanceof TextBlock) { textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size()); - visualizeTextBlock((TextBlock) textBlock, contentStream); + visualizeTextBlock((TextBlock) textBlock, contentStream, 0); } else if (textBlock instanceof Table) { textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size()); visualizeTable((Table) textBlock, contentStream); @@ -71,15 +71,17 @@ public class PdfVisualisationService { PDPage pdPage = document.getPage(page - 1); PDPageContentStream contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true); + int tbnumber = 0; for (AbstractTextContainer textBlock : analyzedPage.getTextBlocks()) { if (textBlock == null) { continue; } if (textBlock instanceof TextBlock) { - visualizeTextBlock((TextBlock) textBlock, contentStream); + visualizeTextBlock((TextBlock) textBlock, contentStream, tbnumber); } else if (textBlock instanceof Table) { visualizeTable((Table) textBlock, contentStream); } + tbnumber++; } contentStream.setStrokingColor(Color.YELLOW); @@ -95,7 +97,7 @@ public class PdfVisualisationService { } - private void visualizeTextBlock(TextBlock textBlock, PDPageContentStream contentStream) throws IOException { + private void visualizeTextBlock(TextBlock textBlock, PDPageContentStream contentStream, int number) throws IOException { contentStream.setStrokingColor(Color.RED); @@ -109,7 +111,7 @@ public class PdfVisualisationService { contentStream.setFont(PDType1Font.TIMES_ROMAN, 9f); contentStream.newLineAtOffset(textBlock.getPdfMinX(), textBlock.getPdfMaxY() + 2); - contentStream.showText(textBlock.getClassification() + textBlock.getOrientation() + "-->" + textBlock.getSequences().get(0).getDir()); + contentStream.showText(number + ": " +textBlock.getClassification() + textBlock.getOrientation() + "-->" + textBlock.getSequences().get(0).getDir()); contentStream.endText(); diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/HeadlinesGoldStandardIntegrationTest.java b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/HeadlinesGoldStandardIntegrationTest.java index 7dd2fd71..96c0acce 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/HeadlinesGoldStandardIntegrationTest.java +++ b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/HeadlinesGoldStandardIntegrationTest.java @@ -143,7 +143,7 @@ public class HeadlinesGoldStandardIntegrationTest { @Test public void extractHeadlines() throws IOException { - AnalyzeRequest request = prepareStorage("files/RSS/26 - Sedaxane - Acute Oral Toxicity - Rat.pdf"); + AnalyzeRequest request = prepareStorage("files/RSS/08 - Acute Oral Toxicity Up and Down Procedur.pdf"); analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(request.getDossierId(), request.getFileId())); analyzeService.analyze(request);