Compare commits

...
Author SHA1 Message Date
Thierry Göckel 8e5ff31810 Fix annotating single letter entities 2020-09-29 15:10:52 +02:00
Dominique Eiflaender 8b1574845c Pull request #43: RED-264: Avoid phantom cells, by merging line till rounded value of biggest char height/width
Merge in RED/redaction-service from RED-264 to master

* commit 'e1adcfb02c4c7d46140884b3d38f8daf1e1aae92':
  Added missing unittest
  RED-264: Avoid phantom cells, by merging line till rounded value of biggest char height/width
2020-09-29 15:04:20 +02:00
deiflaender e1adcfb02c Added missing unittest 2020-09-29 14:54:35 +02:00
deiflaender 11164219c5 RED-264: Avoid phantom cells, by merging line till rounded value of biggest char height/width 2020-09-29 14:21:42 +02:00
Thierry Goeckel c9516205fe Pull request #42: RED-318
Merge in RED/redaction-service from RED-318 to master

* commit 'f5790bccab3fb8dce66aeb9fbe501655ae6b86c3':
  Log entries and manual redaction require one position per line
  Annotate line by line
  Refactor multiple positions to one
  Merge rectangles for single annotation/redaction log entry
2020-09-29 14:15:21 +02:00
Thierry Göckel f5790bccab Log entries and manual redaction require one position per line 2020-09-29 13:54:32 +02:00
Thierry Göckel 74e63ca292 Annotate line by line 2020-09-29 13:43:16 +02:00
Thierry Göckel 6638f0cf9e Refactor multiple positions to one 2020-09-28 17:47:55 +02:00
Thierry Göckel 5448153096 Merge rectangles for single annotation/redaction log entry 2020-09-28 17:31:40 +02:00
Dominique Eiflaender 03274ba1ab Pull request #41: RED-299: Redact complete Author(s) Field in Vertebrate study tables
Merge in RED/redaction-service from RED-299 to master

* commit '664b9b420605cb1adf1113b34c52a45e8024dbbc':
  RED-299: Redact complete Author(s) Field in Vertebrate study tables
2020-09-23 15:34:46 +02:00
deiflaender 664b9b4206 RED-299: Redact complete Author(s) Field in Vertebrate study tables 2020-09-23 15:20:52 +02:00
Thierry Goeckel 564d74e39d Pull request #40: Test modified rules 8 and 10
Merge in RED/redaction-service from RED-288 to master

* commit 'cfb7554c618b9482f529b8183bc0f1f95df7d00f':
  Test modified rules 8 and 9
2020-09-23 11:44:35 +02:00
Thierry Göckel cfb7554c61 Test modified rules 8 and 9 2020-09-22 21:36:05 +02:00
Christoph  Schabert ee40064b32 Pull request #39: RED-316: Switch to new red base image
Merge in RED/redaction-service from RED-316 to master

* commit 'f2fafc3db2ab8825eb4a2136e8eab28a12dbc2e6':
  Switch to rootless base-image
  Upgrade bamboo-specs to 7.1.2
2020-09-16 08:59:45 +02:00
Christoph  Schabert f2fafc3db2 Switch to rootless base-image 2020-09-15 16:02:08 +02:00
Christoph  Schabert d5b0638f11 Upgrade bamboo-specs to 7.1.2 2020-09-15 16:01:26 +02:00
Dominique Eiflaender 130cac0b77 Pull request #38: RED-293: Add unkown Textblocks to paragraphs
Merge in RED/redaction-service from RED-293 to master

* commit 'de310e8a654b601cb4bf258675904a25d4233783':
  RED-293: Add unkown Textblocks to paragraphs
2020-09-14 14:39:36 +02:00
deiflaender de310e8a65 RED-293: Add unkown Textblocks to paragraphs 2020-09-14 13:52:16 +02:00
Dominique Eiflaender a0a78440d8 Pull request #37: RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions
Merge in RED/redaction-service from RED-305 to master

* commit 'e52a54b0a3eb8491ed3e79b3f514364a5cf22f76':
  RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions
2020-09-11 11:18:42 +02:00
deiflaender e52a54b0a3 RED-305: Added ids to manual redaction entries, RED-306: set redacet to false in redactionLog for manual deletions 2020-09-11 11:10:10 +02:00
deiflaender 65a29fb3a5 Added new files and missing dictionary entries 2020-09-07 16:06:25 +02:00
32 changed files with 16072 additions and 119 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.atlassian.bamboo</groupId>
<artifactId>bamboo-specs-parent</artifactId>
<version>7.0.4</version>
<version>7.1.2</version>
<relativePath/>
</parent>
@@ -34,4 +34,4 @@
<!-- run 'mvn test' to perform offline validation of the plan -->
<!-- run 'mvn -Ppublish-specs' to upload the plan to your Bamboo server -->
</project>
</project>
@@ -1,4 +1,4 @@
FROM gin5/platform-base:5.2.0
FROM red/base-image:1.0.0
ARG PLATFORM_JAR
@@ -6,4 +6,4 @@ ENV PLATFORM_JAR ${PLATFORM_JAR}
ENV USES_ELASTICSEARCH false
COPY ["${PLATFORM_JAR}", "/"]
COPY ["${PLATFORM_JAR}", "/"]
@@ -84,7 +84,7 @@ public class ClassificationService {
}
else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document.getFontSizeCounter().getMostPopular() && textBlock.getMostPopularWordStyle().equals("italic") && !document.getFontStyleCounter().getMostPopular().equals("italic") && PositionUtils.getApproxLineCount(textBlock) < 2.9) {
textBlock.setClassification("TextBlock Italic");
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getSequences().size() > 3){
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock)){
textBlock.setClassification("TextBlock Unknown");
}
}
@@ -44,10 +44,10 @@ import lombok.extern.slf4j.Slf4j;
public class PDFLinesTextStripper extends PDFTextStripper {
@Getter
private float minCharWidth = Float.MAX_VALUE;
private int maxCharWidths;
@Getter
private float minCharHeight = Float.MAX_VALUE;
private int maxCharHeight;
@Getter
private final List<TextPositionSequence> textPositionSequences = new ArrayList<>();
@@ -201,8 +201,16 @@ public class PDFLinesTextStripper extends PDFTextStripper {
int startIndex = 0;
for (int i = 0; i <= textPositions.size() - 1; i++) {
minCharWidth = Math.min(minCharWidth, textPositions.get(i).getWidthDirAdj());
minCharHeight = Math.min(minCharHeight, textPositions.get(i).getHeightDir());
int charHeight = (int) textPositions.get(i).getHeightDir();
if(charHeight > maxCharHeight){
maxCharHeight = charHeight;
}
int charWidth = (int) textPositions.get(i).getWidthDirAdj();
if(charWidth > maxCharWidths){
maxCharWidths = charWidth;
}
if (i == 0 && textPositions.get(i).getUnicode().equals(" ")) {
startIndex++;
@@ -241,8 +249,8 @@ public class PDFLinesTextStripper extends PDFTextStripper {
@Override
public String getText(PDDocument doc) throws IOException {
minCharWidth = Float.MAX_VALUE;
minCharHeight = Float.MAX_VALUE;
maxCharWidths = 0;
maxCharWidths = 0;
textPositionSequences.clear();
rulings.clear();
graphicsPath.clear();
@@ -17,6 +17,6 @@ public class ParsedElements {
private boolean landscape;
private boolean rotated;
private float minCharWidth;
private float minCharHeight;
private float maxCharWidth;
private float maxCharHeight;
}
@@ -9,9 +9,7 @@ import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Data
@RequiredArgsConstructor
@@ -19,10 +17,6 @@ public class TextPositionSequence implements CharSequence {
private List<TextPosition> textPositions = new ArrayList<>();
@Getter
@Setter
private float[] annotationColor;
private final int page;
@@ -194,22 +188,18 @@ public class TextPositionSequence implements CharSequence {
public Rectangle getRectangle() {
float height = textPositions.get(0).getHeightDir() + 2;
float height = getTextHeight();
float posXInit;
float posXInit = getX1();
float posXEnd;
float posYInit;
float posYEnd;
if (textPositions.get(0).getRotation() == 90) {
posXEnd = textPositions.get(0).getYDirAdj() + 2;
posXInit = textPositions.get(0).getYDirAdj() - height;
posYInit = textPositions.get(0).getXDirAdj();
posYInit = getY1();
posYEnd = textPositions.get(textPositions.size() - 1).getXDirAdj() - height + 4;
} else {
posXInit = textPositions.get(0).getXDirAdj();
posXEnd = textPositions.get(textPositions.size() - 1)
.getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidth() + 1;
posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj() - 2;
@@ -220,4 +210,5 @@ public class TextPositionSequence implements CharSequence {
return new Rectangle(new Point(posXInit, posYInit), posXEnd - posXInit, posYEnd - posYInit + height, page);
}
}
@@ -1,6 +1,8 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@@ -13,4 +15,29 @@ public class CellValue {
int rowSpanStart;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
TextPositionSequence previous = null;
for (TextPositionSequence word : textBlock.getSequences()) {
if (previous != null) {
if (Math.abs(previous.getY1() - word.getY1()) > word.getTextHeight()) {
sb.append('\n');
} else {
sb.append(' ');
}
}
sb.append(word.toString());
previous = word;
}
return TextNormalizationUtilities.removeHyphenLineBreaks(sb.toString())
.replaceAll("\n", " ")
.replaceAll(" {2}", " ");
}
}
@@ -33,13 +33,14 @@ public class Section {
private Map<String, CellValue> tabularData;
public boolean rowEquals(String headerName, String value){
String cleanHeaderName = headerName.replaceAll("\n", "")
.replaceAll(" ", "")
.replaceAll("-", "");
public boolean rowEquals(String headerName, String value) {
return tabularData != null && tabularData.containsKey(cleanHeaderName)
&& tabularData.get(cleanHeaderName).getTextBlock().getText().equals(value);
String cleanHeaderName = headerName.replaceAll("\n", "").replaceAll(" ", "").replaceAll("-", "");
return tabularData != null && tabularData.containsKey(cleanHeaderName) && tabularData.get(cleanHeaderName)
.getTextBlock()
.getText()
.equals(value);
}
@@ -172,25 +173,46 @@ public class Section {
public void highlightCell(String cellHeader, int ruleNumber, String type) {
String cleanHeaderName = cellHeader.replaceAll("\n", "")
.replaceAll(" ", "")
.replaceAll("-", "");
annotateCell(cellHeader, ruleNumber, type, false, null);
}
public void redactCell(String cellHeader, int ruleNumber, String type, String reason) {
annotateCell(cellHeader, ruleNumber, type, true, reason);
}
public void redactNotCell(String cellHeader, int ruleNumber, String type, String reason) {
annotateCell(cellHeader, ruleNumber, type, false, reason);
}
private void annotateCell(String cellHeader, int ruleNumber, String type, boolean redact, String reason) {
String cleanHeaderName = cellHeader.replaceAll("\n", "").replaceAll(" ", "").replaceAll("-", "");
CellValue value = tabularData.get(cleanHeaderName);
if (value == null) {
log.warn("Could not find any data for {}.", cellHeader);
} else {
Entity entity = new Entity(value.getTextBlock()
.getText(), type, value.getRowSpanStart(), value.getRowSpanStart() + value.getTextBlock()
.getText()
Entity entity = new Entity(value.toString(), type, value.getRowSpanStart(), value.getRowSpanStart() + value.toString()
.length(), headline, sectionNumber);
entity.setRedaction(false);
entity.setRedaction(redact);
entity.setMatchedRule(ruleNumber);
entity.setRedactionReason(cellHeader);
entity.setTargetSequences(value.getTextBlock().getSequences()); // Make sure no other cells with same content are highlighted
entities.add(entity);
}
entity.setRedactionReason(reason);
entity.setTargetSequences(value.getTextBlock()
.getSequences()); // Make sure no other cells with same content are highlighted
// HashSet keeps the older value, but we want the new only.
if(entities.contains(entity)){
entities.remove(entity);
}
entities.add(entity);
entities = removeEntitiesContainedInLarger(entities);
}
}
}
@@ -27,9 +27,7 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class EntityRedactionService {
@@ -72,7 +70,7 @@ public class EntityRedactionService {
.replaceAll("-", "");
tabularData.put(headerName, new CellValue(cell.getTextBlocks().get(0), cellStart));
});
start = start + cell.getTextBlocks().get(0).toString().length();
start = start + cell.toString().length();
for (TextBlock textBlock : cell.getTextBlocks()) {
searchableRow.addAll(textBlock.getSequences());
}
@@ -21,7 +21,6 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractT
import com.iqser.red.service.redaction.v1.server.tableextraction.model.CleanRulings;
import com.iqser.red.service.redaction.v1.server.tableextraction.service.RulingCleaningService;
import com.iqser.red.service.redaction.v1.server.tableextraction.service.TableExtractionService;
import com.iqser.red.service.redaction.v1.server.tableextraction.utils.Utils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -37,6 +36,7 @@ public class PdfSegmentationService {
private final ClassificationService classificationService;
private final SectionsBuilderService sectionsBuilderService;
public Document parseDocument(PDDocument pdDocument) throws IOException {
Document document = new Document();
@@ -56,19 +56,21 @@ public class PdfSegmentationService {
int rotation = pdPage.getRotation();
boolean isRotated = rotation != 0 && rotation != 360;
ParsedElements parsedElements = ParsedElements
.builder()
ParsedElements parsedElements = ParsedElements.builder()
.rulings(stripper.getRulings())
.sequences(stripper.getTextPositionSequences())
.minCharWidth(Utils.round(stripper.getMinCharWidth(), 2))
.minCharHeight(Utils.round(stripper.getMinCharHeight(), 2))
.maxCharWidth(stripper.getMaxCharWidths())
.maxCharHeight(stripper.getMaxCharWidths())
.landscape(isLandscape)
.rotated(isRotated)
.build();
CleanRulings cleanRulings = rulingCleaningService.getCleanRulings(parsedElements.getRulings(), parsedElements.getMinCharWidth(), parsedElements.getMinCharHeight());
CleanRulings cleanRulings = rulingCleaningService.getCleanRulings(parsedElements.getRulings(), parsedElements
.getMaxCharWidth(), parsedElements.getMaxCharHeight());
Page page = blockificationService.blockify(parsedElements.getSequences(), cleanRulings.getHorizontal(), cleanRulings.getVertical());
Page page = blockificationService.blockify(parsedElements.getSequences(), cleanRulings.getHorizontal(), cleanRulings
.getVertical());
page.setRotation(rotation);
tableExtractionService.extractTables(cleanRulings, page);
@@ -91,7 +93,10 @@ public class PdfSegmentationService {
}
private void increaseDocumentStatistics(Page page, Document document) {
if (!page.isLandscape()) {
document.getFontSizeCounter().addAll(page.getFontSizeCounter().getCountPerValue());
}
@@ -100,6 +105,7 @@ public class PdfSegmentationService {
document.getFontStyleCounter().addAll(page.getFontStyleCounter().getCountPerValue());
}
private void buildPageStatistics(Page page) {
// Collect all statistics for the page, except from blocks inside tables, as tables will always be added to BodyTextFrame.
@@ -42,7 +42,9 @@ public class SectionsBuilderService {
if (prev != null && current.getClassification().startsWith("H ") || !document.isHeadlines()) {
Paragraph chunkBlock = buildTextBlock(chunkWords, lastHeadline);
chunkBlock.setHeadline(lastHeadline);
lastHeadline = current.getText();
if(document.isHeadlines()) {
lastHeadline = current.getText();
}
chunkBlockList.add(chunkBlock);
chunkWords = new ArrayList<>();
if (CollectionUtils.isNotEmpty(chunkBlock.getTables())) {
@@ -5,6 +5,8 @@ import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -20,10 +22,10 @@ public class Cell extends Rectangle {
private boolean isHeaderCell;
public Cell(Point2D topLeft, Point2D bottomRight) {
super((float) topLeft.getY(), (float) topLeft.getX(), (float) (bottomRight.getX() - topLeft.getX()),
(float) (bottomRight
super((float) topLeft.getY(), (float) topLeft.getX(), (float) (bottomRight.getX() - topLeft.getX()), (float) (bottomRight
.getY() - topLeft.getY()));
}
@@ -33,4 +35,34 @@ public class Cell extends Rectangle {
textBlocks.add(textBlock);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
TextPositionSequence previous = null;
for (TextBlock textBlock : textBlocks) {
for (TextPositionSequence word : textBlock.getSequences()) {
if (previous != null) {
if (Math.abs(previous.getY1() - word.getY1()) > word.getTextHeight()) {
sb.append('\n');
} else {
sb.append(' ');
}
}
sb.append(word.toString());
previous = word;
}
}
return TextNormalizationUtilities.removeHyphenLineBreaks(sb.toString())
.replaceAll("\n", " ")
.replaceAll(" {2}", " ");
}
}
@@ -18,9 +18,9 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.utils.Utils;
@Service
public class RulingCleaningService {
public CleanRulings getCleanRulings(List<Ruling> rulings, float minCharWidth, float minCharHeight){
public CleanRulings getCleanRulings(List<Ruling> rulings, float maxCharWidth, float maxCharHeight){
if (!rulings.isEmpty()) {
snapPoints(rulings, minCharWidth , minCharHeight);
snapPoints(rulings, maxCharWidth , maxCharHeight);
}
List<Ruling> vrs = new ArrayList<>();
@@ -2,7 +2,11 @@ package com.iqser.red.service.redaction.v1.server.visualization.service;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -14,6 +18,7 @@ import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.ManualRedactionEntry;
@@ -33,9 +38,7 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class AnnotationHighlightService {
@@ -43,7 +46,10 @@ public class AnnotationHighlightService {
private final DictionaryService dictionaryService;
public void highlight(PDDocument document, Document classifiedDoc, boolean flatRedaction, ManualRedactions manualRedactions) throws IOException {
public void highlight(PDDocument document, Document classifiedDoc, boolean flatRedaction,
ManualRedactions manualRedactions) throws IOException {
Set<Integer> manualRedactionPages = getManualRedactionPages(manualRedactions);
for (int page = 1; page <= document.getNumberOfPages(); page++) {
@@ -51,17 +57,36 @@ public class AnnotationHighlightService {
drawSectionFrames(document, classifiedDoc, flatRedaction, pdPage, page);
if (classifiedDoc.getEntities().get(page) == null) {
continue;
if (classifiedDoc.getEntities().get(page) != null) {
addAnnotations(pdPage, classifiedDoc, flatRedaction, manualRedactions, page);
}
addAnnotations(pdPage, classifiedDoc, flatRedaction, manualRedactions, page);
addManualAnnotations(pdPage, classifiedDoc, manualRedactions, page);
if (manualRedactionPages.contains(page)) {
addManualAnnotations(pdPage, classifiedDoc, manualRedactions, page);
}
}
}
private void addAnnotations(PDPage pdPage, Document classifiedDoc, boolean flatRedaction, ManualRedactions manualRedactions, int page) throws IOException {
private Set<Integer> getManualRedactionPages(ManualRedactions manualRedactions) {
Set<Integer> manualRedactionPages = new HashSet<>();
if (manualRedactions == null) {
return manualRedactionPages;
}
manualRedactions.getEntriesToAdd().forEach(entry -> {
entry.getPositions().forEach(pos -> {
manualRedactionPages.add(pos.getPage());
});
});
return manualRedactionPages;
}
private void addAnnotations(PDPage pdPage, Document classifiedDoc, boolean flatRedaction,
ManualRedactions manualRedactions, int page) throws IOException {
List<PDAnnotation> annotations = pdPage.getAnnotations();
@@ -77,16 +102,23 @@ public class AnnotationHighlightService {
if (manualRedactions != null && manualRedactions.getIdsToRemove()
.contains(entityPositionSequence.getId())) {
String manualOverrideReason = entity.getRedactionReason() + ", removed by manual override";
entity.setRedaction(false);
entity.setRedactionReason(entity.getRedactionReason() + ", removed by manual override");
entity.setRedactionReason(manualOverrideReason);
redactionLogEntry.setRedacted(false);
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
}
for (TextPositionSequence textPositions : entityPositionSequence.getSequences()) {
Rectangle rectangle = textPositions.getRectangle();
redactionLogEntry.getPositions().add(rectangle);
annotations.add(createAnnotation(rectangle, entityPositionSequence.getId(), createAnnotationContent(entity), getColor(entity), !flatRedaction && !isHint(entity)));
if (CollectionUtils.isNotEmpty(entityPositionSequence.getSequences())) {
for (Rectangle rectangle : getRectanglesPerLine(entityPositionSequence.getSequences()
.stream()
.flatMap(seq -> seq.getTextPositions().stream())
.collect(Collectors.toList()), page)) {
redactionLogEntry.getPositions().add(rectangle);
annotations.add(createAnnotation(rectangle, entityPositionSequence.getId(),
createAnnotationContent(entity), getColor(entity), !flatRedaction && !isHint(entity)));
}
}
redactionLogEntry.setId(entityPositionSequence.getId());
}
@@ -95,7 +127,33 @@ public class AnnotationHighlightService {
}
private void addManualAnnotations(PDPage pdPage, Document classifiedDoc, ManualRedactions manualRedactions, int page) throws IOException {
private List<Rectangle> getRectanglesPerLine(List<TextPosition> textPositions, int page) {
List<Rectangle> rectangles = new ArrayList<>();
if (textPositions.size() == 1) {
rectangles.add(new TextPositionSequence(textPositions, page).getRectangle());
} else {
float y = textPositions.get(0).getYDirAdj();
int startIndex = 0;
for (int i = 1; i < textPositions.size(); i++) {
float yDirAdj = textPositions.get(i).getYDirAdj();
if (yDirAdj != y) {
rectangles.add(new TextPositionSequence(textPositions.subList(startIndex, i), page).getRectangle());
y = yDirAdj;
startIndex = i;
}
}
if (startIndex != textPositions.size() - 1) {
rectangles.add(new TextPositionSequence(textPositions.subList(startIndex, textPositions.size()), page).getRectangle());
}
}
return rectangles;
}
private void addManualAnnotations(PDPage pdPage, Document classifiedDoc, ManualRedactions manualRedactions,
int page) throws IOException {
if (manualRedactions == null) {
return;
@@ -107,28 +165,34 @@ public class AnnotationHighlightService {
String id = IdBuilder.buildId(manualRedactionEntry);
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry);
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, id);
boolean foundOnPage = false;
for (Rectangle rectangle : manualRedactionEntry.getPositions()) {
if (page != rectangle.getPage()) {
continue;
}
PDAnnotationTextMarkup highlight = createAnnotation(rectangle, id, createAnnotationContent(manualRedactionEntry), getColor(manualRedactionEntry
.getType()), true);
foundOnPage = true;
PDAnnotationTextMarkup highlight = createAnnotation(rectangle, id,
createAnnotationContent(manualRedactionEntry), getColor(manualRedactionEntry.getType()), true);
annotations.add(highlight);
redactionLogEntry.getPositions().add(rectangle);
}
classifiedDoc.getRedactionLogEntities().add(redactionLogEntry);
if (foundOnPage) {
classifiedDoc.getRedactionLogEntities().add(redactionLogEntry);
}
}
}
private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry) {
private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry, String id) {
return RedactionLogEntry.builder()
.id(id)
.color(getColor(manualRedactionEntry.getType()))
.reason(manualRedactionEntry.getReason())
.value(manualRedactionEntry.getValue())
@@ -157,7 +221,8 @@ public class AnnotationHighlightService {
}
private PDAnnotationTextMarkup createAnnotation(Rectangle rectangle, String id, String content, float[] color, boolean popup) {
private PDAnnotationTextMarkup createAnnotation(Rectangle rectangle, String id, String content, float[] color,
boolean popup) {
PDAnnotationTextMarkup annotation = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);
annotation.constructAppearances();
@@ -175,25 +240,15 @@ public class AnnotationHighlightService {
private String createAnnotationContent(Entity entity) {
return new StringBuilder().append("\nRule ")
.append(entity.getMatchedRule())
.append(" matched")
.append("\n\n")
.append(entity.getRedactionReason())
.append("\n\nIn Section : \"")
.append(entity.getHeadline())
.append("\"")
.toString();
return "\nRule " + entity.getMatchedRule() + " matched" + "\n\n" + entity.getRedactionReason() + "\n\nIn " +
"Section : \"" + entity
.getHeadline() + "\"";
}
private String createAnnotationContent(ManualRedactionEntry entry) {
return new StringBuilder().append("\nManual Redaction")
.append("\n\nIn Section : \"")
.append(entry.getSection())
.append("\"")
.toString();
return "\nManual Redaction" + "\n\nIn Section : \"" + entry.getSection() + "\"";
}
@@ -213,7 +268,8 @@ public class AnnotationHighlightService {
// quadPoints is array of x,y coordinates in Z-like order (top-left, top-right, bottom-left,bottom-right)
// of the area to be highlighted
return new float[]{rectangle.getTopLeft().getX(), rectangle.getTopLeft().getY(), rectangle.getTopLeft()
.getX() + rectangle.getWidth(), rectangle.getTopLeft().getY(), rectangle.getTopLeft().getX(), rectangle.getTopLeft()
.getX() + rectangle.getWidth(), rectangle.getTopLeft().getY(), rectangle.getTopLeft().getX(),
rectangle.getTopLeft()
.getY() + rectangle.getHeight(), rectangle.getTopLeft()
.getX() + rectangle.getWidth(), rectangle.getTopLeft().getY() + rectangle.getHeight()};
}
@@ -224,10 +280,7 @@ public class AnnotationHighlightService {
if (!entity.isRedaction()) {
return false;
}
if (isHint(entity)) {
return false;
}
return true;
return !isHint(entity);
}
@@ -255,20 +308,19 @@ public class AnnotationHighlightService {
private boolean isHint(Entity entity) {
List<String> hintTypes = dictionaryService.getHintTypes();
if (CollectionUtils.isNotEmpty(hintTypes) && hintTypes.contains(entity.getType())) {
return true;
}
return false;
return CollectionUtils.isNotEmpty(hintTypes) && hintTypes.contains(entity.getType());
}
private void drawSectionFrames(PDDocument document, Document classifiedDoc, boolean flatRedaction, PDPage pdPage, int page) throws IOException {
private void drawSectionFrames(PDDocument document, Document classifiedDoc, boolean flatRedaction, PDPage pdPage,
int page) throws IOException {
if (flatRedaction) {
return;
}
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage, PDPageContentStream.AppendMode.APPEND, true);
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage,
PDPageContentStream.AppendMode.APPEND, true);
for (Paragraph paragraph : classifiedDoc.getParagraphs()) {
for (int i = 0; i <= paragraph.getPageBlocks().size() - 1; i++) {
@@ -317,7 +369,8 @@ public class AnnotationHighlightService {
if (cell != null) {
contentStream.setLineWidth(0.5f);
contentStream.setStrokingColor(Color.CYAN);
contentStream.addRect((float) cell.getX(), (float) cell.getY(), (float) cell.getWidth(), (float) cell
contentStream.addRect((float) cell.getX(), (float) cell.getY(), (float) cell.getWidth(),
(float) cell
.getHeight());
contentStream.stroke();
}
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
@@ -269,7 +270,7 @@ public class RedactionIntegrationTest {
System.out.println("redactionTest");
long start = System.currentTimeMillis();
ClassPathResource pdfFileResource = new ClassPathResource("files/Trinexapac/96 Trinexapac-ethyl_RAR_09_Volume_3CA_B-7_2018-02-23.pdf");
ClassPathResource pdfFileResource = new ClassPathResource("files/Fludioxonil/50 Fludioxonil_RAR_01_Volume_1_2018-02-21.pdf");
RedactionRequest request = RedactionRequest.builder()
.document(IOUtils.toByteArray(pdfFileResource.getInputStream()))
@@ -388,8 +389,7 @@ public class RedactionIntegrationTest {
public void htmlTablesTest() throws IOException {
System.out.println("htmlTablesTest");
ClassPathResource pdfFileResource = new ClassPathResource("files/Fludioxonil/51 " +
"Fludioxonil_RAR_02_Volume_2_2018-02-21.pdf");
ClassPathResource pdfFileResource = new ClassPathResource("files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06.pdf");
RedactionRequest request = RedactionRequest.builder()
.document(IOUtils.toByteArray(pdfFileResource.getInputStream()))
@@ -422,6 +422,27 @@ public class RedactionIntegrationTest {
}
@Test
public void phantomCellsDocumentTest() throws IOException {
ClassPathResource pdfFileResource = new ClassPathResource("files/Minimal Examples/Phantom Cells.pdf");
RedactionRequest request = RedactionRequest.builder()
.document(IOUtils.toByteArray(pdfFileResource.getInputStream()))
.build();
request.setFlatRedaction(false);
RedactionResult result = redactionController.redact(request);
result.getRedactionLog().getRedactionLogEntry().forEach(entry -> {
if(!entry.isHint()){
assertThat(entry.getReason()).isEqualTo("Not redacted because row is not a vertebrate study");
}
});
}
private static String loadFromClassPath(String path) {
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
@@ -275,6 +275,27 @@ public class EntityRedactionServiceTest {
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(8);
assertThat(classifiedDoc.getEntities().get(2).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(4);
}
pdfFileResource = new ClassPathResource("files/Minimal Examples/Header Propagation2.pdf");
dictionaryResponse = DictionaryResponse.builder()
.entries(Arrays.asList("Tribolet, R.", "Muir, G.", "Kühne-Thu, H.", "Close, C."))
.build();
when(dictionaryClient.getVersion()).thenReturn(DICTIONARY_VERSION.incrementAndGet());
when(dictionaryClient.getDictionaryForType(NAME_CODE)).thenReturn(dictionaryResponse);
addressResponse = DictionaryResponse.builder()
.entries(Collections.singletonList("Novartis Crop Protection AG, Basel, Switzerland"))
.build();
when(dictionaryClient.getDictionaryForType(ADDRESS_CODE)).thenReturn(addressResponse);
try (PDDocument pdDocument = PDDocument.load(pdfFileResource.getInputStream())) {
Document classifiedDoc = pdfSegmentationService.parseDocument(pdDocument);
entityRedactionService.processDocument(classifiedDoc, null);
assertThat(classifiedDoc.getEntities()).hasSize(1); // one page
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 9).count()).isEqualTo(3);
assertThat(classifiedDoc.getEntities().get(1).stream().filter(entity -> entity.getMatchedRule() == 8).count()).isEqualTo(8);
}
}
@@ -312,7 +333,7 @@ public class EntityRedactionServiceTest {
"global Section section\n" +
"rule \"8: Not redacted because Vertebrate Study = N\"\n" +
" when\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"N\"))\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"N\") || rowEquals(\"Vertebrate study Y/N\", \"No\"))\n" +
" then\n" +
" section.redactNot(\"name\", 8, \"Not redacted because row is not a vertebrate study\");\n" +
" section.redactNot(\"address\", 8, \"Not redacted because row is not a vertebrate study\");\n" +
@@ -320,7 +341,8 @@ public class EntityRedactionServiceTest {
" end\n" +
"rule \"9: Redact Authors and Addresses in Reference Table, if it is a Vertebrate study\"\n" +
" when\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"Y\"))\n" +
" Section(rowEquals(\"Vertebrate study Y/N\", \"Y\") || rowEquals(\"Vertebrate study Y/N\", " +
"\"Yes\"))\n" +
" then\n" +
" section.redact(\"name\", 9, \"Redacted because row is a vertebrate study\");\n" +
" section.redact(\"address\", 9, \"Redacted because row is a vertebrate study\");\n" +
@@ -227,6 +227,7 @@ CIBA-GEIGY Limited, Toxicology Services, Short-term Toxicology, 4332 Stein, Swit
CIBA-GEIGY Ltd. CH-4002 Basle, Switzerland
CIBA-GEIGY Ltd., Product Safety, Ecotoxicology, CH-4002 Basel, Switzerland
CIBA-GEIGY Ltd., Switzerland
CibaGeigy Ltd., CH-4002, Basel, Switzerland
CIP Chemisches Institut Pforzheim GmbH, Pforzheim, Germany
CL PHARMA AG
CL PHARMA AG, Linz, Austria
@@ -1563,4 +1564,36 @@ Zyma SA
Zyma SA, Nyon, Switzerland
“Bayer CropScience AG” Monheim
“Biologische Bundesanstalt (BBA)”, Berlin-Dahlem
“W. Neudorff GmbH KG”, An der Mühle 3, D- 31860 Emmertal
“W. Neudorff GmbH KG”, An der Mühle 3, D- 31860 Emmertal
Syngenta Ltd Jealott’s Hill Int. Research Centre Bracknell, Berkshire, RG42 6EY, United Kingdom
MITOX Consultants Science Park 408, 1098XH Amsterdam, NL
Eurofins Agrosciences Services Eco-Chem GmbH, Eutinger Str. 24, 75233 Niefern-Öschelbronn, Germany
Fraunhofer Institute for Molecular Biology and Applied Ecology (IME), 57392 Schmallenberg, Germany
Syngenta Crop Protection AGRCC Cytotest Cell Research GmbH, Germany
BASF SE, Ludwigshafen, Germany
AGRCC Ltd., Switzerland
AGWIL Research Lab. Inc., USA
Charles River Laboratories, UK
Ciba-Geigy Corporation Environmental Health Centre, Farmington
Syngenta Crop Protection, Inc., Greensboro, United States
Experimental Toxicology and Ecology, BASF SE
Wildlife International, Ltd. 8598 Commerce Drive Easton, MD 21601 USA
Küberich, Wiesentheid / Geesdorf, Germany
CiToxLAB Hungary Ltd. H8200 Veszprém, Szabadságpuszta, Hungary
Forellenzucht Trostadt GbR, Trostadt, Germany
Osage Catfisheries Inc., Osage Beach, MO, USA
Fischzucht Rhönforelle, Gersfeld, Germany
Aquatic Biosystems, Ft. Collins, Colorado, USA
West Aquarium GmbH, 37431 Bad Lauterberg, Germany
Aquatic BioSystems, Fort Collins, Colorado, USA
“U.S. Environmental Research Laboratory”, Duluth, Minnesota, USA
Continuous laboratory cultures
Wildlife International, Ltd. 8598 Commerce Drive Easton, MD 21601 USA
Osage Catfisheries, Inc., Osage Beach, MO, USA
Osage catfisheries, Osage Beach (MO), USA
Ciba-Geigy Corporation, Environmental Health Centre, Farmington
Novartis Crop Protection Inc., Greensboro, United States
Syngenta, Jealott’s Hill International Research Station, UK
Hillandale Farms, Kenansville, Florida, USA
Jealott’s Hill International Research Station, UK
L & C Dairy, Avon Park, Florida, UK
@@ -5290,6 +5290,7 @@ Naeb O.
Nagai K.
Nagashima Yoshikazu
Nagata H.|Tanaka T.
Nagra, B
Nagra B.S
Nagra B.|Kingdom N.
Nagy K
@@ -5640,6 +5641,7 @@ Pedersen Carol
Pedersen Carol|DuCharme Darlene
Pedersen C|Ducharme D.
Pederson C.A.
Pederson
Peffer
Peffer R
Peffer R.
@@ -6249,6 +6251,7 @@ Roth M.
Roth Markus
Roth RN|Heilman RD
Roth, M.
Roth
Rotroff
Rouaud, J. L.
Roussel C
@@ -6848,7 +6851,7 @@ Sokolowshi Andrea
Sokolowski A
Sokolowski A.
Sokolowski Andrea
Sokolowski,
Sokolowski
Sole
Sole C
Sole, C.
@@ -7248,6 +7251,7 @@ Thanei, P.
Tharp BE
Thede B.
Theophilidis G
Thevenaz
Thevenaz P
Thevenaz P.
Thevenaz Ph.
@@ -8193,4 +8197,113 @@ Zoriki Hosomi R.
Zoriki Hosomi Rosana
Zuberer D
Zubrod J
Zwicker R.E.
Zwicker R.E.
Ott
Argus et al
Mihail
Clapp
Murfitt R
Yang
Swarbrick
Hertl
Breitwieser
Seville, A
Trumper, C.
Gasso-Brown, D.
Váliczkó, É
Váliczkó, É.
Durando, J.
Tarcai, Z.
Pooles, A
Roig, J.
Schäfers, C.
Nixon, W.
Zok, S.
Durjava M.K. et al
Jatzek J
Salinas E
Sousa J. V
Caferella, M.A.
Salina, E.
Walraven J
Kaspers et al
Schneider et al
Lowrie C
Macdougall J
Roberts S
Madrid, S.O.
Maynard, M.S.
Ray, W.
Ray, W.J.
Váliczkó É
Tarcai Z
Orovecz B
Kaspers
Tarcai Z.
Gizler, A
Whitlow
Johnson et al
Bryden et al
Váliczkó
Höger
Dorgerloh
Schäfers C
Caferella M.A
Salinas, E
Zok S
Tummon OJ
Eckstein CL
Francis PD
Ott M
Richeux F
Rajsekhar PV
Webbley
O’Hagan
MacDonald
Jewkes
Harris
Tomlinson
Tomlinson et al
Nagy
Török-Bathó
Hargitai
Shearer
Robertson
Strepka
Hackford
Blunt
Sieber
Elcombe
Haines
Kang
Sayers
Harris J
Baxter A
Macdonald M
PetusArpasy M
TorokBatho M.
van Ravenzwaa y B
Cords S
Dymarkows ka K.
Bahig M
Pekari K
Bercz J
Russell L
Kitchen K
Miyagawa M.
Sasaki Y
Yin D
Huff J
Blackburn K
Carlson G
Pereira M
Stoner G
Zelenak V
Valiczko E
Tomlinso n J
Trumper C
Senciuc M
Vance C.
Przeorska J.
Melville S.
Wicksted G.
@@ -51,7 +51,7 @@ rule "5: Do not redact in guideline sections"
rule "6: Redact contact information if applicant is found"
when
eval(section.headlineContainsWord("applicant") || section.getText().contains("Applicant"));
eval(section.headlineContainsWord("applicant") || section.getText().contains("Applicant") || section.headlineContainsWord("Primary contact") || section.headlineContainsWord("Alternative contact"));
then
section.redactLineAfter("Name:", "address", 6, "Applicant information was found");
section.redactBetween("Address:", "Contact", "address", 6, "Applicant information was found");
@@ -62,14 +62,20 @@ rule "6: Redact contact information if applicant is found"
section.redactLineAfter("Tel:", "address", 6, "Applicant information was found");
section.redactLineAfter("E-mail:", "address", 6, "Applicant information was found");
section.redactLineAfter("Email:", "address", 6, "Applicant information was found");
section.redactLineAfter("e-mail:", "address", 6, "Applicant information was found");
section.redactLineAfter("E-mail address:", "address", 6, "Applicant information was found");
section.redactLineAfter("Contact:", "address", 6, "Applicant information was found");
section.redactLineAfter("Alternative contact:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone number:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone No:", "address", 6, "Applicant information was found");
section.redactLineAfter("Fax number:", "address", 6, "Applicant information was found");
section.redactLineAfter("Telephone:", "address", 6, "Applicant information was found");
section.redactLineAfter("Company:", "address", 6, "Applicant information was found");
section.redactBetween("No:", "Fax", "address", 6, "Applicant information was found");
section.redactBetween("Contact:", "Tel.:", "address", 6, "Applicant information was found");
end
rule "7: Redact contact information if Producer is found"
when
eval(section.getText().toLowerCase().contains("producer of the plant protection") || section.getText().toLowerCase().contains("producer of the active substance") || section.getText().contains("Manufacturer of the active substance") || section.getText().contains("Manufacturer:") || section.getText().contains("Producer or producers of the active substance"));
@@ -93,9 +99,9 @@ rule "7: Redact contact information if Producer is found"
rule "8: Not redacted because Vertebrate Study = N"
when
Section(rowEquals("Vertebrate study Y/N", "N"))
Section(rowEquals("Vertebrate study Y/N", "N") || rowEquals("Vertebrate study Y/N", "No"))
then
section.redactNot("name", 8, "Not redacted because row is not a vertebrate study");
section.redactNotCell("Author(s)", 8, "name", "Not redacted because row is not a vertebrate study");
section.redactNot("address", 8, "Not redacted because row is not a vertebrate study");
section.highlightCell("Vertebrate study Y/N", 8, "hint_only");
end
@@ -112,9 +118,9 @@ rule "9: Redact if must redact entry is found"
rule "10: Redact Authors and Addresses in Reference Table if it is a Vertebrate study"
when
Section(rowEquals("Vertebrate study Y/N", "Y"))
Section(rowEquals("Vertebrate study Y/N", "Y") || rowEquals("Vertebrate study Y/N", "Yes"))
then
section.redact("name", 10, "Redacted because row is a vertebrate study");
section.redactCell("Author(s)", 10, "name", "Redacted because row is a vertebrate study");
section.redact("address", 10, "Redacted because row is a vertebrate study");
section.highlightCell("Vertebrate study Y/N", 10, "must_redact");
end
File diff suppressed because one or more lines are too long