Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/main/java/com/mindee/image/ExtractedImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ExtractedImage {
private final String filename;
private final String saveFormat;
private final int pageId;
private final int elementId;

/**
* Default constructor.
Expand All @@ -27,11 +28,18 @@ public class ExtractedImage {
* @param filename Name of the extracted image.
* @param saveFormat Format to save the image as, defaults to PNG.
*/
public ExtractedImage(BufferedImage image, String filename, String saveFormat, int pageId) {
public ExtractedImage(
BufferedImage image,
String filename,
String saveFormat,
int pageId,
int elementId
) {
this.image = image;
this.filename = filename;
this.saveFormat = saveFormat;
this.pageId = pageId;
this.elementId = elementId;
}

/**
Expand All @@ -53,11 +61,10 @@ public void writeToFile(String outputPath) throws IOException {
* @throws IOException Throws if the file can't be accessed.
*/
public void writeToFile(Path outputPath) throws IOException {
if (Files.isDirectory(outputPath)) {
outputPath = outputPath.resolve(this.filename);
if (!Files.isDirectory(outputPath)) {
throw new IllegalArgumentException("Provided path is not a directory.");
}
var outputfile = outputPath.toFile();
ImageIO.write(this.image, this.saveFormat, outputfile);
ImageIO.write(this.image, this.saveFormat, outputPath.resolve(this.filename).toFile());
}

/**
Expand Down
84 changes: 28 additions & 56 deletions src/main/java/com/mindee/image/ImageExtractor.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,17 @@ public class ImageExtractor {
public ImageExtractor(LocalInputSource source) throws IOException {

this.pageImages = new ArrayList<>();
this.filename = source.getFilename();

if (source.isPDF()) {
this.saveFormat = "jpg";
var pdfPageImages = getPDFRasterizer().PDFToImages(source.getFile(), source.getFilename());
for (PDFPageImage pdfPageImage : pdfPageImages) {
this.pageImages.add(pdfPageImage.getImage());
}
this.filename = source.getFilename() + "." + this.saveFormat;
} else {
this.filename = source.getFilename();
String[] splitName = InputSourceUtils.splitNameStrict(this.filename);
this.saveFormat = splitName[1].toLowerCase();

var input = new ByteArrayInputStream(source.getFile());
this.pageImages.add(ImageIO.read(input));
}
Expand Down Expand Up @@ -64,53 +62,29 @@ public int getPageCount() {
*
* @param <FieldT> Type of field (needs to support positioning data).
* @param fields List of Fields to extract.
* @param pageIndex The page index to extract, begins at 0.
* @param pageId The page index to extract, begins at 0.
* @return A list of {@link ExtractedImage}.
*/
public <FieldT extends PositionDataField> ExtractedImages extractImagesFromPage(
List<FieldT> fields,
int pageIndex
int pageId
) {
return extractImagesFromPage(fields, pageIndex, this.filename);
}

/**
* Extract multiple images on a given page from a list of fields having position data.
*
* @param <FieldT> Type of field (needs to support positioning data).
* @param fields List of Fields to extract.
* @param pageIndex The page index to extract, begins at 0.
* @param outputName The base output filename, must have an image extension.
* @return A list of {@link ExtractedImage}.
*/
public <FieldT extends PositionDataField> ExtractedImages extractImagesFromPage(
List<FieldT> fields,
int pageIndex,
String outputName
) {
String filename;
if (this.getPageCount() > 1) {
String[] splitName = InputSourceUtils.splitNameStrict(outputName);
filename = splitName[0] + "." + this.saveFormat;
} else {
filename = outputName;
}
return extractFromPage(fields, pageIndex, filename);
return extractFromPage(fields, pageId, this.filename);
}

private <FieldT extends PositionDataField> ExtractedImages extractFromPage(
List<FieldT> fields,
int pageIndex,
int pageId,
String outputName
) {
String[] splitName = InputSourceUtils.splitNameStrict(outputName);
var filename = String
.format("%s_page-%3s.%s", splitName[0], pageIndex + 1, splitName[1])
.replace(" ", "0");

var extractedImages = new ExtractedImages();
for (int i = 0; i < fields.size(); i++) {
ExtractedImage extractedImage = extractImage(fields.get(i), pageIndex, i + 1, filename);
for (int elementId = 0; elementId < fields.size(); elementId++) {
ExtractedImage extractedImage = extractImage(
fields.get(elementId),
pageId,
elementId,
outputName
);
if (extractedImage != null) {
extractedImages.add(extractedImage);
}
Expand All @@ -123,33 +97,31 @@ private <FieldT extends PositionDataField> ExtractedImages extractFromPage(
*
* @param <FieldT> Type of field (needs to support positioning data).
* @param field The field to extract.
* @param index The index to use for naming the extracted image.
* @param elementId The index to use for naming the extracted image.
* @param filename Name of the file.
* @param pageIndex The page index to extract, begins at 0.
* @param pageId The page index to extract, begins at 0.
* @return The {@link ExtractedImage}, or <code>null</code> if the field does not have valid
* position data.
*/
public <FieldT extends PositionDataField> ExtractedImage extractImage(
FieldT field,
int pageIndex,
int index,
int pageId,
int elementId,
String filename
) {
String[] splitName = InputSourceUtils.splitNameStrict(filename);
String saveFormat = splitName[1].toLowerCase();
var polygon = field.getPolygon();
if (polygon == null) {
return null;
}
String fieldFilename = splitName[0]
+ String.format("_%3s", index).replace(" ", "0")
+ "."
+ saveFormat;
return new ExtractedImage(
extractImage(polygon.getAsBbox(), pageIndex),
fieldFilename,
saveFormat,
pageIndex
extractImage(polygon.getAsBbox(), pageId),
String
.format("%s_page-%3s-item-%3s.%s", splitName[0], pageId + 1, elementId + 1, this.saveFormat)
.replace(" ", "0"),
this.saveFormat,
pageId,
elementId
);
}

Expand All @@ -158,17 +130,17 @@ public <FieldT extends PositionDataField> ExtractedImage extractImage(
*
* @param <FieldT> Type of field (needs to support positioning data).
* @param field The field to extract.
* @param index The index to use for naming the extracted image.
* @param pageIndex The 0-based page index to extract.
* @param elementId The index to use for naming the extracted image.
* @param pageId The 0-based page index to extract.
* @return The {@link ExtractedImage}, or <code>null</code> if the field does not have valid
* position data.
*/
public <FieldT extends PositionDataField> ExtractedImage extractImage(
FieldT field,
int pageIndex,
int index
int pageId,
int elementId
) {
return extractImage(field, pageIndex, index, this.filename);
return extractImage(field, pageId, elementId, this.filename);
}

private BufferedImage extractImage(Bbox bbox, int pageIndex) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/mindee/pdf/BasePDFExtractor.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ public ExtractedPDFs extractSubDocuments(List<List<Integer>> pageIndexes) throws
protected String makeFilename(List<Integer> pageNumbers) {
String[] splitName = InputSourceUtils.splitNameStrict(filename);
return splitName[0]
+ String.format("_%3s", pageNumbers.get(0)).replace(" ", "0")
+ String.format("_pages-%3s", pageNumbers.get(0) + 1).replace(" ", "0")
+ "-"
+ String.format("%3s", pageNumbers.get(pageNumbers.size() - 1)).replace(" ", "0")
+ String.format("%3s", pageNumbers.get(pageNumbers.size() - 1) + 1).replace(" ", "0")
+ "."
+ splitName[1];
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/mindee/pdf/ExtractedPDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ public ExtractedPDF(byte[] fileBytes, String filename) {
* @throws IOException Throws if the file can't be accessed.
*/
public void writeToFile(Path outputPath) throws IOException {
if (Files.isDirectory(outputPath)) {
outputPath = outputPath.resolve(this.filename);
if (!Files.isDirectory(outputPath)) {
throw new IllegalArgumentException("Provided path is not a directory.");
}
Files.write(outputPath, this.fileBytes);

Files.write(outputPath.resolve(this.filename), this.fileBytes);
}

/**
Expand Down
41 changes: 32 additions & 9 deletions src/main/java/com/mindee/v2/fileoperations/Crop.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import com.mindee.v2.product.crop.CropItem;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Crop {
private final ImageExtractor imageExtractor;
Expand All @@ -15,21 +18,41 @@ public Crop(LocalInputSource inputSource) throws IOException {
this.imageExtractor = new ImageExtractor(inputSource);
}

public ExtractedImage extractSingleCrop(CropItem cropItem) throws IOException {
public ExtractedImage extractSingleCrop(CropItem cropItem) {
return this.imageExtractor
.extractImage(cropItem.getLocation(), cropItem.getLocation().getPage(), 0);
}

public ExtractedImages extractMultipleCrops(List<CropItem> cropItems) {
var extractedImages = new ExtractedImages();
for (int i = 0; i < cropItems.size(); i++) {
var cropItem = cropItems.get(i);
extractedImages
.add(
this.imageExtractor
.extractImage(cropItem.getLocation(), cropItem.getLocation().getPage(), i + 1)
);
if (cropItems == null || cropItems.isEmpty()) {
return new ExtractedImages();
}

// Group crops by page, preserving insertion order
Map<Integer, List<CropItem>> cropsByPage = cropItems
.stream()
.collect(
Collectors
.groupingBy(
item -> item.getLocation().getPage(),
java.util.LinkedHashMap::new,
Collectors.toList()
)
);

var extractedImages = new ExtractedImages();
cropsByPage
.forEach(
(page, pageCrops) -> IntStream
.range(0, pageCrops.size())
.forEach(
elementId -> extractedImages
.add(
this.imageExtractor
.extractImage(pageCrops.get(elementId).getLocation(), page, elementId)
)
)
);
return extractedImages;
}
}
11 changes: 11 additions & 0 deletions src/test/java/com/mindee/TestingUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
import org.junit.jupiter.api.Assertions;

public class TestingUtilities {
public static void deleteRecursively(Path path) throws IOException {
if (Files.exists(path)) {
try (var entries = Files.walk(path)) {
entries
.sorted(java.util.Comparator.reverseOrder())
.map(Path::toFile)
.forEach(java.io.File::delete);
}
}
}

public static Path getResourcePath(String filePath) {
return Paths.get("src/test/resources/" + filePath);
}
Expand Down
20 changes: 8 additions & 12 deletions src/test/java/com/mindee/image/ImageExtractorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ public void givenAnImage_shouldExtractPositionFields() throws IOException {
LocalInputSource source = extractedImage.asInputSource();
Assertions
.assertEquals(
String.format("default_sample_page-001_%3s.jpg", i + 1).replace(" ", "0"),
String
.format("default_sample_page-%3s-item-%3s.jpg", page.getPageId() + 1, i + 1)
.replace(" ", "0"),
source.getFilename()
);
}
Expand All @@ -93,28 +95,22 @@ public void givenAnImage_shouldExtractValueFields() throws IOException {

for (Page<BarcodeReaderV1Document> page : inference.getPages()) {
List<ExtractedImage> codes1D = extractor
.extractImagesFromPage(
page.getPrediction().getCodes1D(),
page.getPageId(),
"barcodes_1D.png"
);
.extractImagesFromPage(page.getPrediction().getCodes1D(), page.getPageId());
for (int i = 0; i < codes1D.size(); i++) {
ExtractedImage extractedImage = codes1D.get(i);
Assertions.assertNotNull(extractedImage.getImage());
LocalInputSource source = extractedImage.asInputSource();
Assertions
.assertEquals(
String.format("barcodes_1D_page-001_%3s.png", i + 1).replace(" ", "0"),
String
.format("default_sample_page-%3s-item-%3s.jpg", page.getPageId() + 1, i + 1)
.replace(" ", "0"),
source.getFilename()
);
extractedImage.writeToFile(getResourcePath("output/"));
}
List<ExtractedImage> codes2D = extractor
.extractImagesFromPage(
page.getPrediction().getCodes2D(),
page.getPageId(),
"barcodes_2D.png"
);
.extractImagesFromPage(page.getPrediction().getCodes2D(), page.getPageId());
for (ExtractedImage extractedImage : codes2D) {
Assertions.assertNotNull(extractedImage.getImage());
extractedImage.writeToFile(getResourcePath("output/"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ public void givenAPDF_shouldExtractInvoices() throws IOException, InterruptedExc
List<ExtractedPDF> extractedPDFsStrict = extractor
.extractInvoices(inference.getPrediction().getInvoicePageGroups(), false);
Assertions.assertEquals(2, extractedPDFsStrict.size());
Assertions.assertEquals("default_sample_000-000.pdf", extractedPDFsStrict.get(0).getFilename());
Assertions.assertEquals("default_sample_001-001.pdf", extractedPDFsStrict.get(1).getFilename());
Assertions
.assertEquals("default_sample_pages-001-001.pdf", extractedPDFsStrict.get(0).getFilename());
Assertions
.assertEquals("default_sample_pages-002-002.pdf", extractedPDFsStrict.get(1).getFilename());

PredictResponse<InvoiceV4> invoice0 = getInvoicePrediction(
extractedPDFsStrict.get(0).asInputSource()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ public void givenAPDF_shouldExtractInvoicesNoStrict() throws IOException {
var extractedPDFSNoStrict = extractor
.extractInvoices(inference.getPrediction().getInvoicePageGroups(), false);
Assertions.assertEquals(3, extractedPDFSNoStrict.size());
Assertions.assertEquals("invoice_5p_000-000.pdf", extractedPDFSNoStrict.get(0).getFilename());
Assertions.assertEquals("invoice_5p_001-003.pdf", extractedPDFSNoStrict.get(1).getFilename());
Assertions.assertEquals("invoice_5p_004-004.pdf", extractedPDFSNoStrict.get(2).getFilename());
Assertions
.assertEquals("invoice_5p_pages-001-001.pdf", extractedPDFSNoStrict.get(0).getFilename());
Assertions
.assertEquals("invoice_5p_pages-002-004.pdf", extractedPDFSNoStrict.get(1).getFilename());
Assertions
.assertEquals("invoice_5p_pages-005-005.pdf", extractedPDFSNoStrict.get(2).getFilename());
}

@Test
Expand All @@ -48,7 +51,9 @@ public void givenAPDF_shouldExtractInvoicesStrict() throws IOException {
var extractedPDFStrict = extractor
.extractInvoices(inference.getPrediction().getInvoicePageGroups(), true);
Assertions.assertEquals(2, extractedPDFStrict.size());
Assertions.assertEquals("invoice_5p_000-000.pdf", extractedPDFStrict.get(0).getFilename());
Assertions.assertEquals("invoice_5p_001-004.pdf", extractedPDFStrict.get(1).getFilename());
Assertions
.assertEquals("invoice_5p_pages-001-001.pdf", extractedPDFStrict.get(0).getFilename());
Assertions
.assertEquals("invoice_5p_pages-002-005.pdf", extractedPDFStrict.get(1).getFilename());
}
}
Loading
Loading