Merge branch 'main' into deleteZone
This commit is contained in:
commit
661524d675
31 files changed
+1111
-477
No files matched your search
@@ -1,56 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
public class Algorithm {
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final ArrayList<Parameter> parameters;
|
||||
|
||||
public Algorithm(String name, String path) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.parameters = new ArrayList<>();
|
||||
switch (path) {
|
||||
case "changeLuminosity":
|
||||
Parameter param1 = new Parameter("delta", "delta", -255, 255, 1);
|
||||
this.parameters.add(param1);
|
||||
break;
|
||||
case "histogram":
|
||||
break;
|
||||
case "colorFilter":
|
||||
Parameter param2 = new Parameter("hue", "Teinte", 0, 359, 1);
|
||||
this.parameters.add(param2);
|
||||
break;
|
||||
case "meanFilter":
|
||||
Parameter param3 = new Parameter("size", "Taille du filtre", 1, 101, 2);
|
||||
this.parameters.add(param3);
|
||||
break;
|
||||
case "gaussienFilter":
|
||||
// TODO
|
||||
// Parameter param4 = new Parameter("size", "Taille du filtre", 1, 101, 2);
|
||||
// this.parameters.add(param4);
|
||||
break;
|
||||
case "contours":
|
||||
break;
|
||||
default:
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid path");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public ArrayList<Parameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package pdl.backend.Algorithm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
import pdl.backend.Algorithm.Parameters.ParameterNullPointerException;
|
||||
|
||||
public class Algorithm {
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final List<Parameter> parameters;
|
||||
private final AlgorithmFunctionInterface function;
|
||||
|
||||
public Algorithm(String name, String path, List<Parameter> parameters, AlgorithmFunctionInterface function) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.parameters = parameters;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public void apply(Planar<GrayU8> image) {
|
||||
List<Object> para = this.parameters.stream().map(p -> p.getValue()).toList();
|
||||
for (Object p : para) {
|
||||
if (p == null) {
|
||||
throw new ParameterNullPointerException("Parameter not set");
|
||||
}
|
||||
}
|
||||
this.function.apply(image, para);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public List<Parameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
node.put("name", this.getName());
|
||||
node.put("path", this.getPath());
|
||||
ArrayNode parameters = node.putArray("parameters");
|
||||
for (Parameter p : this.getParameters()) {
|
||||
parameters.add(p.getNode(mapper));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package pdl.backend.Algorithm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
|
||||
public interface AlgorithmFunctionInterface {
|
||||
void apply(Planar<GrayU8> image, List<Object> parameters);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class DoubleParameter extends Parameter {
|
||||
private final double min;
|
||||
private final double max;
|
||||
private final double step;
|
||||
|
||||
public DoubleParameter(String name, String displayName, double min, double max, double step) {
|
||||
super(name, displayName, "range");
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public double getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public double getStep() {
|
||||
return this.step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(Double.parseDouble(value));
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
node.put("min", this.min);
|
||||
node.put("max", this.max);
|
||||
node.put("step", this.step);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class IntegerParameter extends Parameter {
|
||||
private final int min;
|
||||
private final int max;
|
||||
private final int step;
|
||||
|
||||
public IntegerParameter(String name, String displayName, int min, int max, int step) {
|
||||
super(name, displayName, "range");
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return this.step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
node.put("min", this.min);
|
||||
node.put("max", this.max);
|
||||
node.put("step", this.step);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public abstract class Parameter {
|
||||
private final String name;
|
||||
private final String displayName;
|
||||
private final String type;
|
||||
private Object value;
|
||||
|
||||
public Parameter(String name, String displayName, String type) {
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
node.put("name", this.name);
|
||||
node.put("displayName", this.displayName);
|
||||
node.put("type", this.type);
|
||||
return node;
|
||||
}
|
||||
|
||||
public void setValueObject(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
public class ParameterNullPointerException extends NullPointerException {
|
||||
public ParameterNullPointerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package pdl.backend.Algorithm.Parameters;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class SelectParameter extends Parameter {
|
||||
private final String[] options;
|
||||
|
||||
public SelectParameter(String name, String displayName, String[] options) {
|
||||
super(name, displayName, "select");
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public String[] getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValueObject(value);
|
||||
}
|
||||
|
||||
public ObjectNode getNode(ObjectMapper mapper) {
|
||||
ObjectNode node = super.getNode(mapper);
|
||||
ArrayNode optionsNode = mapper.createArrayNode();
|
||||
for (String option : this.options) {
|
||||
optionsNode.add(option);
|
||||
}
|
||||
node.set("options", optionsNode);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class AlgorithmController {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@GetMapping(value = "/algorithms", produces = "application/json")
|
||||
@ResponseBody
|
||||
public ArrayNode getAlgorithmList() {
|
||||
ArrayList<Algorithm> algorithms = new ArrayList<Algorithm>();
|
||||
Algorithm algo = new Algorithm("Changement de luminosité", "changeLuminosity");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Changement de teinte", "colorFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Filtre Moyenneur", "meanFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Filtre Gaussien", "gaussienFilter");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Détection de contours", "contours");
|
||||
algorithms.add(algo);
|
||||
algo = new Algorithm("Égalisation d'histogramme", "histogram");
|
||||
algorithms.add(algo);
|
||||
|
||||
ArrayNode nodes = mapper.createArrayNode();
|
||||
for (Algorithm a : algorithms) {
|
||||
ObjectNode objectNode = mapper.createObjectNode();
|
||||
objectNode.put("name", a.getName());
|
||||
objectNode.put("path", a.getPath());
|
||||
ArrayNode parameters = objectNode.putArray("parameters");
|
||||
for (Parameter p : a.getParameters()) {
|
||||
ObjectNode paramNode = mapper.createObjectNode();
|
||||
paramNode.put("name", p.getName());
|
||||
paramNode.put("displayName", p.getDisplayName());
|
||||
paramNode.put("min", p.getMin());
|
||||
paramNode.put("max", p.getMax());
|
||||
paramNode.put("step", p.getStep());
|
||||
parameters.add(paramNode);
|
||||
}
|
||||
nodes.add(objectNode);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package pdl.backend.Controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Algorithm;
|
||||
import pdl.backend.Algorithm.Parameters.DoubleParameter;
|
||||
import pdl.backend.Algorithm.Parameters.IntegerParameter;
|
||||
import pdl.backend.Algorithm.Parameters.SelectParameter;
|
||||
import pdl.backend.Image.ImageProcessing;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class AlgorithmController {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
public static List<Algorithm> algorithmList() {
|
||||
final String[] borderTypes = { "SKIP", "ZERO", "NORMALIZED", "REFLECT", "EXTENDED", "WRAP" };
|
||||
ArrayList<Algorithm> algorithms = new ArrayList<Algorithm>();
|
||||
algorithms.add(new Algorithm("Changement de luminosité", "changeLuminosity",
|
||||
Arrays.asList(new IntegerParameter("delta", "delta", -255, 255, 1)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.changeLuminosity(input, (int) para.get(0));
|
||||
}));
|
||||
algorithms.add(new Algorithm("Changement de teinte", "colorFilter",
|
||||
Arrays.asList(new IntegerParameter("hue", "Teinte", 0, 359, 1)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.colorFilter(input, (int) para.get(0));
|
||||
}));
|
||||
|
||||
// TODO: TESTS
|
||||
algorithms.add(new Algorithm("Filtre Moyenneur", "meanFilter", Arrays.asList(
|
||||
new IntegerParameter("size", "Taille du filtre", 1, 21, 2),
|
||||
new SelectParameter("borderType", "Type de bordure", borderTypes)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.meanFilter(input, (int) para.get(0), BorderType.valueOf((String) para.get(1)));
|
||||
}));
|
||||
|
||||
// TODO: TESTS
|
||||
algorithms.add(new Algorithm("Filtre Gaussien", "gaussienFilter", Arrays.asList(
|
||||
new IntegerParameter("size", "Taille du filtre", 1, 21, 2),
|
||||
new DoubleParameter("sigma", "Sigma", 0.1, 2, 0.1),
|
||||
new SelectParameter("borderType", "Type de bordure", borderTypes)),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.gaussienFilter(input, (int) para.get(0), (double) para.get(1),
|
||||
BorderType.valueOf((String) para.get(2)));
|
||||
}));
|
||||
|
||||
algorithms.add(new Algorithm("Détection de contours", "contours", new ArrayList<Parameter>(),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.gradientImageSobel(input);
|
||||
}));
|
||||
|
||||
algorithms.add(new Algorithm("Égalisation d'histogramme", "histogram", new ArrayList<Parameter>(),
|
||||
(Planar<GrayU8> input, List<Object> para) -> {
|
||||
ImageProcessing.histogram(input);
|
||||
}));
|
||||
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/algorithms", produces = "application/json")
|
||||
@ResponseBody
|
||||
public ArrayNode getAlgorithmList() {
|
||||
List<Algorithm> algorithms = AlgorithmController.algorithmList();
|
||||
ArrayNode nodes = mapper.createArrayNode();
|
||||
for (Algorithm a : algorithms) {
|
||||
nodes.add(a.getNode(mapper));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
}
|
||||
+38
-72
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Controller;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -16,8 +16,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import boofcv.io.image.ConvertBufferedImage;
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
import pdl.backend.Algorithm.Algorithm;
|
||||
import pdl.backend.Algorithm.Parameters.Parameter;
|
||||
import pdl.backend.Image.Image;
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
@@ -135,6 +140,23 @@ public class ImageController {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public BorderType getBorderType(String borderType) {
|
||||
switch (borderType) {
|
||||
case "EXTENDED":
|
||||
return BorderType.EXTENDED;
|
||||
case "REFLECT":
|
||||
return BorderType.REFLECT;
|
||||
case "REFLECT_101":
|
||||
return BorderType.NORMALIZED;
|
||||
case "WRAP":
|
||||
return BorderType.WRAP;
|
||||
case "ZERO":
|
||||
return BorderType.ZERO;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* récupère une image et la modifie si un algorithme est renseigné
|
||||
*
|
||||
@@ -159,6 +181,7 @@ public class ImageController {
|
||||
return ResponseEntity.ok().contentType(image.getMediaType())
|
||||
.body(new InputStreamResource(inputStream));
|
||||
}
|
||||
final List<Algorithm> algos = AlgorithmController.algorithmList();
|
||||
// Vérifie la présence du paramètre "algorithm"
|
||||
if (!parameters.containsKey("algorithm")) {
|
||||
return JSONError("First parameter should be 'algorithm'", HttpStatus.BAD_REQUEST);
|
||||
@@ -173,82 +196,25 @@ public class ImageController {
|
||||
}
|
||||
Planar<GrayU8> input = ConvertBufferedImage.convertFromPlanar(bufImg, null, true, GrayU8.class);
|
||||
|
||||
// applique l'algo a l'image en fonction des parametres et verifie la validite
|
||||
// des parametres
|
||||
switch (algo) {
|
||||
case "changeLuminosity":
|
||||
if (!parameters.containsKey("delta") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("delta"));
|
||||
ImageProcessing.changeLuminosity(input, i);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'delta' should be an integer", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
break;
|
||||
case "histogram":
|
||||
if (parameters.size() != 1)
|
||||
return JSONError("Algorithm 'histogram' doesn't require any parameters", HttpStatus.BAD_REQUEST);
|
||||
ImageProcessing.histogram(input);
|
||||
break;
|
||||
case "colorFilter":
|
||||
if (!parameters.containsKey("hue") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'colorFilter' requires one parameter 'hue'(integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("hue"));
|
||||
if (i < 0) {
|
||||
return JSONError("Parameter 'hue' should be an integer between 0 and 360",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
for (Algorithm a : algos) {
|
||||
if (a.getPath().equals(algo)) {
|
||||
for (Parameter p : a.getParameters()) {
|
||||
if (!parameters.containsKey(p.getName())) {
|
||||
return JSONError("Parameter '" + p.getName() + "' is missing", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
p.setValue(parameters.get(p.getName()));
|
||||
} catch (Exception e) {
|
||||
return JSONError("Parameter '" + p.getName() + "' is invalid", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
ImageProcessing.colorFilter(i, input);
|
||||
// hue entre 0 et 360?
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'hue' should be an integer between 0 and 360",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
break;
|
||||
case "meanFilter":
|
||||
if (!parameters.containsKey("size") || parameters.size() != 2)
|
||||
return JSONError("Algorithm 'meanFilter' requires one parameter 'size'(positive odd integer)",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
int i = Integer.parseInt(parameters.get("size"));
|
||||
if (i % 2 == 0)
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.meanFilter(clone, input, i);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
a.apply(input);
|
||||
} catch (Exception e) {
|
||||
return JSONError("There was an internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
break;
|
||||
case "gaussienFilter":
|
||||
if (/* !parameters.containsKey("size") || */ parameters.size() != 1)
|
||||
return JSONError("Algorithm 'gaussienFilter' requires no parameters", HttpStatus.BAD_REQUEST);
|
||||
try {
|
||||
// int i = Integer.parseInt(parameters.get("size"));
|
||||
// if (i % 2 == 0)
|
||||
// return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
// HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.gaussienFilter(clone, input, 5);
|
||||
} catch (NumberFormatException e) {
|
||||
return JSONError("Parameter 'size' should be a positive odd integer",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
break;
|
||||
case "contours":
|
||||
if (parameters.size() != 1)
|
||||
return JSONError("Algorithm 'contours' doesn't require any parameters", HttpStatus.BAD_REQUEST);
|
||||
Planar<GrayU8> clone = input.clone();
|
||||
ImageProcessing.gradientImageSobel(clone, input);
|
||||
break;
|
||||
default:
|
||||
return JSONError("Requested algorithm doesn't exist", HttpStatus.BAD_REQUEST);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// transforme l'image en quelque chose que le navigateur puisse lire
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -13,6 +13,8 @@ import java.util.Optional;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import pdl.backend.Dao;
|
||||
|
||||
@Repository
|
||||
public class ImageDao implements Dao<Image> {
|
||||
|
||||
@@ -21,7 +23,7 @@ public class ImageDao implements Dao<Image> {
|
||||
private final Map<Long, Image> images = new HashMap<>();
|
||||
|
||||
public ImageDao() throws IOException {
|
||||
this("images");
|
||||
this("/images");
|
||||
}
|
||||
|
||||
public ImageDao(final String folderPathStr) throws IOException {
|
||||
+132
-80
@@ -1,5 +1,6 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import boofcv.struct.border.BorderType;
|
||||
import boofcv.struct.image.GrayU8;
|
||||
import boofcv.struct.image.Planar;
|
||||
|
||||
@@ -9,7 +10,10 @@ import java.util.Random;
|
||||
|
||||
import boofcv.concurrency.BoofConcurrency;
|
||||
|
||||
public class ImageProcessing {
|
||||
public final class ImageProcessing {
|
||||
|
||||
private ImageProcessing() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,15 +50,14 @@ public class ImageProcessing {
|
||||
*
|
||||
* Convertit une image en couleur en une image en niveaux de gris
|
||||
*
|
||||
* @param input l'image d'entrée en couleur
|
||||
* @param output l'image de sortie en niveaux de gris
|
||||
* @param input l'image d'entrée en couleur
|
||||
*/
|
||||
public static void convertColorToGray(Planar<GrayU8> input, Planar<GrayU8> output) {
|
||||
public static void convertColorToGray(Planar<GrayU8> input) {
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
int[] rgb = getRGBValue(input, x, y);
|
||||
int gray = (int) (0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]);
|
||||
setRGBValue(output, x, y, new int[] { gray, gray, gray });
|
||||
setRGBValue(input, x, y, new int[] { gray, gray, gray });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -93,8 +96,8 @@ public class ImageProcessing {
|
||||
*/
|
||||
public static void histogram(Planar<GrayU8> input) {
|
||||
int h[] = new int[256];
|
||||
Planar<GrayU8> grayInput = input.createSameShape();
|
||||
convertColorToGray(input, grayInput);
|
||||
Planar<GrayU8> grayInput = input.clone();
|
||||
convertColorToGray(grayInput);
|
||||
// On remplit le tableau avec le nombre de pixels pour chaque niveau de
|
||||
// gris
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
@@ -218,7 +221,7 @@ public class ImageProcessing {
|
||||
* @param hue La valeur de la teinte à appliquer
|
||||
* @param input L'image à modifier
|
||||
*/
|
||||
public static void colorFilter(int hue, Planar<GrayU8> input) {
|
||||
public static void colorFilter(Planar<GrayU8> input, int hue) {
|
||||
if (hue < 0 || hue > 360) {
|
||||
throw new IllegalArgumentException("Hue doit être compris entre 0 et 360");
|
||||
}
|
||||
@@ -240,15 +243,15 @@ public class ImageProcessing {
|
||||
* Calcule et stocke dans output l'image de la norme du gradient
|
||||
* calculé sur input par convolution avec les filtres de Sobel.
|
||||
*
|
||||
* @param input L'image d'entrée en niveaux de gris.
|
||||
* @param output L'image de sortie en niveaux de gris.
|
||||
* @param input L'image d'entrée en niveaux de gris.
|
||||
*/
|
||||
public static void gradientImageSobel(Planar<GrayU8> input, Planar<GrayU8> output) {
|
||||
public static void gradientImageSobel(Planar<GrayU8> input) {
|
||||
Planar<GrayU8> output = input.createSameShape();
|
||||
int h1[][] = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
|
||||
int h2[][] = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
|
||||
int half = h1.length / 2;
|
||||
Planar<GrayU8> inputGray = input.createSameShape();
|
||||
convertColorToGray(input, inputGray);
|
||||
Planar<GrayU8> inputGray = input.clone();
|
||||
convertColorToGray(inputGray);
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
if (y < half || x < half || y >= input.height - half || x >= input.width - half) {
|
||||
@@ -279,78 +282,84 @@ public class ImageProcessing {
|
||||
setRGBValue(output, x, y, rgb);
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < input.width; i++) {
|
||||
for (int j = 0; j < input.height; j++) {
|
||||
int[] rgb = getRGBValue(output, i, j);
|
||||
setRGBValue(input, i, j, rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique un filtre moyenneur de taille indiquée à l'image
|
||||
* passée en paramètre (le bord de l'image n'est pas modifié)
|
||||
* passée en paramètre et stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input image d'entrée
|
||||
* @param output image de sortie
|
||||
* @param size taille du filtre à appliquer (doit être impaire)
|
||||
* @param input image d'entrée
|
||||
* @param size taille du filtre à appliquer (doit être impaire)
|
||||
* @param borderType type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static void meanFilter(Planar<GrayU8> input, Planar<GrayU8> output, int size) {
|
||||
public static void meanFilter(Planar<GrayU8> input, int size, BorderType borderType) {
|
||||
// Vérification que la taille est impaire
|
||||
if (size % 2 == 0) {
|
||||
throw new IllegalArgumentException("La taille doit être impaire");
|
||||
if (size % 2 == 0 || size < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"La taille doit être impaire et positive");
|
||||
}
|
||||
// Boucle pour parcourir tous les pixels de l'image d'entrée
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
if (y < size / 2 || x < size / 2 || y >= input.height - size / 2
|
||||
|| x >= input.width - size / 2) {
|
||||
setRGBValue(output, x, y, getRGBValue(input, x, y));
|
||||
continue;
|
||||
}
|
||||
int totalValue[] = new int[3];
|
||||
// Boucle pour parcourir le carré size x size autour du pixel actuel
|
||||
for (int j = y - size / 2; j < y + size / 2 + 1; j++) {
|
||||
for (int i = x - size / 2; i < x + size / 2 + 1; i++) {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += input.getBand(k).get(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Calcule la moyenne des pixels du carré et l'applique au pixel actuel dans
|
||||
// l'image de
|
||||
// sortie
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= size * size;
|
||||
}
|
||||
setRGBValue(output, x, y, totalValue);
|
||||
int[][] kernel = new int[size][size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int j = 0; j < size; j++) {
|
||||
kernel[i][j] = 1;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
convolution(input, kernel, borderType);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Calcule le noyau gaussien de taille demandée
|
||||
*
|
||||
* @param size taille du noyau gaussien
|
||||
* @return noyau gaussien
|
||||
* Applique un filtre gaussien de taille et de valeur données sur une image et
|
||||
* stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input L'image d'entrée
|
||||
* @param size La taille du filtre à appliquer (doit être impaire)
|
||||
* @param sigma La valeur du sigma à utiliser pour le filtre gaussien.
|
||||
* @param borderType Le type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static int[][] createGaussienKernel(int size) {
|
||||
// Vérification que la taille est impaire
|
||||
if (size % 2 == 0 || size < 0) {
|
||||
throw new IllegalArgumentException("La taille doit être impaire et positive");
|
||||
public static void gaussienFilter(Planar<GrayU8> input, int size, double sigma, BorderType borderType) {
|
||||
if (size % 2 == 0 || size < 0 || sigma <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"La taille doit être impaire et positive et sigma doit être strictement positif");
|
||||
}
|
||||
double[][] kernel = new double[size][size];
|
||||
double sigma = 4 / 3;
|
||||
double sum = 0.0; // For accumulating the kernel values
|
||||
for (int x = 0; x < size; ++x)
|
||||
for (int y = 0; y < size; ++y) {
|
||||
kernel[x][y] = 1 / (2 * Math.PI * sigma * sigma) * Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
|
||||
|
||||
int half = size / 2;
|
||||
double sum = 0;
|
||||
for (int x = -half; x <= half; ++x) {
|
||||
for (int y = -half; y <= half; ++y) {
|
||||
double gaussianValue = 1 / (2 * Math.PI * sigma * sigma)
|
||||
* Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
|
||||
kernel[x + half][y + half] = gaussianValue;
|
||||
sum += gaussianValue;
|
||||
}
|
||||
}
|
||||
// On récupère le pourcentage de chaque valeur par rapport à la somme
|
||||
for (int x = -half; x <= half; ++x) {
|
||||
for (int y = -half; y <= half; ++y) {
|
||||
kernel[x + half][y + half] = kernel[x + half][y + half] / sum;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
int[][] kernelk = new int[size][size];
|
||||
for (int x = 0; x < size; ++x)
|
||||
for (int y = 0; y < size; ++y)
|
||||
kernelk[x][y] = (int) (kernel[x][y] / sum);
|
||||
|
||||
return kernelk;
|
||||
int[][] kernelInt = new int[size][size];
|
||||
double factor = 1 / kernel[0][0];
|
||||
// Si le facteur est trop grand (la valeur du coin est très très petite par
|
||||
// rapport au reste), on le fixe à 1 million
|
||||
if (factor > 1000000) {
|
||||
factor = 1000000;
|
||||
}
|
||||
// normalisation
|
||||
for (int x = 0; x < size; ++x) {
|
||||
for (int y = 0; y < size; ++y) {
|
||||
kernelInt[x][y] = (int) (kernel[x][y] * factor);
|
||||
}
|
||||
}
|
||||
convolution(input, kernelInt, borderType);
|
||||
}
|
||||
|
||||
public static int[][] createGaussienKernel(int size, double sigma) {
|
||||
@@ -397,15 +406,14 @@ public class ImageProcessing {
|
||||
* Effectue une convolution sur une image avec le noyau
|
||||
* donné et stocke le résultat dans l'image de sortie.
|
||||
*
|
||||
* @param input L'image d'entrée
|
||||
* @param output L'image de sortie
|
||||
* @param kernel Le noyau de convolution à utiliser.
|
||||
* @param input L'image d'entrée
|
||||
* @param kernel Le noyau de convolution à utiliser.
|
||||
* @param borderType Le type de bord à utiliser pour la convolution
|
||||
*/
|
||||
public static void gaussienFilter(Planar<GrayU8> input, Planar<GrayU8> output,
|
||||
int size) {
|
||||
int[][] kernel = { { 1, 2, 3, 2, 1 }, { 2, 6, 8, 6, 2 }, { 3, 8, 10, 8, 3 }, { 2, 6, 8, 6, 2 },
|
||||
{ 1, 2, 3, 2, 1 } };
|
||||
int half = size / 2;
|
||||
public static void convolution(Planar<GrayU8> input, int[][] kernel, BorderType borderType) {
|
||||
Planar<GrayU8> copy = input.clone();
|
||||
int size = kernel.length;
|
||||
int half = kernel.length / 2;
|
||||
int coefs = 0;
|
||||
// Calcul du nombre total de coefficients dans le noyau
|
||||
for (int i = 0; i < kernel.length; i++) {
|
||||
@@ -417,10 +425,54 @@ public class ImageProcessing {
|
||||
// Parcours de l'image d'entrée et application de la convolution
|
||||
BoofConcurrency.loopFor(0, input.height, y -> {
|
||||
for (int x = 0; x < input.width; x++) {
|
||||
// Vérification si le carré sera à l'intérieur de l'image
|
||||
// On traite d'abord les bords
|
||||
if (y < half || x < half || y >= input.height - half || x >= input.width -
|
||||
half) {
|
||||
setRGBValue(output, x, y, getRGBValue(input, x, y));
|
||||
if (borderType == BorderType.SKIP) {
|
||||
setRGBValue(input, x, y, getRGBValue(copy, x, y));
|
||||
} else if (borderType == BorderType.ZERO) {
|
||||
setRGBValue(input, x, y, new int[] { 0, 0, 0 });
|
||||
} else {
|
||||
int totalValue[] = new int[3];
|
||||
int coefsUsed = 0;
|
||||
for (int j = y - half; j < y + half + 1; j++) {
|
||||
int tmpj = j;
|
||||
if (j < 0 || j >= input.height) {
|
||||
if (borderType == BorderType.EXTENDED) {
|
||||
tmpj = j < 0 ? 0 : input.height - 1;
|
||||
} else if (borderType == BorderType.NORMALIZED) {
|
||||
continue;
|
||||
} else if (borderType == BorderType.REFLECT) {
|
||||
tmpj = j < 0 ? -j : (input.height - 1) - (j - (input.height - 1));
|
||||
} else if (borderType == BorderType.WRAP) {
|
||||
tmpj = j < 0 ? j + (input.height - 1) : (j - (input.height - 1));
|
||||
}
|
||||
}
|
||||
for (int i = x - size / 2; i < x + size / 2 + 1; i++) {
|
||||
int tmpi = i;
|
||||
if (i < 0 || i >= input.width) {
|
||||
if (borderType == BorderType.EXTENDED) {
|
||||
tmpi = i < 0 ? 0 : input.width - 1;
|
||||
} else if (borderType == BorderType.NORMALIZED) {
|
||||
continue;
|
||||
} else if (borderType == BorderType.REFLECT) {
|
||||
tmpi = i < 0 ? -i : (input.width - 1) - (i - (input.width - 1));
|
||||
} else if (borderType == BorderType.WRAP) {
|
||||
tmpi = i < 0 ? i + (input.width - 1) : (i - (input.width - 1));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += copy.getBand(k).get(tmpi, tmpj)
|
||||
* kernel[i + (-x + half)][j + (-y + half)];
|
||||
}
|
||||
coefsUsed += kernel[i + (-x + half)][j + (-y + half)];
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= coefsUsed;
|
||||
}
|
||||
setRGBValue(input, x, y, totalValue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int currentJ = 0;
|
||||
@@ -430,7 +482,7 @@ public class ImageProcessing {
|
||||
for (int j = y - half; j <= y + half; j++) {
|
||||
for (int i = x - half; i <= x + half; i++) {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] += input.getBand(k).get(i, j) * kernel[currentJ][currentI];
|
||||
totalValue[k] += copy.getBand(k).get(i, j) * kernel[currentJ][currentI];
|
||||
}
|
||||
currentI++;
|
||||
}
|
||||
@@ -440,7 +492,7 @@ public class ImageProcessing {
|
||||
for (int k = 0; k < totalValue.length; k++) {
|
||||
totalValue[k] /= totalCoefs;
|
||||
}
|
||||
setRGBValue(output, x, y, totalValue);
|
||||
setRGBValue(input, x, y, totalValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package pdl.backend;
|
||||
package pdl.backend.Image;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package pdl.backend;
|
||||
|
||||
public class Parameter {
|
||||
private final String name;
|
||||
private final String displayName;
|
||||
private final int min;
|
||||
private final int max;
|
||||
private final int step;
|
||||
|
||||
public Parameter(String name, String displayName, int min, int max, int step) {
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return this.step;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import pdl.backend.Image.Image;
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.io.IOException;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import pdl.backend.Image.ImageDao;
|
||||
|
||||
public class ImageDaoTests {
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user