diff --git a/build.gradle b/build.gradle
index ddd4656..8be99de 100644
--- a/build.gradle
+++ b/build.gradle
@@ -60,8 +60,6 @@ dependencies {
// Mockito-Junit https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
- testImplementation 'com.google.jimfs:jimfs:1.3.0'
-
testJavaagent "net.bytebuddy:byte-buddy-agent:1.15.7"
// Commons Collections https://mvnrepository.com/artifact/org.apache.commons/commons-collections4
diff --git a/src/main/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategy.java b/src/main/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategy.java
index a76a8a9..dbd5fb5 100644
--- a/src/main/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategy.java
+++ b/src/main/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategy.java
@@ -18,7 +18,9 @@
*/
package com.github.mfl28.boundingboxeditor.model.io;
-import com.github.mfl28.boundingboxeditor.model.data.*;
+import com.github.mfl28.boundingboxeditor.model.data.BoundingBoxData;
+import com.github.mfl28.boundingboxeditor.model.data.ImageAnnotation;
+import com.github.mfl28.boundingboxeditor.model.data.ImageAnnotationData;
import com.github.mfl28.boundingboxeditor.model.io.results.IOErrorInfoEntry;
import com.github.mfl28.boundingboxeditor.model.io.results.ImageAnnotationExportResult;
import com.opencsv.CSVWriterBuilder;
@@ -34,18 +36,18 @@
/**
* Saving-strategy to export annotations to a CSV file.
- *
+ *
* The CSVSaveStrategy supports {@link BoundingBoxData} only.
*/
public class CSVSaveStrategy implements ImageAnnotationSaveStrategy {
- private static final String FILE_NAME_SERIALIZED_NAME = "name";
- private static final String ID_SERIALIZED_NAME = "id";
- private static final String LABEL_SERIALIZED_NAME = "label";
- private static final String MIN_X_SERIALIZED_NAME = "xMin";
- private static final String MAX_X_SERIALIZED_NAME = "xMax";
- private static final String MIN_Y_SERIALIZED_NAME = "yMin";
- private static final String MAX_Y_SERIALIZED_NAME = "yMax";
- private static final String UNSUPPORTED_BOUNDING_SHAPE = "CSV can export Rectangles only";
+ private static final String FILE_NAME_SERIALIZED_NAME = "filename";
+ private static final String WIDTH_SERIALIZED_NAME = "width";
+ private static final String HEIGHT_SERIALIZED_NAME = "height";
+ private static final String CLASS_SERIALIZED_NAME = "class";
+ private static final String MIN_X_SERIALIZED_NAME = "xmin";
+ private static final String MAX_X_SERIALIZED_NAME = "xmax";
+ private static final String MIN_Y_SERIALIZED_NAME = "ymin";
+ private static final String MAX_Y_SERIALIZED_NAME = "ymax";
@Override
public ImageAnnotationExportResult save(ImageAnnotationData annotations, Path destination,
@@ -55,25 +57,29 @@ public ImageAnnotationExportResult save(ImageAnnotationData annotations, Path de
final List errorEntries = new ArrayList<>();
- try(ICSVWriter writer = new CSVWriterBuilder(Files.newBufferedWriter(destination, StandardCharsets.UTF_8)).build()) {
- String[] header = {FILE_NAME_SERIALIZED_NAME, ID_SERIALIZED_NAME, LABEL_SERIALIZED_NAME, MIN_X_SERIALIZED_NAME, MAX_X_SERIALIZED_NAME, MIN_Y_SERIALIZED_NAME, MAX_Y_SERIALIZED_NAME};
+ try (ICSVWriter writer = new CSVWriterBuilder(Files.newBufferedWriter(destination, StandardCharsets.UTF_8)).build()) {
+ String[] header = {
+ FILE_NAME_SERIALIZED_NAME,
+ WIDTH_SERIALIZED_NAME,
+ HEIGHT_SERIALIZED_NAME,
+ CLASS_SERIALIZED_NAME,
+ MIN_X_SERIALIZED_NAME,
+ MIN_Y_SERIALIZED_NAME,
+ MAX_X_SERIALIZED_NAME,
+ MAX_Y_SERIALIZED_NAME};
+
writer.writeNext(header);
- for (ImageAnnotation imageAnnotation : annotations.imageAnnotations()) {
- for (BoundingShapeData boundingShapeData : imageAnnotation.getBoundingShapeData()) {
+
+ for (var imageAnnotation : annotations.imageAnnotations()) {
+ for (var boundingShapeData : imageAnnotation.getBoundingShapeData()) {
if (boundingShapeData instanceof BoundingBoxData boundingBoxData) {
- double xMin = imageAnnotation.getImageMetaData().getImageWidth() * boundingBoxData.getXMinRelative();
- double xMax = imageAnnotation.getImageMetaData().getImageWidth() * boundingBoxData.getXMaxRelative();
- double yMin = imageAnnotation.getImageMetaData().getImageHeight() * boundingBoxData.getYMinRelative();
- double yMax = imageAnnotation.getImageMetaData().getImageHeight() * boundingBoxData.getYMaxRelative();
- String[] line = { imageAnnotation.getImageFileName(), String.valueOf(nrProcessedAnnotations), boundingShapeData.getCategoryName(), String.valueOf((int) xMin), String.valueOf((int) xMax), String.valueOf((int) yMin), String.valueOf((int) yMax)};
- writer.writeNext(line);
- } else {
- errorEntries.add(new IOErrorInfoEntry(imageAnnotation.getImageFileName(), UNSUPPORTED_BOUNDING_SHAPE));
+ writer.writeNext(buildLine(imageAnnotation, boundingBoxData));
}
+
progress.set(1.0 * nrProcessedAnnotations++ / totalNrAnnotations);
}
}
- } catch(IOException e) {
+ } catch (IOException e) {
errorEntries.add(new IOErrorInfoEntry(destination.getFileName().toString(), e.getMessage()));
}
@@ -82,4 +88,22 @@ public ImageAnnotationExportResult save(ImageAnnotationData annotations, Path de
errorEntries
);
}
+
+ private static String[] buildLine(ImageAnnotation imageAnnotation, BoundingBoxData boundingBoxData) {
+ double imageWidth = imageAnnotation.getImageMetaData().getImageWidth();
+ double imageHeight = imageAnnotation.getImageMetaData().getImageHeight();
+
+ var bounds = boundingBoxData.getAbsoluteBoundsInImage(imageWidth, imageHeight);
+
+ return new String[]{
+ imageAnnotation.getImageFileName(),
+ String.valueOf((int) Math.round(imageWidth)),
+ String.valueOf((int) Math.round(imageHeight)),
+ boundingBoxData.getCategoryName(),
+ String.valueOf((int) Math.round(bounds.getMinX())),
+ String.valueOf((int) Math.round(bounds.getMinY())),
+ String.valueOf((int) Math.round(bounds.getMaxX())),
+ String.valueOf((int) Math.round(bounds.getMaxY()))
+ };
+ }
}
diff --git a/src/test/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategyTest.java b/src/test/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategyTest.java
index 95ca563..c4d2e53 100644
--- a/src/test/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategyTest.java
+++ b/src/test/java/com/github/mfl28/boundingboxeditor/model/io/CSVSaveStrategyTest.java
@@ -20,20 +20,20 @@
import com.github.mfl28.boundingboxeditor.model.data.*;
import com.github.mfl28.boundingboxeditor.model.io.results.ImageAnnotationExportResult;
-import com.google.common.jimfs.Configuration;
-import com.google.common.jimfs.Jimfs;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.paint.Color;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class CSVSaveStrategyTest {
@@ -41,81 +41,44 @@ class CSVSaveStrategyTest {
* One image with two annotations gets exported to CSV.
*/
@Test
- void multipleRectangles() throws IOException {
- ObjectCategory objectCategory = new ObjectCategory("category", Color.YELLOW);
+ void testCSVSaving(@TempDir Path tempDir) throws IOException {
+ var objectCategory1 = new ObjectCategory("catA", Color.YELLOW);
+ var objectCategory2 = new ObjectCategory("catB", Color.BLUE);
+ var imageMetaData1 = new ImageMetaData("sample1.png", "folderName", "url", 100, 200, 0);
+ var imageMetaData2 = new ImageMetaData("sample2.png", "folderName", "url", 400, 300, 0);
- ImageAnnotation imageAnnotation = new ImageAnnotation(new ImageMetaData("sample.png", "folderName", "url", 100, 200, 0));
- List boundingShapeDatas = new ArrayList<>();
- boundingShapeDatas.add(new BoundingBoxData(objectCategory, 0,0,0.5,0.5, List.of("tag")));
- boundingShapeDatas.add(new BoundingBoxData(objectCategory, 0,0,0.25,0.25, List.of("tag")));
- imageAnnotation.setBoundingShapeData(boundingShapeDatas);
+ List boundingShapeData1 = List.of(
+ new BoundingBoxData(objectCategory1, 0, 0, 0.5, 0.5, List.of("tag")),
+ new BoundingBoxData(objectCategory2, 0, 0, 0.25, 0.25, List.of("tag")),
+ new BoundingPolygonData(objectCategory1, List.of(0.0, 0.0, 0.5, 0.5), Collections.emptyList())
+ );
- ImageAnnotationData annotations = new ImageAnnotationData(List.of(imageAnnotation),
- Map.of("object", 1),
- Map.of("object", objectCategory));
- Path destination = Jimfs.newFileSystem(Configuration.unix()).getPath("annotations.csv");
- ImageAnnotationExportResult save = new CSVSaveStrategy().save(annotations, destination, new SimpleDoubleProperty(0));
- assertTrue(save.getErrorTableEntries().isEmpty());
+ var imageAnnotation1 = new ImageAnnotation(imageMetaData1, boundingShapeData1);
- String content = Files.readString(destination);
- assertEquals("""
- "name","id","label","xMin","xMax","yMin","yMax"
- "sample.png","0","category","0","50","0","100"
- "sample.png","1","category","0","25","0","50"
- """, content);
- }
+ List boundingShapeData2 = List.of(
+ new BoundingBoxData(objectCategory2, 0.1, 0, 0.5, 0.2, List.of("tag"))
+ );
- /**
- * Two images with each one annotation gets exported.
- */
- @Test
- void multipleImages() throws IOException {
- ObjectCategory objectCategory = new ObjectCategory("category", Color.YELLOW);
+ var imageAnnotation2 = new ImageAnnotation(imageMetaData2, boundingShapeData2);
- ImageAnnotation imageAnnotation1 = new ImageAnnotation(
- new ImageMetaData("sample1.png", "folderName", "url", 100, 200, 0),
- List.of(new BoundingBoxData(objectCategory, 0,0,0.5,0.5, List.of("tag"))));
+ ImageAnnotationData annotations = new ImageAnnotationData(
+ List.of(imageAnnotation1, imageAnnotation2),
+ Map.of("catA", 2, "catB", 2),
+ Map.of("catA", objectCategory1, "catB", objectCategory2));
- ImageAnnotation imageAnnotation2 = new ImageAnnotation(
- new ImageMetaData("sample2.png", "folderName", "url", 100, 200, 0),
- List.of(new BoundingBoxData(objectCategory, 0,0,0.25,0.25, List.of("tag"))));
+ Path destination = tempDir.resolve("annotations.csv");
- ImageAnnotationData annotations = new ImageAnnotationData(List.of(imageAnnotation1, imageAnnotation2),
- Map.of("object", 1),
- Map.of("object", objectCategory));
- Path destination = Jimfs.newFileSystem(Configuration.unix()).getPath("annotations.csv");
ImageAnnotationExportResult save = new CSVSaveStrategy().save(annotations, destination, new SimpleDoubleProperty(0));
+
assertTrue(save.getErrorTableEntries().isEmpty());
String content = Files.readString(destination);
- assertEquals("""
- "name","id","label","xMin","xMax","yMin","yMax"
- "sample1.png","0","category","0","50","0","100"
- "sample2.png","1","category","0","25","0","50"
- """, content);
- }
-
- /**
- * One image with one annotation should be saved. The annotation uses an unsupported Bounding Shape, so a ErrorTableEntry is expected.
- */
- @Test
- void wrongBoundingShape() throws IOException {
- ObjectCategory objectCategory = new ObjectCategory("category", Color.YELLOW);
-
- ImageAnnotation imageAnnotation1 = new ImageAnnotation(
- new ImageMetaData("sample1.png", "folderName", "url", 100, 200, 0),
- List.of(new BoundingPolygonData(objectCategory, List.of(0.0, 0.0, 0.5, 0.5), List.of("tag"))));
- ImageAnnotationData annotations = new ImageAnnotationData(List.of(imageAnnotation1),
- Map.of("object", 1),
- Map.of("object", objectCategory));
- Path destination = Jimfs.newFileSystem(Configuration.unix()).getPath("annotations.csv");
- ImageAnnotationExportResult save = new CSVSaveStrategy().save(annotations, destination, new SimpleDoubleProperty(0));
- assertEquals(1, save.getErrorTableEntries().size());
-
- String content = Files.readString(destination);
assertEquals("""
- "name","id","label","xMin","xMax","yMin","yMax"
+ "filename","width","height","class","xmin","ymin","xmax","ymax"
+ "sample1.png","100","200","catA","0","0","50","100"
+ "sample1.png","100","200","catB","0","0","25","50"
+ "sample2.png","400","300","catB","40","0","200","60"
""", content);
}
}
\ No newline at end of file