mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-25 01:41:40 +00:00
Consistently serialize neural network data (#2224)
This commit is contained in:
@@ -86,7 +86,7 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin
|
||||
});
|
||||
|
||||
axiosPost("/objectdetection/rename", "rename an object detection model", {
|
||||
modelPath: model.modelPath.replace("file:", ""),
|
||||
modelPath: model.modelPath,
|
||||
newName: newName
|
||||
});
|
||||
showRenameDialog.value.show = false;
|
||||
@@ -418,7 +418,7 @@ const handleBulkImport = () => {
|
||||
<a
|
||||
ref="exportIndividualModel"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath.replace('file:', '')}`"
|
||||
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath}`"
|
||||
:download="`${showInfo.model.nickname}_${showInfo.model.family}_${showInfo.model.version}_${showInfo.model.resolutionWidth}x${showInfo.model.resolutionHeight}_${showInfo.model.labels.join('_')}.${showInfo.model.family.toLowerCase()}`"
|
||||
target="_blank"
|
||||
/>
|
||||
|
||||
@@ -17,15 +17,9 @@
|
||||
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
|
||||
@@ -37,7 +31,6 @@ import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
@@ -45,89 +38,41 @@ import org.eclipse.jetty.io.EofException;
|
||||
public class JacksonUtils {
|
||||
public static class UIMap extends HashMap<String, Object> {}
|
||||
|
||||
// Custom Path serializer that outputs just the path string without file:/ prefix
|
||||
public static class PathSerializer extends JsonSerializer<Path> {
|
||||
@Override
|
||||
public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
if (value == null) {
|
||||
gen.writeNull();
|
||||
} else {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Path deserializer that reads path strings
|
||||
public static class PathDeserializer extends JsonDeserializer<Path> {
|
||||
@Override
|
||||
public Path deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String pathString = p.getValueAsString();
|
||||
if (pathString == null || pathString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle case where old serialized data might still have file:/ prefix
|
||||
if (pathString.startsWith("file:/")) {
|
||||
pathString = pathString.substring(6); // Remove "file:/" prefix
|
||||
}
|
||||
|
||||
return Paths.get(pathString);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Path key deserializer for Maps with Path keys
|
||||
public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
|
||||
@Override
|
||||
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle case where old serialized data might still have file:/ prefix
|
||||
if (key.startsWith("file:/")) {
|
||||
key = key.substring(6); // Remove "file:/" prefix
|
||||
}
|
||||
|
||||
return Paths.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to create ObjectMapper with Path serialization support
|
||||
private static ObjectMapper createObjectMapperWithPathSupport(Class<?> baseType) {
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build();
|
||||
|
||||
SimpleModule pathModule = new SimpleModule();
|
||||
pathModule.addSerializer(Path.class, new PathSerializer());
|
||||
pathModule.addDeserializer(Path.class, new PathDeserializer());
|
||||
pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());
|
||||
|
||||
return JsonMapper.builder()
|
||||
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.addModule(pathModule)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> void serialize(Path path, T object) throws IOException {
|
||||
serialize(path, object, true);
|
||||
}
|
||||
|
||||
public static <T> String serializeToString(T object) throws IOException {
|
||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
}
|
||||
|
||||
public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
|
||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
saveJsonString(json, path, forceSync);
|
||||
}
|
||||
|
||||
public static <T> T deserialize(Map<?, ?> s, Class<T> ref) throws IOException {
|
||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
|
||||
return objectMapper.convertValue(s, ref);
|
||||
}
|
||||
|
||||
@@ -136,14 +81,28 @@ public class JacksonUtils {
|
||||
throw new EofException("Provided empty string for class " + ref.getName());
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
||||
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
|
||||
return objectMapper.readValue(s, ref);
|
||||
}
|
||||
|
||||
public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
|
||||
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
File jsonFile = new File(path.toString());
|
||||
if (jsonFile.exists() && jsonFile.length() > 0) {
|
||||
return objectMapper.readValue(jsonFile, ref);
|
||||
@@ -156,12 +115,6 @@ public class JacksonUtils {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addDeserializer(ref, deserializer);
|
||||
|
||||
// Add Path support to custom deserializer case as well
|
||||
module.addSerializer(Path.class, new PathSerializer());
|
||||
module.addDeserializer(Path.class, new PathDeserializer());
|
||||
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());
|
||||
|
||||
objectMapper.registerModule(module);
|
||||
|
||||
File jsonFile = new File(path.toString());
|
||||
@@ -182,12 +135,6 @@ public class JacksonUtils {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(ref, serializer);
|
||||
|
||||
// Add Path support to custom serializer case as well
|
||||
module.addSerializer(Path.class, new PathSerializer());
|
||||
module.addDeserializer(Path.class, new PathDeserializer());
|
||||
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());
|
||||
|
||||
objectMapper.registerModule(module);
|
||||
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
saveJsonString(json, path, forceSync);
|
||||
|
||||
@@ -850,33 +850,30 @@ public class RequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private record DeleteObjectDetectionModelRequest(String modelPath) {}
|
||||
private record DeleteObjectDetectionModelRequest(Path modelPath) {}
|
||||
|
||||
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
|
||||
logger.info("Deleting object detection model");
|
||||
Path modelPath;
|
||||
|
||||
try {
|
||||
DeleteObjectDetectionModelRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);
|
||||
|
||||
modelPath = Path.of(request.modelPath.substring(7));
|
||||
|
||||
if (modelPath == null) {
|
||||
if (request.modelPath == null) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided model path was malformed");
|
||||
logger.error("The provided model path was malformed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelPath.toFile().exists()) {
|
||||
if (!request.modelPath.toFile().exists()) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided model path does not exist");
|
||||
logger.error("The provided model path does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelPath.toFile().delete()) {
|
||||
if (!request.modelPath.toFile().delete()) {
|
||||
ctx.status(500);
|
||||
ctx.result("Unable to delete the model file");
|
||||
logger.error("Unable to delete the model file");
|
||||
@@ -886,7 +883,7 @@ public class RequestHandler {
|
||||
if (!ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.removeModel(modelPath)) {
|
||||
.removeModel(request.modelPath)) {
|
||||
ctx.status(400);
|
||||
ctx.result("The model's information was not found in the config");
|
||||
logger.error("The model's information was not found in the config");
|
||||
@@ -910,26 +907,24 @@ public class RequestHandler {
|
||||
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
|
||||
}
|
||||
|
||||
private record RenameObjectDetectionModelRequest(String modelPath, String newName) {}
|
||||
private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
|
||||
|
||||
public static void onRenameObjectDetectionModelRequest(Context ctx) {
|
||||
try {
|
||||
RenameObjectDetectionModelRequest request =
|
||||
JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);
|
||||
|
||||
Path modelPath = Path.of(request.modelPath);
|
||||
|
||||
if (modelPath == null) {
|
||||
if (request.modelPath == null) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided model path was malformed");
|
||||
logger.error("The provided model path was malformed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelPath.toFile().exists()) {
|
||||
if (!request.modelPath.toFile().exists()) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided model path does not exist");
|
||||
logger.error("The model path: " + modelPath + " does not exist");
|
||||
logger.error("The model path: " + request.modelPath + " does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -943,7 +938,7 @@ public class RequestHandler {
|
||||
if (!ConfigManager.getInstance()
|
||||
.getConfig()
|
||||
.neuralNetworkPropertyManager()
|
||||
.renameModel(modelPath, request.newName)) {
|
||||
.renameModel(request.modelPath, request.newName)) {
|
||||
ctx.status(400);
|
||||
ctx.result("The model's information was not found in the config");
|
||||
logger.error("The model's information was not found in the config");
|
||||
|
||||
Reference in New Issue
Block a user