Spaces:
Running
on
L40S
Running
on
L40S
Commit
Β·
8378233
1
Parent(s):
0a5e214
avoid hash collision between users using the same image
Browse files- client/src/hooks/useMainStore.ts +10 -10
- client/src/lib/facePoke.ts +2 -2
- client/src/types.ts +3 -3
- engine.py +7 -33
- public/index.js +10 -10
client/src/hooks/useMainStore.ts
CHANGED
@@ -18,7 +18,7 @@ export type ImageState = ImageStateValues & {
|
|
18 |
setIsFollowingCursor: (isFollowingCursor: boolean) => void
|
19 |
setIsGazingAtCursor: (isGazingAtCursor: boolean) => void
|
20 |
setOriginalImage: (url: string) => void
|
21 |
-
|
22 |
setPreviewImage: (url: string) => void
|
23 |
resetImage: () => void
|
24 |
setAverageLatency: (averageLatency: number) => void
|
@@ -41,7 +41,7 @@ export const getDefaultState = (): ImageStateValues => ({
|
|
41 |
isFollowingCursor: false,
|
42 |
isGazingAtCursor: false,
|
43 |
originalImage: '',
|
44 |
-
|
45 |
previewImage: '',
|
46 |
minLatency: 20, // min time between requests
|
47 |
averageLatency: 190, // this should be the average for most people
|
@@ -98,7 +98,7 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
98 |
setIsFollowingCursor: (isFollowingCursor: boolean) => set({ isFollowingCursor }),
|
99 |
setIsGazingAtCursor: (isGazingAtCursor: boolean) => set({ isGazingAtCursor }),
|
100 |
setOriginalImage: (url) => set({ originalImage: url }),
|
101 |
-
|
102 |
setPreviewImage: (url) => set({ previewImage: url }),
|
103 |
resetImage: () => {
|
104 |
const { originalImage } = get()
|
@@ -121,11 +121,11 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
121 |
}})
|
122 |
},
|
123 |
handleServerResponse: async (params: OnServerResponseParams) => {
|
124 |
-
const { originalImage, setMetadata, setPreviewImage,
|
125 |
if (typeof params.error === "string") {
|
126 |
console.error(`handleServerResponse: failed to perform the request, resetting the app (${params.error})`)
|
127 |
setPreviewImage(originalImage)
|
128 |
-
|
129 |
} else if (typeof params.image !== "undefined") {
|
130 |
|
131 |
// this is where we decide to paste back the image as a whole,
|
@@ -141,7 +141,7 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
141 |
setPreviewImage(image);
|
142 |
} else if (typeof params.loaded !== "undefined") {
|
143 |
//console.log(`handleServerResponse: received a json`, params)
|
144 |
-
|
145 |
setMetadata({
|
146 |
center: params.loaded.c, // center - 2x1
|
147 |
size: params.loaded.s, // size - scalar
|
@@ -149,7 +149,7 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
149 |
angle: params.loaded.a, //angle - rad, counterclockwise
|
150 |
})
|
151 |
|
152 |
-
// right after we received the
|
153 |
await modifyImage({
|
154 |
landmark: {
|
155 |
group: 'background',
|
@@ -264,7 +264,7 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
264 |
|
265 |
const {
|
266 |
originalImage,
|
267 |
-
|
268 |
params: previousParams,
|
269 |
setParams,
|
270 |
setError,
|
@@ -424,8 +424,8 @@ export const useMainStore = create<ImageState>((set, get) => ({
|
|
424 |
|
425 |
try {
|
426 |
|
427 |
-
if (
|
428 |
-
facePoke.transformImage(
|
429 |
}
|
430 |
|
431 |
} catch (error) {
|
|
|
18 |
setIsFollowingCursor: (isFollowingCursor: boolean) => void
|
19 |
setIsGazingAtCursor: (isGazingAtCursor: boolean) => void
|
20 |
setOriginalImage: (url: string) => void
|
21 |
+
setOriginalImageUuid: (uuid: string) => void
|
22 |
setPreviewImage: (url: string) => void
|
23 |
resetImage: () => void
|
24 |
setAverageLatency: (averageLatency: number) => void
|
|
|
41 |
isFollowingCursor: false,
|
42 |
isGazingAtCursor: false,
|
43 |
originalImage: '',
|
44 |
+
originalImageUuid: '',
|
45 |
previewImage: '',
|
46 |
minLatency: 20, // min time between requests
|
47 |
averageLatency: 190, // this should be the average for most people
|
|
|
98 |
setIsFollowingCursor: (isFollowingCursor: boolean) => set({ isFollowingCursor }),
|
99 |
setIsGazingAtCursor: (isGazingAtCursor: boolean) => set({ isGazingAtCursor }),
|
100 |
setOriginalImage: (url) => set({ originalImage: url }),
|
101 |
+
setOriginalImageUuid: (originalImageUuid) => set({ originalImageUuid }),
|
102 |
setPreviewImage: (url) => set({ previewImage: url }),
|
103 |
resetImage: () => {
|
104 |
const { originalImage } = get()
|
|
|
121 |
}})
|
122 |
},
|
123 |
handleServerResponse: async (params: OnServerResponseParams) => {
|
124 |
+
const { originalImage, setMetadata, setPreviewImage, setOriginalImageUuid, applyModifiedHeadToCanvas, modifyImage } = useMainStore.getState();
|
125 |
if (typeof params.error === "string") {
|
126 |
console.error(`handleServerResponse: failed to perform the request, resetting the app (${params.error})`)
|
127 |
setPreviewImage(originalImage)
|
128 |
+
setOriginalImageUuid('')
|
129 |
} else if (typeof params.image !== "undefined") {
|
130 |
|
131 |
// this is where we decide to paste back the image as a whole,
|
|
|
141 |
setPreviewImage(image);
|
142 |
} else if (typeof params.loaded !== "undefined") {
|
143 |
//console.log(`handleServerResponse: received a json`, params)
|
144 |
+
setOriginalImageUuid(params.loaded.u)
|
145 |
setMetadata({
|
146 |
center: params.loaded.c, // center - 2x1
|
147 |
size: params.loaded.s, // size - scalar
|
|
|
149 |
angle: params.loaded.a, //angle - rad, counterclockwise
|
150 |
})
|
151 |
|
152 |
+
// right after we received the id, we perform a first blank request
|
153 |
await modifyImage({
|
154 |
landmark: {
|
155 |
group: 'background',
|
|
|
264 |
|
265 |
const {
|
266 |
originalImage,
|
267 |
+
originalImageUuid,
|
268 |
params: previousParams,
|
269 |
setParams,
|
270 |
setError,
|
|
|
424 |
|
425 |
try {
|
426 |
|
427 |
+
if (originalImageUuid) {
|
428 |
+
facePoke.transformImage(originalImageUuid, params);
|
429 |
}
|
430 |
|
431 |
} catch (error) {
|
client/src/lib/facePoke.ts
CHANGED
@@ -135,8 +135,8 @@ export class FacePoke {
|
|
135 |
this.sendBlobMessage(await blob.arrayBuffer());
|
136 |
}
|
137 |
|
138 |
-
public transformImage(
|
139 |
-
this.sendJsonMessage({
|
140 |
}
|
141 |
|
142 |
private sendBlobMessage(buffer: ArrayBuffer): void {
|
|
|
135 |
this.sendBlobMessage(await blob.arrayBuffer());
|
136 |
}
|
137 |
|
138 |
+
public transformImage(uuid: string, params: Partial<ImageModificationParams>): void {
|
139 |
+
this.sendJsonMessage({ uuid, params });
|
140 |
}
|
141 |
|
142 |
private sendBlobMessage(buffer: ArrayBuffer): void {
|
client/src/types.ts
CHANGED
@@ -30,7 +30,7 @@ export interface Metadata {
|
|
30 |
*/
|
31 |
export interface ModifyImageMessage {
|
32 |
image?: string;
|
33 |
-
|
34 |
params: Partial<ImageModificationParams>;
|
35 |
}
|
36 |
|
@@ -38,7 +38,7 @@ export type OnServerResponseParams = {
|
|
38 |
image?: Blob
|
39 |
error?: string
|
40 |
loaded?: {
|
41 |
-
|
42 |
} & {
|
43 |
c: number[] //center - 2x1
|
44 |
s: number // size - scalar
|
@@ -80,7 +80,7 @@ export interface ImageStateValues {
|
|
80 |
isGazingAtCursor: boolean
|
81 |
originalImage: string
|
82 |
previewImage: string
|
83 |
-
|
84 |
minLatency: number
|
85 |
averageLatency: number
|
86 |
maxLatency: number
|
|
|
30 |
*/
|
31 |
export interface ModifyImageMessage {
|
32 |
image?: string;
|
33 |
+
uuid?: string;
|
34 |
params: Partial<ImageModificationParams>;
|
35 |
}
|
36 |
|
|
|
38 |
image?: Blob
|
39 |
error?: string
|
40 |
loaded?: {
|
41 |
+
i: string
|
42 |
} & {
|
43 |
c: number[] //center - 2x1
|
44 |
s: number // size - scalar
|
|
|
80 |
isGazingAtCursor: boolean
|
81 |
originalImage: string
|
82 |
previewImage: string
|
83 |
+
originalImageUuid: string
|
84 |
minLatency: number
|
85 |
averageLatency: number
|
86 |
maxLatency: number
|
engine.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import logging
|
2 |
import hashlib
|
3 |
import os
|
@@ -61,37 +62,10 @@ class Engine:
|
|
61 |
|
62 |
logger.info("β
FacePoke Engine initialized successfully.")
|
63 |
|
64 |
-
async def get_image_hash(self, image: Union[Image.Image, str, bytes]) -> str:
|
65 |
-
"""
|
66 |
-
Compute or retrieve the hash for an image.
|
67 |
-
|
68 |
-
Args:
|
69 |
-
image (Union[Image.Image, str, bytes]): The input image, either as a PIL Image,
|
70 |
-
base64 string, or bytes.
|
71 |
-
|
72 |
-
Returns:
|
73 |
-
str: The computed hash of the image.
|
74 |
-
"""
|
75 |
-
if isinstance(image, str):
|
76 |
-
# Assume it's already a hash if it's a string of the right length
|
77 |
-
if len(image) == 32:
|
78 |
-
return image
|
79 |
-
# Otherwise, assume it's a base64 string
|
80 |
-
image = base64_data_uri_to_PIL_Image(image)
|
81 |
-
|
82 |
-
if isinstance(image, Image.Image):
|
83 |
-
return hashlib.md5(image.tobytes()).hexdigest()
|
84 |
-
elif isinstance(image, bytes):
|
85 |
-
return hashlib.md5(image).hexdigest()
|
86 |
-
else:
|
87 |
-
raise ValueError("Unsupported image type")
|
88 |
-
|
89 |
@alru_cache(maxsize=512)
|
90 |
async def load_image(self, data):
|
91 |
image = Image.open(io.BytesIO(data))
|
92 |
-
|
93 |
-
image_hash = await self.get_image_hash(image)
|
94 |
-
|
95 |
img_rgb = np.array(image)
|
96 |
|
97 |
inference_cfg = self.live_portrait.live_portrait_wrapper.cfg
|
@@ -113,13 +87,13 @@ class Engine:
|
|
113 |
'inference_cfg': inference_cfg
|
114 |
}
|
115 |
|
116 |
-
self.processed_cache[
|
117 |
|
118 |
# Calculate the bounding box
|
119 |
bbox_info = parse_bbox_from_landmark(processed_data['crop_info']['lmk_crop'], scale=1.0)
|
120 |
|
121 |
return {
|
122 |
-
'
|
123 |
|
124 |
# those aren't easy to serialize
|
125 |
'c': bbox_info['center'], # 2x1
|
@@ -129,12 +103,12 @@ class Engine:
|
|
129 |
# 'bbox_rot': bbox_info['bbox_rot'].toList(), # 4x2
|
130 |
}
|
131 |
|
132 |
-
async def transform_image(self,
|
133 |
# If we don't have the image in cache yet, add it
|
134 |
-
if
|
135 |
raise ValueError("cache miss")
|
136 |
|
137 |
-
processed_data = self.processed_cache[
|
138 |
|
139 |
try:
|
140 |
# Apply modifications based on params
|
|
|
1 |
+
import uuid
|
2 |
import logging
|
3 |
import hashlib
|
4 |
import os
|
|
|
62 |
|
63 |
logger.info("β
FacePoke Engine initialized successfully.")
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
@alru_cache(maxsize=512)
|
66 |
async def load_image(self, data):
|
67 |
image = Image.open(io.BytesIO(data))
|
68 |
+
uuid = uuid.uuid4()
|
|
|
|
|
69 |
img_rgb = np.array(image)
|
70 |
|
71 |
inference_cfg = self.live_portrait.live_portrait_wrapper.cfg
|
|
|
87 |
'inference_cfg': inference_cfg
|
88 |
}
|
89 |
|
90 |
+
self.processed_cache[uuid] = processed_data
|
91 |
|
92 |
# Calculate the bounding box
|
93 |
bbox_info = parse_bbox_from_landmark(processed_data['crop_info']['lmk_crop'], scale=1.0)
|
94 |
|
95 |
return {
|
96 |
+
'u': uuid,
|
97 |
|
98 |
# those aren't easy to serialize
|
99 |
'c': bbox_info['center'], # 2x1
|
|
|
103 |
# 'bbox_rot': bbox_info['bbox_rot'].toList(), # 4x2
|
104 |
}
|
105 |
|
106 |
+
async def transform_image(self, uuid: str, params: Dict[str, float]) -> bytes:
|
107 |
# If we don't have the image in cache yet, add it
|
108 |
+
if uuid not in self.processed_cache:
|
109 |
raise ValueError("cache miss")
|
110 |
|
111 |
+
processed_data = self.processed_cache[uuid]
|
112 |
|
113 |
try:
|
114 |
# Apply modifications based on params
|
public/index.js
CHANGED
@@ -29754,8 +29754,8 @@ class FacePoke {
|
|
29754 |
const blob = new Blob([buffer], { type: "application/octet-binary" });
|
29755 |
this.sendBlobMessage(await blob.arrayBuffer());
|
29756 |
}
|
29757 |
-
transformImage(
|
29758 |
-
this.sendJsonMessage({
|
29759 |
}
|
29760 |
sendBlobMessage(buffer) {
|
29761 |
if (!this.ws || this.ws.readyState !== WebSocketState.OPEN) {
|
@@ -29888,7 +29888,7 @@ var getDefaultState = () => ({
|
|
29888 |
isFollowingCursor: false,
|
29889 |
isGazingAtCursor: false,
|
29890 |
originalImage: "",
|
29891 |
-
|
29892 |
previewImage: "",
|
29893 |
minLatency: 20,
|
29894 |
averageLatency: 190,
|
@@ -29943,7 +29943,7 @@ var useMainStore = create((set, get) => ({
|
|
29943 |
setIsFollowingCursor: (isFollowingCursor) => set({ isFollowingCursor }),
|
29944 |
setIsGazingAtCursor: (isGazingAtCursor) => set({ isGazingAtCursor }),
|
29945 |
setOriginalImage: (url) => set({ originalImage: url }),
|
29946 |
-
|
29947 |
setPreviewImage: (url) => set({ previewImage: url }),
|
29948 |
resetImage: () => {
|
29949 |
const { originalImage } = get();
|
@@ -29966,16 +29966,16 @@ var useMainStore = create((set, get) => ({
|
|
29966 |
} });
|
29967 |
},
|
29968 |
handleServerResponse: async (params) => {
|
29969 |
-
const { originalImage, setMetadata, setPreviewImage,
|
29970 |
if (typeof params.error === "string") {
|
29971 |
console.error(`handleServerResponse: failed to perform the request, resetting the app (${params.error})`);
|
29972 |
setPreviewImage(originalImage);
|
29973 |
-
|
29974 |
} else if (typeof params.image !== "undefined") {
|
29975 |
const image = await convertImageToBase64(params.image);
|
29976 |
setPreviewImage(image);
|
29977 |
} else if (typeof params.loaded !== "undefined") {
|
29978 |
-
|
29979 |
setMetadata({
|
29980 |
center: params.loaded.c,
|
29981 |
size: params.loaded.s,
|
@@ -30041,7 +30041,7 @@ var useMainStore = create((set, get) => ({
|
|
30041 |
modifyImage: async ({ landmark, vector, mode }) => {
|
30042 |
const {
|
30043 |
originalImage,
|
30044 |
-
|
30045 |
params: previousParams,
|
30046 |
setParams,
|
30047 |
setError,
|
@@ -30139,8 +30139,8 @@ var useMainStore = create((set, get) => ({
|
|
30139 |
}
|
30140 |
setParams(params);
|
30141 |
try {
|
30142 |
-
if (
|
30143 |
-
facePoke.transformImage(
|
30144 |
}
|
30145 |
} catch (error) {
|
30146 |
setError("Failed to modify image");
|
|
|
29754 |
const blob = new Blob([buffer], { type: "application/octet-binary" });
|
29755 |
this.sendBlobMessage(await blob.arrayBuffer());
|
29756 |
}
|
29757 |
+
transformImage(uuid, params) {
|
29758 |
+
this.sendJsonMessage({ uuid, params });
|
29759 |
}
|
29760 |
sendBlobMessage(buffer) {
|
29761 |
if (!this.ws || this.ws.readyState !== WebSocketState.OPEN) {
|
|
|
29888 |
isFollowingCursor: false,
|
29889 |
isGazingAtCursor: false,
|
29890 |
originalImage: "",
|
29891 |
+
originalImageUuid: "",
|
29892 |
previewImage: "",
|
29893 |
minLatency: 20,
|
29894 |
averageLatency: 190,
|
|
|
29943 |
setIsFollowingCursor: (isFollowingCursor) => set({ isFollowingCursor }),
|
29944 |
setIsGazingAtCursor: (isGazingAtCursor) => set({ isGazingAtCursor }),
|
29945 |
setOriginalImage: (url) => set({ originalImage: url }),
|
29946 |
+
setOriginalImageUuid: (originalImageUuid) => set({ originalImageUuid }),
|
29947 |
setPreviewImage: (url) => set({ previewImage: url }),
|
29948 |
resetImage: () => {
|
29949 |
const { originalImage } = get();
|
|
|
29966 |
} });
|
29967 |
},
|
29968 |
handleServerResponse: async (params) => {
|
29969 |
+
const { originalImage, setMetadata, setPreviewImage, setOriginalImageUuid, applyModifiedHeadToCanvas, modifyImage } = useMainStore.getState();
|
29970 |
if (typeof params.error === "string") {
|
29971 |
console.error(`handleServerResponse: failed to perform the request, resetting the app (${params.error})`);
|
29972 |
setPreviewImage(originalImage);
|
29973 |
+
setOriginalImageUuid("");
|
29974 |
} else if (typeof params.image !== "undefined") {
|
29975 |
const image = await convertImageToBase64(params.image);
|
29976 |
setPreviewImage(image);
|
29977 |
} else if (typeof params.loaded !== "undefined") {
|
29978 |
+
setOriginalImageUuid(params.loaded.u);
|
29979 |
setMetadata({
|
29980 |
center: params.loaded.c,
|
29981 |
size: params.loaded.s,
|
|
|
30041 |
modifyImage: async ({ landmark, vector, mode }) => {
|
30042 |
const {
|
30043 |
originalImage,
|
30044 |
+
originalImageUuid,
|
30045 |
params: previousParams,
|
30046 |
setParams,
|
30047 |
setError,
|
|
|
30139 |
}
|
30140 |
setParams(params);
|
30141 |
try {
|
30142 |
+
if (originalImageUuid) {
|
30143 |
+
facePoke.transformImage(originalImageUuid, params);
|
30144 |
}
|
30145 |
} catch (error) {
|
30146 |
setError("Failed to modify image");
|