Skip to content

Commit 6285f1e

Browse files
authored
Consistently serialize neural network data (#2224)
1 parent 192e2a1 commit 6285f1e

File tree

3 files changed

+50
-108
lines changed

3 files changed

+50
-108
lines changed

photon-client/src/components/settings/ObjectDetectionCard.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin
8686
});
8787
8888
axiosPost("/objectdetection/rename", "rename an object detection model", {
89-
modelPath: model.modelPath.replace("file:", ""),
89+
modelPath: model.modelPath,
9090
newName: newName
9191
});
9292
showRenameDialog.value.show = false;
@@ -418,7 +418,7 @@ const handleBulkImport = () => {
418418
<a
419419
ref="exportIndividualModel"
420420
style="color: black; text-decoration: none; display: none"
421-
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath.replace('file:', '')}`"
421+
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath}`"
422422
: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()}`"
423423
target="_blank"
424424
/>

photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java

Lines changed: 38 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,9 @@
1717

1818
package org.photonvision.common.util.file;
1919

20-
import com.fasterxml.jackson.core.JsonGenerator;
21-
import com.fasterxml.jackson.core.JsonParser;
2220
import com.fasterxml.jackson.core.json.JsonReadFeature;
23-
import com.fasterxml.jackson.databind.DeserializationContext;
2421
import com.fasterxml.jackson.databind.DeserializationFeature;
25-
import com.fasterxml.jackson.databind.JsonDeserializer;
26-
import com.fasterxml.jackson.databind.JsonSerializer;
2722
import com.fasterxml.jackson.databind.ObjectMapper;
28-
import com.fasterxml.jackson.databind.SerializerProvider;
2923
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
3024
import com.fasterxml.jackson.databind.json.JsonMapper;
3125
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
@@ -37,97 +31,48 @@
3731
import java.io.FileOutputStream;
3832
import java.io.IOException;
3933
import java.nio.file.Path;
40-
import java.nio.file.Paths;
4134
import java.util.HashMap;
4235
import java.util.Map;
4336
import org.eclipse.jetty.io.EofException;
4437

4538
public class JacksonUtils {
4639
public static class UIMap extends HashMap<String, Object> {}
4740

48-
// Custom Path serializer that outputs just the path string without file:/ prefix
49-
public static class PathSerializer extends JsonSerializer<Path> {
50-
@Override
51-
public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers)
52-
throws IOException {
53-
if (value == null) {
54-
gen.writeNull();
55-
} else {
56-
gen.writeString(value.toString());
57-
}
58-
}
59-
}
60-
61-
// Custom Path deserializer that reads path strings
62-
public static class PathDeserializer extends JsonDeserializer<Path> {
63-
@Override
64-
public Path deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
65-
String pathString = p.getValueAsString();
66-
if (pathString == null || pathString.isEmpty()) {
67-
return null;
68-
}
69-
70-
// Handle case where old serialized data might still have file:/ prefix
71-
if (pathString.startsWith("file:/")) {
72-
pathString = pathString.substring(6); // Remove "file:/" prefix
73-
}
74-
75-
return Paths.get(pathString);
76-
}
77-
}
78-
79-
// Custom Path key deserializer for Maps with Path keys
80-
public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
81-
@Override
82-
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
83-
if (key == null || key.isEmpty()) {
84-
return null;
85-
}
86-
87-
// Handle case where old serialized data might still have file:/ prefix
88-
if (key.startsWith("file:/")) {
89-
key = key.substring(6); // Remove "file:/" prefix
90-
}
91-
92-
return Paths.get(key);
93-
}
94-
}
95-
96-
// Helper method to create ObjectMapper with Path serialization support
97-
private static ObjectMapper createObjectMapperWithPathSupport(Class<?> baseType) {
98-
PolymorphicTypeValidator ptv =
99-
BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build();
100-
101-
SimpleModule pathModule = new SimpleModule();
102-
pathModule.addSerializer(Path.class, new PathSerializer());
103-
pathModule.addDeserializer(Path.class, new PathDeserializer());
104-
pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());
105-
106-
return JsonMapper.builder()
107-
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
108-
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
109-
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
110-
.addModule(pathModule)
111-
.build();
112-
}
113-
11441
public static <T> void serialize(Path path, T object) throws IOException {
11542
serialize(path, object, true);
11643
}
11744

11845
public static <T> String serializeToString(T object) throws IOException {
119-
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
46+
PolymorphicTypeValidator ptv =
47+
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
48+
ObjectMapper objectMapper =
49+
JsonMapper.builder()
50+
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
51+
.build();
12052
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
12153
}
12254

12355
public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
124-
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
56+
PolymorphicTypeValidator ptv =
57+
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
58+
ObjectMapper objectMapper =
59+
JsonMapper.builder()
60+
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
61+
.build();
12562
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
12663
saveJsonString(json, path, forceSync);
12764
}
12865

12966
public static <T> T deserialize(Map<?, ?> s, Class<T> ref) throws IOException {
130-
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
67+
PolymorphicTypeValidator ptv =
68+
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
69+
ObjectMapper objectMapper =
70+
JsonMapper.builder()
71+
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
72+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
73+
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
74+
.build();
75+
13176
return objectMapper.convertValue(s, ref);
13277
}
13378

@@ -136,14 +81,28 @@ public static <T> T deserialize(String s, Class<T> ref) throws IOException {
13681
throw new EofException("Provided empty string for class " + ref.getName());
13782
}
13883

139-
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
140-
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
84+
PolymorphicTypeValidator ptv =
85+
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
86+
ObjectMapper objectMapper =
87+
JsonMapper.builder()
88+
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
89+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
90+
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
91+
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
92+
.build();
14193

14294
return objectMapper.readValue(s, ref);
14395
}
14496

14597
public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
146-
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
98+
PolymorphicTypeValidator ptv =
99+
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
100+
ObjectMapper objectMapper =
101+
JsonMapper.builder()
102+
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
103+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
104+
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
105+
.build();
147106
File jsonFile = new File(path.toString());
148107
if (jsonFile.exists() && jsonFile.length() > 0) {
149108
return objectMapper.readValue(jsonFile, ref);
@@ -156,12 +115,6 @@ public static <T> T deserialize(Path path, Class<T> ref, StdDeserializer<T> dese
156115
ObjectMapper objectMapper = new ObjectMapper();
157116
SimpleModule module = new SimpleModule();
158117
module.addDeserializer(ref, deserializer);
159-
160-
// Add Path support to custom deserializer case as well
161-
module.addSerializer(Path.class, new PathSerializer());
162-
module.addDeserializer(Path.class, new PathDeserializer());
163-
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());
164-
165118
objectMapper.registerModule(module);
166119

167120
File jsonFile = new File(path.toString());
@@ -182,12 +135,6 @@ public static <T> void serialize(
182135
ObjectMapper objectMapper = new ObjectMapper();
183136
SimpleModule module = new SimpleModule();
184137
module.addSerializer(ref, serializer);
185-
186-
// Add Path support to custom serializer case as well
187-
module.addSerializer(Path.class, new PathSerializer());
188-
module.addDeserializer(Path.class, new PathDeserializer());
189-
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());
190-
191138
objectMapper.registerModule(module);
192139
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
193140
saveJsonString(json, path, forceSync);

photon-server/src/main/java/org/photonvision/server/RequestHandler.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -850,33 +850,30 @@ public static void onBulkImportObjectDetectionModelRequest(Context ctx) {
850850
}
851851
}
852852

853-
private record DeleteObjectDetectionModelRequest(String modelPath) {}
853+
private record DeleteObjectDetectionModelRequest(Path modelPath) {}
854854

855855
public static void onDeleteObjectDetectionModelRequest(Context ctx) {
856856
logger.info("Deleting object detection model");
857-
Path modelPath;
858857

859858
try {
860859
DeleteObjectDetectionModelRequest request =
861860
JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);
862861

863-
modelPath = Path.of(request.modelPath.substring(7));
864-
865-
if (modelPath == null) {
862+
if (request.modelPath == null) {
866863
ctx.status(400);
867864
ctx.result("The provided model path was malformed");
868865
logger.error("The provided model path was malformed");
869866
return;
870867
}
871868

872-
if (!modelPath.toFile().exists()) {
869+
if (!request.modelPath.toFile().exists()) {
873870
ctx.status(400);
874871
ctx.result("The provided model path does not exist");
875872
logger.error("The provided model path does not exist");
876873
return;
877874
}
878875

879-
if (!modelPath.toFile().delete()) {
876+
if (!request.modelPath.toFile().delete()) {
880877
ctx.status(500);
881878
ctx.result("Unable to delete the model file");
882879
logger.error("Unable to delete the model file");
@@ -886,7 +883,7 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) {
886883
if (!ConfigManager.getInstance()
887884
.getConfig()
888885
.neuralNetworkPropertyManager()
889-
.removeModel(modelPath)) {
886+
.removeModel(request.modelPath)) {
890887
ctx.status(400);
891888
ctx.result("The model's information was not found in the config");
892889
logger.error("The model's information was not found in the config");
@@ -910,26 +907,24 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) {
910907
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
911908
}
912909

913-
private record RenameObjectDetectionModelRequest(String modelPath, String newName) {}
910+
private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}
914911

915912
public static void onRenameObjectDetectionModelRequest(Context ctx) {
916913
try {
917914
RenameObjectDetectionModelRequest request =
918915
JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);
919916

920-
Path modelPath = Path.of(request.modelPath);
921-
922-
if (modelPath == null) {
917+
if (request.modelPath == null) {
923918
ctx.status(400);
924919
ctx.result("The provided model path was malformed");
925920
logger.error("The provided model path was malformed");
926921
return;
927922
}
928923

929-
if (!modelPath.toFile().exists()) {
924+
if (!request.modelPath.toFile().exists()) {
930925
ctx.status(400);
931926
ctx.result("The provided model path does not exist");
932-
logger.error("The model path: " + modelPath + " does not exist");
927+
logger.error("The model path: " + request.modelPath + " does not exist");
933928
return;
934929
}
935930

@@ -943,7 +938,7 @@ public static void onRenameObjectDetectionModelRequest(Context ctx) {
943938
if (!ConfigManager.getInstance()
944939
.getConfig()
945940
.neuralNetworkPropertyManager()
946-
.renameModel(modelPath, request.newName)) {
941+
.renameModel(request.modelPath, request.newName)) {
947942
ctx.status(400);
948943
ctx.result("The model's information was not found in the config");
949944
logger.error("The model's information was not found in the config");

0 commit comments

Comments
 (0)