From 873604ca6dfb823d3eb87b52f21bd7446335c0f5 Mon Sep 17 00:00:00 2001 From: Valentin Porcellini Date: Fri, 12 Jul 2024 15:20:42 +0200 Subject: [PATCH 01/12] Added NDD feature + refactoring --- package-lock.json | 76 ++- package.json | 1 + .../NavItems/tools/Alltools/ToolsMenu.jsx | 3 +- .../SyntheticImageDetection/NddDatagrid.jsx | 176 ++++++ .../SyntheticImageDetectionAlgorithms.jsx | 101 +++ .../tools/SyntheticImageDetection/index.jsx | 47 +- .../syntheticImageDetectionResults.jsx | 592 ++++++++++++------ src/components/SideMenu/index.jsx | 2 +- src/constants/roles.jsx | 11 + src/constants/tools.jsx | 12 +- .../tools/syntheticImageDetectionActions.jsx | 4 + .../tools/syntheticImageDetectionReducer.jsx | 8 + 12 files changed, 800 insertions(+), 233 deletions(-) create mode 100644 src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx create mode 100644 src/components/NavItems/tools/SyntheticImageDetection/SyntheticImageDetectionAlgorithms.jsx create mode 100644 src/constants/roles.jsx diff --git a/package-lock.json b/package-lock.json index f4da796a9..b461ffe9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@mui/icons-material": "^5.14.12", "@mui/lab": "^5.0.0-alpha.147", "@mui/material": "^5.14.12", + "@mui/x-data-grid": "^7.9.0", "@mui/x-date-pickers": "^6.16.1", "@reduxjs/toolkit": "^1.9.7", "@types/chrome": "^0.0.266", @@ -1912,9 +1913,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3010,12 +3011,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", - "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.0.tgz", + "integrity": "sha512-sYpubkO1MZOnxNyVOClrPNOTs0MfuRVVnAvCeMaOaXt6GimgQbnUcshYv2pSr6PFj+Mqzdff/FYOBceK8u5QgA==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.14", + "@mui/utils": "^5.16.0", "prop-types": "^15.8.1" }, "engines": { @@ -3067,15 +3068,15 @@ } }, "node_modules/@mui/system": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", - "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.0.tgz", + "integrity": "sha512-9YbkC2m3+pNumAvubYv+ijLtog6puJ0fJ6rYfzfLCM47pWrw3m+30nXNM8zMgDaKL6vpfWJcCXm+LPaWBpy7sw==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.14", + "@mui/private-theming": "^5.16.0", "@mui/styled-engine": "^5.15.14", "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", + "@mui/utils": "^5.16.0", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3119,9 +3120,9 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", - "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.0.tgz", + "integrity": "sha512-kLLi5J1xY+mwtUlMb8Ubdxf4qFAA1+U7WPBvjM/qQ4CIwLCohNb0sHo1oYPufjSIH/Z9+dhVxD7dJlfGjd1AVA==", "dependencies": { "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", @@ -3145,6 +3146,32 @@ } } }, + "node_modules/@mui/x-data-grid": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.9.0.tgz", + "integrity": "sha512-RkrVD+tfcR/h3j2p2uqohxA00C5tCJIV5gb5+2ap8XdM0Y8XMF81bB8UADWenU5W83UTErWvtU7n4gCl7hJO9g==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@mui/system": "^5.16.0", + "@mui/utils": "^5.16.0", + "@mui/x-internals": "7.9.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.15.14", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@mui/x-date-pickers": { "version": "6.19.9", "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.19.9.tgz", @@ -3210,6 +3237,25 @@ } } }, + "node_modules/@mui/x-internals": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.9.0.tgz", + "integrity": "sha512-RJRrM6moaDZ8S11gDt8OKVclKm2v9khpIyLkpenNze+tT4dQYoU3liW5P2t31hA4Na/T6JQKNosB4qmB2TYfZw==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@mui/utils": "^5.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index b58cafab7..1f65a0300 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@mui/icons-material": "^5.14.12", "@mui/lab": "^5.0.0-alpha.147", "@mui/material": "^5.14.12", + "@mui/x-data-grid": "^7.9.0", "@mui/x-date-pickers": "^6.16.1", "@reduxjs/toolkit": "^1.9.7", "@types/chrome": "^0.0.266", diff --git a/src/components/NavItems/tools/Alltools/ToolsMenu.jsx b/src/components/NavItems/tools/Alltools/ToolsMenu.jsx index c45e21b59..37da4c8fe 100644 --- a/src/components/NavItems/tools/Alltools/ToolsMenu.jsx +++ b/src/components/NavItems/tools/Alltools/ToolsMenu.jsx @@ -30,7 +30,8 @@ import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; import { Audiotrack } from "@mui/icons-material"; -import { ROLES, tools, TOOLS_CATEGORIES } from "../../../../constants/tools"; +import { tools, TOOLS_CATEGORIES } from "../../../../constants/tools"; +import { ROLES } from "../../../../constants/roles"; function TabPanel(props) { const { children, value, index, ...other } = props; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx new file mode 100644 index 000000000..6c05057b8 --- /dev/null +++ b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx @@ -0,0 +1,176 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import { DataGrid, useGridApiRef } from "@mui/x-data-grid"; +import Link from "@mui/material/Link"; +import { Chip, Grid, Stack, Typography } from "@mui/material"; +import { + getAlertColor, + getAlertLabel, + getPercentageColorCode, +} from "./syntheticImageDetectionResults"; +import { i18nLoadNamespace } from "../../../Shared/Languages/i18nLoadNamespace"; +import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; + +const NddDataGrid = ({ rows }) => { + const keyword = i18nLoadNamespace( + "components/NavItems/tools/SyntheticImageDetection", + ); + + const apiRef = useGridApiRef(); + // const [isLoading, setIsLoading] = useState(false); + + // const resizeData = useCallback(() => { + // setIsLoading(true); + // + // if (!rows) return; + // + // apiRef.current + // .autosizeColumns({ + // includeHeaders: true, + // includeOutliers: true, + // }) + // .then(() => { + // if (rows) setIsLoading(false); + // }); + // }, [apiRef, rows]); + // + // useEffect(() => { + // resizeData(); + // }, [resizeData]); + + const detectionRateStack = (row) => { + const detectionPercentageNdImage = row.detectionRate1; + + const algorithm = syntheticImageDetectionAlgorithms.find( + (algo) => (algo.apiServiceName = row.algorithmName), + ); + + return ( + + + {keyword(algorithm.name)} + + + <> + + {keyword("synthetic_image_detection_probability_text")}{" "} + + + {detectionPercentageNdImage}% + + + + + + ); + }; + + const imageUrlsCell = (urls) => { + if (!urls || !Array.isArray(urls) || urls.length === 0) return <>; + + return ( + + {urls.map((url, index) => ( + + + {`#${index + 1}`} + + + ))} + + ); + }; + + const columns = [ + { + field: "id", + headerName: "ID", + width: 10, + }, + { + field: "image", + headerName: "Image", + minWidth: 150, + // flex: 1, + + renderCell: (params) => ( + + ), + }, + { + field: "detectionRate1", + headerName: "Detection Rate #1", + type: "number", + // minWidth: 300, + flex: 1, + renderCell: (params) => detectionRateStack(params.row), + }, + { + field: "archiveUrl", + headerName: "Archive URL", + // width: 150, + renderCell: (params) => ( + + {params.value} + + ), + }, + { + field: "imageUrls", + headerName: "Image URLs", + flex: 1, + renderCell: (params) => imageUrlsCell(params.value), + }, + ]; + + return ( + + "auto"} + rows={rows} + columns={columns} + initialState={{ + pagination: { + paginationModel: { + pageSize: 5, + }, + }, + }} + autosizeOptions={{ + includeHeaders: true, + includeOutliers: true, + columns: ["image", "detectionRate1"], + expand: true, + }} + pageSizeOptions={[5]} + disableRowSelectionOnClick + /> + + ); +}; + +export default NddDataGrid; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/SyntheticImageDetectionAlgorithms.jsx b/src/components/NavItems/tools/SyntheticImageDetection/SyntheticImageDetectionAlgorithms.jsx new file mode 100644 index 000000000..98a790fd0 --- /dev/null +++ b/src/components/NavItems/tools/SyntheticImageDetection/SyntheticImageDetectionAlgorithms.jsx @@ -0,0 +1,101 @@ +import { ROLES } from "../../../../constants/roles"; + +/** + * @file Provides constants and helper functions used for the synthetic image detection tool + * + */ + +/** + * Thresholds (percentage) for the different analysis categories + * @type {{THRESHOLD_2: number, THRESHOLD_3: number, THRESHOLD_1: number}} + */ +export const DETECTION_THRESHOLDS = { + THRESHOLD_1: 50, + THRESHOLD_2: 70, + THRESHOLD_3: 90, +}; + +export class SyntheticImageDetectionAlgorithm { + /** + * + * @param apiServiceName {string} The service parameter for the API call + * @param name {string} The algorithm name key + * @param description {string} The algorithm description key + * @param roleNeeded {?Roles} Role needed to get the detection results for the algorithm + */ + constructor(apiServiceName, name, description, roleNeeded) { + this.apiServiceName = apiServiceName; + this.name = name; + this.description = description; + this.roleNeeded = roleNeeded; + } +} + +export const ganR50Mever = new SyntheticImageDetectionAlgorithm( + "gan_r50_mever", + "synthetic_image_detection_gan_name", + "synthetic_image_detection_gan_description", + ROLES.BETA_TESTER ?? null, +); + +export const ldmR50Grip = new SyntheticImageDetectionAlgorithm( + "ldm_r50_grip", + "synthetic_image_detection_diffusion_name", + "synthetic_image_detection_diffusion_description", + ROLES.BETA_TESTER, +); +export const proGanR50Grip = new SyntheticImageDetectionAlgorithm( + "progan_r50_grip", + "synthetic_image_detection_progan_name", + "synthetic_image_detection_progan_description", + ROLES.BETA_TESTER, +); +export const admR50Grip = new SyntheticImageDetectionAlgorithm( + "adm_r50_grip", + "synthetic_image_detection_adm_name", + "synthetic_image_detection_adm_description", + ROLES.EXTRA_FEATURE, +); +export const proGanRineMever = new SyntheticImageDetectionAlgorithm( + "progan_rine_mever", + "synthetic_image_detection_progan_rine_mever_name", + "synthetic_image_detection_progan_rine_mever_description", + ROLES.EXTRA_FEATURE, +); +export const ldmRineMever = new SyntheticImageDetectionAlgorithm( + "ldm_rine_mever", + "synthetic_image_detection_ldm_rine_mever_name", + "synthetic_image_detection_ldm_rine_mever_description", + ROLES.EXTRA_FEATURE, +); +export const ldmR50Mever = new SyntheticImageDetectionAlgorithm( + "ldm_r50_mever", + "synthetic_image_detection_ldm_rine_mever_name", + "synthetic_image_detection_ldm_rine_mever_description", + ROLES.EXTRA_FEATURE, +); + +/** + * The list of the synthetic image detection algorithms + * @type {SyntheticImageDetectionAlgorithm[]} + */ +export const syntheticImageDetectionAlgorithms = [ + ganR50Mever, + ldmR50Grip, + proGanR50Grip, + admR50Grip, + proGanRineMever, + ldmRineMever, + ldmR50Mever, +]; + +/** + * Returns a list of algorithms that can be used by a user according to their roles + * @param roles {?Array} + * @returns {SyntheticImageDetectionAlgorithm[]} + */ +export const getSyntheticImageDetectionAlgorithmsForRoles = (roles) => { + return syntheticImageDetectionAlgorithms.filter((algorithm) => + roles.includes(algorithm.roleNeeded), + ); +}; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index b5796b00e..02cf90871 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from "react-redux"; import { resetSyntheticImageDetectionImage, setSyntheticImageDetectionLoading, + setSyntheticImageDetectionNearDuplicates, setSyntheticImageDetectionResult, } from "../../../../redux/actions/tools/syntheticImageDetectionActions"; @@ -28,6 +29,7 @@ import SyntheticImageDetectionResults from "./syntheticImageDetectionResults"; import { setError } from "redux/reducers/errorReducer"; import StringFileUploadField from "../../../Shared/StringFileUploadField"; import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils"; +import { getSyntheticImageDetectionAlgorithmsForRoles } from "./SyntheticImageDetectionAlgorithms"; const SyntheticImageDetection = () => { const classes = useMyStyles(); @@ -46,6 +48,7 @@ const SyntheticImageDetection = () => { ); const result = useSelector((state) => state.syntheticImageDetection.result); const url = useSelector((state) => state.syntheticImageDetection.url); + const nd = useSelector((state) => state.syntheticImageDetection.duplicates); const [input, setInput] = useState(url ? url : ""); const [imageFile, setImageFile] = useState(undefined); @@ -69,8 +72,10 @@ const SyntheticImageDetection = () => { dispatch(setSyntheticImageDetectionLoading(true)); const modeURL = "images/"; - const services = - "gan_r50_mever,ldm_r50_grip,progan_r50_grip,adm_r50_grip,ldm_r50_mever,progan_rine_mever,ldm_rine_mever"; + + const services = getSyntheticImageDetectionAlgorithmsForRoles(role) + .map((algorithm) => algorithm.apiServiceName) + .join(","); const baseURL = process.env.REACT_APP_CAA_DEEPFAKE_URL; @@ -108,7 +113,7 @@ const SyntheticImageDetection = () => { bodyFormData.append("file", image); res = await axios.post(baseURL + modeURL + "jobs", bodyFormData, { method: "post", - params: { services: services }, + params: { services: services, search_similar: true }, headers: { "Content-Type": "multipart/form-data", }, @@ -117,7 +122,7 @@ const SyntheticImageDetection = () => { default: res = await axios.post(baseURL + modeURL + "jobs", null, { - params: { url: url, services: services }, + params: { url: url, services: services, search_similar: true }, }); break; } @@ -136,13 +141,42 @@ const SyntheticImageDetection = () => { handleError("error_" + error.status); } - if (response && response.data != null) + if (response && response.data != null) { + console.log(response.data); dispatch( setSyntheticImageDetectionResult({ url: image ? URL.createObjectURL(image) : url, result: response.data, }), ); + } + + if ( + response && + response.data && + response.data.similar_images && + response.data.similar_images.completed + ) { + let imgSimilarRes; + + try { + imgSimilarRes = await axios.get(baseURL + modeURL + "similar/" + id); + } catch (error) { + handleError("error_" + error.status); + } + + console.log(imgSimilarRes.data); + + if ( + !imgSimilarRes.data || + !imgSimilarRes.data.similar_media || + imgSimilarRes.data.similar_media.length === 0 + ) { + dispatch(setSyntheticImageDetectionNearDuplicates(null)); + } + + dispatch(setSyntheticImageDetectionNearDuplicates(imgSimilarRes.data)); + } }; const waitUntilFinish = async (id) => { @@ -278,9 +312,10 @@ const SyntheticImageDetection = () => { {result && ( )} diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index f965dff76..78123a861 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -6,6 +6,7 @@ import { Alert, Box, Card, + CardContent, CardHeader, Chip, Divider, @@ -24,58 +25,98 @@ import GaugeChart from "react-gauge-chart"; import Tooltip from "@mui/material/Tooltip"; import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils"; import GaugeChartModalExplanation from "../../../Shared/GaugeChartModalExplanation"; +import NddDatagrid from "./NddDatagrid"; +import { + admR50Grip, + DETECTION_THRESHOLDS, + ganR50Mever, + ldmR50Grip, + ldmRineMever, + proGanR50Grip, + proGanRineMever, + SyntheticImageDetectionAlgorithm, +} from "./SyntheticImageDetectionAlgorithms"; + +/** + * Returns the alert color code for the given percentage n + * @param n {number} + * @returns {"error" | "warning" | "success"} + */ +export const getAlertColor = (n) => { + if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { + return "error"; + } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { + return "warning"; + } else { + return "success"; + } +}; + +export const getAlertLabel = (n, keyword) => { + if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { + return keyword("synthetic_image_detection_alert_label_4"); + } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { + return keyword("synthetic_image_detection_alert_label_3"); + } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_1) { + return keyword("synthetic_image_detection_alert_label_2"); + } else { + return keyword("synthetic_image_detection_alert_label_1"); + } +}; -const SyntheticImageDetectionResults = (props) => { +export const getPercentageColorCode = (n) => { + if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { + return "#FF0000"; + } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { + return "#FFAA00"; + } else { + return "green"; + } +}; + +class NddResult { + /** + * + * @param id {number} + * @param image {string} + * @param archiveUrl {string} + * @param imageUrls {string} + * @param detectionRate1 {number} + * @param algorithmName {string} + */ + constructor(id, image, archiveUrl, imageUrls, detectionRate1, algorithmName) { + this.id = id; + this.image = image; + this.archiveUrl = archiveUrl; + this.imageUrls = imageUrls; + this.detectionRate1 = detectionRate1; + this.algorithmName = algorithmName; + } +} + +const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { const keyword = i18nLoadNamespace( "components/NavItems/tools/SyntheticImageDetection", ); - class SyntheticImageDetectionAlgorithmResult { + class SyntheticImageDetectionAlgorithmResult extends SyntheticImageDetectionAlgorithm { /** * - * @param methodName {string} + * @param syntheticImageDetectionAlgorithm {SyntheticImageDetectionAlgorithm} * @param predictionScore {number} * @param isError {boolean} */ - constructor(methodName, predictionScore, isError) { - (this.methodName = methodName), - (this.predictionScore = predictionScore), - (this.isError = isError); + constructor(syntheticImageDetectionAlgorithm, predictionScore, isError) { + super( + syntheticImageDetectionAlgorithm.apiServiceName, + syntheticImageDetectionAlgorithm.name, + syntheticImageDetectionAlgorithm.description, + syntheticImageDetectionAlgorithm.roleNeeded, + ); + (this.predictionScore = predictionScore), (this.isError = isError); } } - const DeepfakeImageDetectionMethodNames = { - gan: { - name: keyword("synthetic_image_detection_gan_name"), - description: keyword("synthetic_image_detection_gan_description"), - }, - diffusion: { - name: keyword("synthetic_image_detection_diffusion_name"), - description: keyword("synthetic_image_detection_diffusion_description"), - }, - progan_r50_grip: { - name: keyword("synthetic_image_detection_progan_name"), - description: keyword("synthetic_image_detection_progan_description"), - }, - adm_r50_grip: { - name: keyword("synthetic_image_detection_adm_name"), - description: keyword("synthetic_image_detection_adm_description"), - }, - progan_rine_mever: { - name: keyword("synthetic_image_detection_progan_rine_mever_name"), - description: keyword( - "synthetic_image_detection_progan_rine_mever_description", - ), - }, - ldm_rine_mever: { - name: keyword("synthetic_image_detection_ldm_rine_mever_name"), - description: keyword( - "synthetic_image_detection_ldm_rine_mever_description", - ), - }, - }; - const results = props.result; - const url = props.url; const imgElement = React.useRef(null); const imgContainerRef = useRef(null); @@ -90,68 +131,81 @@ const SyntheticImageDetectionResults = (props) => { useEffect(() => { setResultsHaveErrors(false); - - const diffusionScore = new SyntheticImageDetectionAlgorithmResult( - //previously unina_report - Object.keys(DeepfakeImageDetectionMethodNames)[1], - !results.ldm_r50_grip_report.prediction - ? 0 - : results.ldm_r50_grip_report.prediction * 100, - !results.ldm_r50_grip_report.prediction, - ); - const ganScore = new SyntheticImageDetectionAlgorithmResult( - //previously gan_report - Object.keys(DeepfakeImageDetectionMethodNames)[0], - !results.gan_r50_mever_report.prediction - ? 0 - : results.gan_r50_mever_report.prediction * 100, - !results.gan_r50_mever_report.prediction, - ); - - const proganScore = new SyntheticImageDetectionAlgorithmResult( - Object.keys(DeepfakeImageDetectionMethodNames)[2], - !results.progan_r50_grip_report.prediction - ? 0 - : results.progan_r50_grip_report.prediction * 100, - !results.progan_r50_grip_report.prediction, - ); - - const admScore = new SyntheticImageDetectionAlgorithmResult( - Object.keys(DeepfakeImageDetectionMethodNames)[3], - !results.adm_r50_grip_report.prediction - ? 0 - : results.adm_r50_grip_report.prediction * 100, - !results.adm_r50_grip_report.prediction, - ); - - const proganRineScore = new SyntheticImageDetectionAlgorithmResult( - Object.keys(DeepfakeImageDetectionMethodNames)[4], - !results.progan_rine_mever_report.prediction - ? 0 - : results.progan_rine_mever_report.prediction * 100, - !results.progan_rine_mever_report.prediction, - ); - - const ldmRineScore = new SyntheticImageDetectionAlgorithmResult( - Object.keys(DeepfakeImageDetectionMethodNames)[5], - !results.ldm_rine_mever_report.prediction - ? 0 - : results.ldm_rine_mever_report.prediction * 100, - !results.ldm_rine_mever_report.prediction, - ); - - const res = ( - role.includes("EXTRA_FEATURE") - ? [ - diffusionScore, - ganScore, - proganScore, - admScore, - proganRineScore, - ldmRineScore, - ] - : [diffusionScore, ganScore, proganScore] - ).sort((a, b) => b.predictionScore - a.predictionScore); + let diffusionScore; + + if (results.ldm_r50_grip_report) + diffusionScore = new SyntheticImageDetectionAlgorithmResult( + //previously unina_report + ldmR50Grip, + !results.ldm_r50_grip_report.prediction + ? 0 + : results.ldm_r50_grip_report.prediction * 100, + !results.ldm_r50_grip_report.prediction, + ); + + let ganScore; + + if (results.gan_r50_mever_report) + ganScore = new SyntheticImageDetectionAlgorithmResult( + //previously gan_report + ganR50Mever, + !results.gan_r50_mever_report.prediction + ? 0 + : results.gan_r50_mever_report.prediction * 100, + !results.gan_r50_mever_report.prediction, + ); + + let proganScore; + + if (results.progan_r50_grip_report) + proganScore = new SyntheticImageDetectionAlgorithmResult( + proGanR50Grip, + !results.progan_r50_grip_report.prediction + ? 0 + : results.progan_r50_grip_report.prediction * 100, + !results.progan_r50_grip_report.prediction, + ); + + let admScore; + if (results.adm_r50_grip_report) + admScore = new SyntheticImageDetectionAlgorithmResult( + admR50Grip, + !results.adm_r50_grip_report.prediction + ? 0 + : results.adm_r50_grip_report.prediction * 100, + !results.adm_r50_grip_report.prediction, + ); + + let proganRineScore; + if (results.progan_rine_mever_report) + proganRineScore = new SyntheticImageDetectionAlgorithmResult( + proGanRineMever, + !results.progan_rine_mever_report.prediction + ? 0 + : results.progan_rine_mever_report.prediction * 100, + !results.progan_rine_mever_report.prediction, + ); + + let ldmRineScore; + if (results.ldm_rine_mever_report) + ldmRineScore = new SyntheticImageDetectionAlgorithmResult( + ldmRineMever, + !results.ldm_rine_mever_report.prediction + ? 0 + : results.ldm_rine_mever_report.prediction * 100, + !results.ldm_rine_mever_report.prediction, + ); + + const res = [ + diffusionScore, + ganScore, + proganScore, + admScore, + proganRineScore, + ldmRineScore, + ] + .filter((i) => i !== undefined) + .sort((a, b) => b.predictionScore - a.predictionScore); const hasResultError = () => { for (const algorithm of res) { @@ -176,7 +230,6 @@ const SyntheticImageDetectionResults = (props) => { const client_id = getclientId(); const session = useSelector((state) => state.userSession); - const role = useSelector((state) => state.userSession.user.roles); const uid = session && session.user ? session.user.id : null; useTrackEvent( @@ -189,52 +242,6 @@ const SyntheticImageDetectionResults = (props) => { uid, ); - const handleClose = () => { - props.handleClose(); - }; - const DETECTION_THRESHOLDS = { - THRESHOLD_1: 50, - THRESHOLD_2: 70, - THRESHOLD_3: 90, - }; - - const getPercentageColorCode = (n) => { - if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { - return "#FF0000"; - } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { - return "#FFAA00"; - } else { - return "green"; - } - }; - - /** - * Returns the alert color code for the given percentage n - * @param n {number} - * @returns {"error" | "warning" | "success"} - */ - const getAlertColor = (n) => { - if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { - return "error"; - } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { - return "warning"; - } else { - return "success"; - } - }; - - const getAlertLabel = (n) => { - if (n >= DETECTION_THRESHOLDS.THRESHOLD_3) { - return keyword("synthetic_image_detection_alert_label_4"); - } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_2) { - return keyword("synthetic_image_detection_alert_label_3"); - } else if (n >= DETECTION_THRESHOLDS.THRESHOLD_1) { - return keyword("synthetic_image_detection_alert_label_2"); - } else { - return keyword("synthetic_image_detection_alert_label_1"); - } - }; - /** * Returns a percentage between 0 and 99 for display purposes. We exclude 0 and 100 values. * @param percentage {number} @@ -255,6 +262,20 @@ const SyntheticImageDetectionResults = (props) => { ); }; + const [nddDetailsPanelMessage, setNddDetailsPanelMessage] = useState( + "synthetic_image_detection_ndd_additional_results_hide", + ); + const handleNddDetailsChange = () => { + nddDetailsPanelMessage === + "synthetic_image_detection_ndd_additional_results_hide" + ? setNddDetailsPanelMessage( + "synthetic_image_detection_ndd_additional_results", + ) + : setNddDetailsPanelMessage( + "synthetic_image_detection_ndd_additional_results_hide", + ); + }; + const keywords = [ "synthetic_image_detection_scale_modal_explanation_rating_1", "synthetic_image_detection_scale_modal_explanation_rating_2", @@ -263,52 +284,90 @@ const SyntheticImageDetectionResults = (props) => { ]; const colors = ["#00FF00", "#AAFF03", "#FFA903", "#FF0000"]; + const getNddRows = (nddResults) => { + let rows = []; + for (let i = 0; i < nddResults.length; i += 1) { + const res = nddResults[i]; + rows.push( + new NddResult( + i + 1, + res.archive_url, + res.archive_url, + res.origin_urls, + sanitizeDetectionPercentage( + res.detections[Object.keys(res.detections)[0]] * 100, + ), + Object.keys(res.detections)[0], + ), + ); + } + return rows; + }; + + if (nd) { + console.log(getNddRows(nd.similar_media)); + } + return ( - - - - - - } - /> + + + + + } + /> + - - - - {"Displays + + + - - + > + {"Displays + + + {syntheticImageScores.length > 0 ? ( @@ -434,6 +493,16 @@ const SyntheticImageDetectionResults = (props) => { toolName={"SyntheticImageDetection"} thresholds={DETECTION_THRESHOLDS} /> + + {nd && nd.similar_media && nd.similar_media.length > 0 && ( + + + Similar images were detected as synthetic. See detection + details for similar images below. + + + )} + {resultsHaveErrors && ( {keyword("synthetic_image_detection_algorithms_errors")} @@ -468,11 +537,7 @@ const SyntheticImageDetectionResults = (props) => { variant={"h6"} sx={{ fontWeight: "bold" }} > - { - DeepfakeImageDetectionMethodNames[ - item.methodName - ].name - } + {keyword(item.name)} { {!item.isError && ( )} @@ -517,7 +585,7 @@ const SyntheticImageDetectionResults = (props) => { {/* { mb={2} > - { - DeepfakeImageDetectionMethodNames[ - item.methodName - ].description - } + {keyword(item.description)} @@ -570,9 +634,139 @@ const SyntheticImageDetectionResults = (props) => { )} + + {nd && nd.similar_media && nd.similar_media.length > 0 && ( + + + }> + {keyword(nddDetailsPanelMessage)} + + + + + {/*{nd.similar_media.map((similarMedia, key) => (*/} + {/* */} + {/* {similarMedia &&*/} + {/* similarMedia.detections &&*/} + {/* Object.keys(similarMedia.detections).map(*/} + {/* (detectionAlgorithm, i) => {*/} + {/* const detectionPercentageNdImage =*/} + {/* sanitizeDetectionPercentage(*/} + {/* similarMedia.detections[*/} + {/* detectionAlgorithm*/} + {/* ] * 100,*/} + {/* );*/} + {/* return (*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* {*/} + {/* deepfakeImageDetectionMethodNames(keyword)[*/} + {/* detectionAlgorithm*/} + {/* ].name*/} + {/* }*/} + {/* */} + {/* */} + {/* */} + {/* <>*/} + {/* */} + {/* {keyword(*/} + {/* "synthetic_image_detection_probability_text",*/} + {/* )}{" "}*/} + {/* */} + {/* */} + {/* {detectionPercentageNdImage}%*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* );*/} + {/* },*/} + {/* )}*/} + {/* */} + {/* */} + {/* {"Links:"}*/} + {/* */} + + {/* {similarMedia.origin_urls &&*/} + {/* similarMedia.origin_urls.length > 0 &&*/} + {/* similarMedia.origin_urls.map(*/} + {/* (originUrl, index) => (*/} + {/* */} + {/* */} + {/* {`#${index + 1}`}*/} + {/* */} + {/* */} + {/* ),*/} + {/* )}*/} + {/* */} + {/* */} + {/* {"Archive"}*/} + {/* */} + {/* */} + {/* */} + {/*))}*/} + + + + {/**/} + + )} + - - + + ); }; diff --git a/src/components/SideMenu/index.jsx b/src/components/SideMenu/index.jsx index 21b31187d..5dc2b8967 100644 --- a/src/components/SideMenu/index.jsx +++ b/src/components/SideMenu/index.jsx @@ -31,11 +31,11 @@ import ImageIcon from "../NavBar/images/SVG/Image/Images.svg"; import SearchIcon from "../NavBar/images/SVG/Search/Search.svg"; import DataIcon from "../NavBar/images/SVG/DataAnalysis/Data_analysis.svg"; import { - ROLES, TOOL_GROUPS, TOOL_STATUS_ICON, TOOLS_CATEGORIES, } from "../../constants/tools"; +import { ROLES } from "../../constants/roles"; import { selectTopMenuItem } from "../../redux/reducers/navReducer"; import { TOP_MENU_ITEMS } from "../../constants/topMenuItems"; import { selectTool } from "../../redux/reducers/tools/toolReducer"; diff --git a/src/constants/roles.jsx b/src/constants/roles.jsx new file mode 100644 index 000000000..c3dc42f18 --- /dev/null +++ b/src/constants/roles.jsx @@ -0,0 +1,11 @@ +/** + * Represents the user roles that can be needed to access a given topMenuItem + * @typedef Roles + * @type {{BETA_TESTER: string, ARCHIVE: string, LOCK: string}} + */ +export const ROLES = { + ARCHIVE: "ARCHIVE", + BETA_TESTER: "BETA_TESTER", + LOCK: "lock", + EXTRA_FEATURE: "EXTRA_FEATURE", +}; diff --git a/src/constants/tools.jsx b/src/constants/tools.jsx index 19d6ad90f..efb0c46dd 100644 --- a/src/constants/tools.jsx +++ b/src/constants/tools.jsx @@ -53,6 +53,7 @@ import SemanticSearch from "../components/NavItems/tools/SemanticSearch"; import TwitterSna from "../components/NavItems/tools/TwitterSna/TwitterSna"; import Archive from "../components/NavItems/tools/Archive"; import About from "../components/NavItems/About/About"; +import { ROLES } from "./roles"; /** * Represents the categories to which the tools belong @@ -99,17 +100,6 @@ export const TOOL_GROUPS = { MORE: "more", }; -/** - * Represents the user roles that can be needed to access a given topMenuItem - * @typedef Roles - * @type {{BETA_TESTER: string, ARCHIVE: string, LOCK: string}} - */ -export const ROLES = { - ARCHIVE: "ARCHIVE", - BETA_TESTER: "BETA_TESTER", - LOCK: "lock", -}; - /** * Represents a topMenuItem that can be used by users */ diff --git a/src/redux/actions/tools/syntheticImageDetectionActions.jsx b/src/redux/actions/tools/syntheticImageDetectionActions.jsx index acb25a804..dd38de66a 100644 --- a/src/redux/actions/tools/syntheticImageDetectionActions.jsx +++ b/src/redux/actions/tools/syntheticImageDetectionActions.jsx @@ -11,3 +11,7 @@ export const setSyntheticImageDetectionLoading = createAction( export const setSyntheticImageDetectionResult = createAction( "SET_SYNTHETIC_IMAGE_DETECTION_RESULT", ); + +export const setSyntheticImageDetectionNearDuplicates = createAction( + "SET_SYNTHETIC_IMAGE_DETECTION_NEAR_DUPLICATES", +); diff --git a/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx b/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx index 881bb8107..39aa6052c 100644 --- a/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx +++ b/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx @@ -2,6 +2,7 @@ const defaultState = { url: "", result: null, loading: false, + duplicates: null, }; const syntheticImageDetectionReducer = (state = defaultState, action) => { @@ -12,6 +13,7 @@ const syntheticImageDetectionReducer = (state = defaultState, action) => { url: "", result: null, loading: false, + duplicates: null, }; case "SET_SYNTHETIC_IMAGE_DETECTION_LOADING": return { @@ -25,6 +27,12 @@ const syntheticImageDetectionReducer = (state = defaultState, action) => { result: action.payload.result, loading: false, }; + case "SET_SYNTHETIC_IMAGE_DETECTION_NEAR_DUPLICATES": + return { + ...state, + duplicates: action.payload, + loading: false, + }; default: return state; } From e6a87d577095d45bbe32de7429569f5751229048 Mon Sep 17 00:00:00 2001 From: Valentin Porcellini Date: Tue, 16 Jul 2024 15:01:34 +0200 Subject: [PATCH 02/12] Improved NDD results, added missing algorithms --- .../SyntheticImageDetection/NddDatagrid.jsx | 78 +++++++--- .../SyntheticImageDetectionAlgorithms.jsx | 60 ++++++-- .../tools/SyntheticImageDetection/index.jsx | 6 +- .../syntheticImageDetectionResults.jsx | 141 ++++++------------ .../tools/syntheticImageDetectionReducer.jsx | 6 +- 5 files changed, 159 insertions(+), 132 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx index 6c05057b8..2c01d6aac 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx @@ -3,13 +3,12 @@ import Box from "@mui/material/Box"; import { DataGrid, useGridApiRef } from "@mui/x-data-grid"; import Link from "@mui/material/Link"; import { Chip, Grid, Stack, Typography } from "@mui/material"; +import { i18nLoadNamespace } from "../../../Shared/Languages/i18nLoadNamespace"; import { getAlertColor, getAlertLabel, getPercentageColorCode, } from "./syntheticImageDetectionResults"; -import { i18nLoadNamespace } from "../../../Shared/Languages/i18nLoadNamespace"; -import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; const NddDataGrid = ({ rows }) => { const keyword = i18nLoadNamespace( @@ -38,18 +37,14 @@ const NddDataGrid = ({ rows }) => { // resizeData(); // }, [resizeData]); - const detectionRateStack = (row) => { - const detectionPercentageNdImage = row.detectionRate1; + const detectionRateStack = (row, index) => { + if (!row.detectionResults[index]) return null; - const algorithm = syntheticImageDetectionAlgorithms.find( - (algo) => (algo.apiServiceName = row.algorithmName), - ); + const detectionPercentageNdImage = + row.detectionResults[index].predictionScore; return ( - - {keyword(algorithm.name)} - <> @@ -93,6 +88,58 @@ const NddDataGrid = ({ rows }) => { ); }; + /** + * Computes the JSX element to display for the algorithm name cell + * @param params + * @param index {number} The array position + * @returns {React.JSX.Element|null} The JSX element if applicable else null + */ + const renderAlgorithmName = (params, index) => { + if (!params.row.detectionResults[index]) { + return null; + } + + return ( + + {keyword(params.row.detectionResults[index].name)} + + ); + }; + + /** + * A helper function to compute the detection rows as not all the NDD results have the same number of algorithms for detections + * @returns {*[]} + */ + const detectionDetailsRows = () => { + const maxSizeAlgorithm = Math.max( + ...rows.map((row) => row.detectionResults.length), + ); + + let algorithmsRows = []; + + for (let i = 0; i < maxSizeAlgorithm; i++) { + algorithmsRows.push({ + field: `detectionName${i + 1}`, + headerName: `Algorithm #${i + 1}`, + type: "string", + // minWidth: 300, + flex: 1, + renderCell: (params) => renderAlgorithmName(params, i), + }); + + algorithmsRows.push({ + field: `detectionRate${i + 1}`, + headerName: `Score #${i + 1}`, + type: "number", + // minWidth: 300, + flex: 1, + renderCell: (params) => detectionRateStack(params.row, i), + }); + } + + return algorithmsRows; + }; + const columns = [ { field: "id", @@ -113,14 +160,7 @@ const NddDataGrid = ({ rows }) => { /> ), }, - { - field: "detectionRate1", - headerName: "Detection Rate #1", - type: "number", - // minWidth: 300, - flex: 1, - renderCell: (params) => detectionRateStack(params.row), - }, + ...detectionDetailsRows(), { field: "archiveUrl", headerName: "Archive URL", @@ -142,7 +182,7 @@ const NddDataGrid = ({ rows }) => { return ( { roles.includes(algorithm.roleNeeded), ); }; + +/** + * Returns the Synthetic Image Detection Algorithm Object from the given APi Service Name + * @param apiName {string} + * @returns {SyntheticImageDetectionAlgorithm} + */ +export const getSyntheticImageDetectionAlgorithmFromApiName = (apiName) => { + return syntheticImageDetectionAlgorithms.find( + (algorithm) => algorithm.apiServiceName === apiName, + ); +}; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 02cf90871..168fe3e09 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -29,7 +29,7 @@ import SyntheticImageDetectionResults from "./syntheticImageDetectionResults"; import { setError } from "redux/reducers/errorReducer"; import StringFileUploadField from "../../../Shared/StringFileUploadField"; import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils"; -import { getSyntheticImageDetectionAlgorithmsForRoles } from "./SyntheticImageDetectionAlgorithms"; +import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; const SyntheticImageDetection = () => { const classes = useMyStyles(); @@ -73,7 +73,7 @@ const SyntheticImageDetection = () => { dispatch(setSyntheticImageDetectionLoading(true)); const modeURL = "images/"; - const services = getSyntheticImageDetectionAlgorithmsForRoles(role) + const services = syntheticImageDetectionAlgorithms .map((algorithm) => algorithm.apiServiceName) .join(","); @@ -142,7 +142,6 @@ const SyntheticImageDetection = () => { } if (response && response.data != null) { - console.log(response.data); dispatch( setSyntheticImageDetectionResult({ url: image ? URL.createObjectURL(image) : url, @@ -170,6 +169,7 @@ const SyntheticImageDetection = () => { if ( !imgSimilarRes.data || !imgSimilarRes.data.similar_media || + !Array.isArray(imgSimilarRes.data.similar_media) || imgSimilarRes.data.similar_media.length === 0 ) { dispatch(setSyntheticImageDetectionNearDuplicates(null)); diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 78123a861..3c4713481 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -27,14 +27,10 @@ import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils"; import GaugeChartModalExplanation from "../../../Shared/GaugeChartModalExplanation"; import NddDatagrid from "./NddDatagrid"; import { - admR50Grip, DETECTION_THRESHOLDS, - ganR50Mever, - ldmR50Grip, - ldmRineMever, - proGanR50Grip, - proGanRineMever, + getSyntheticImageDetectionAlgorithmFromApiName, SyntheticImageDetectionAlgorithm, + syntheticImageDetectionAlgorithms, } from "./SyntheticImageDetectionAlgorithms"; /** @@ -81,16 +77,14 @@ class NddResult { * @param image {string} * @param archiveUrl {string} * @param imageUrls {string} - * @param detectionRate1 {number} - * @param algorithmName {string} + * @param detectionResults {SyntheticImageDetectionAlgorithmResult[]} */ - constructor(id, image, archiveUrl, imageUrls, detectionRate1, algorithmName) { + constructor(id, image, archiveUrl, imageUrls, detectionResults) { this.id = id; this.image = image; this.archiveUrl = archiveUrl; this.imageUrls = imageUrls; - this.detectionRate1 = detectionRate1; - this.algorithmName = algorithmName; + this.detectionResults = detectionResults; } } @@ -131,79 +125,23 @@ const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { useEffect(() => { setResultsHaveErrors(false); - let diffusionScore; - - if (results.ldm_r50_grip_report) - diffusionScore = new SyntheticImageDetectionAlgorithmResult( - //previously unina_report - ldmR50Grip, - !results.ldm_r50_grip_report.prediction - ? 0 - : results.ldm_r50_grip_report.prediction * 100, - !results.ldm_r50_grip_report.prediction, - ); - - let ganScore; - - if (results.gan_r50_mever_report) - ganScore = new SyntheticImageDetectionAlgorithmResult( - //previously gan_report - ganR50Mever, - !results.gan_r50_mever_report.prediction - ? 0 - : results.gan_r50_mever_report.prediction * 100, - !results.gan_r50_mever_report.prediction, - ); - - let proganScore; - - if (results.progan_r50_grip_report) - proganScore = new SyntheticImageDetectionAlgorithmResult( - proGanR50Grip, - !results.progan_r50_grip_report.prediction - ? 0 - : results.progan_r50_grip_report.prediction * 100, - !results.progan_r50_grip_report.prediction, - ); - - let admScore; - if (results.adm_r50_grip_report) - admScore = new SyntheticImageDetectionAlgorithmResult( - admR50Grip, - !results.adm_r50_grip_report.prediction - ? 0 - : results.adm_r50_grip_report.prediction * 100, - !results.adm_r50_grip_report.prediction, - ); + let res = []; - let proganRineScore; - if (results.progan_rine_mever_report) - proganRineScore = new SyntheticImageDetectionAlgorithmResult( - proGanRineMever, - !results.progan_rine_mever_report.prediction - ? 0 - : results.progan_rine_mever_report.prediction * 100, - !results.progan_rine_mever_report.prediction, - ); + for (const algorithm of syntheticImageDetectionAlgorithms) { + const algorithmReport = results[algorithm.apiServiceName + "_report"]; - let ldmRineScore; - if (results.ldm_rine_mever_report) - ldmRineScore = new SyntheticImageDetectionAlgorithmResult( - ldmRineMever, - !results.ldm_rine_mever_report.prediction - ? 0 - : results.ldm_rine_mever_report.prediction * 100, - !results.ldm_rine_mever_report.prediction, - ); + if (algorithmReport) { + res.push( + new SyntheticImageDetectionAlgorithmResult( + algorithm, + !algorithmReport.prediction ? 0 : algorithmReport.prediction * 100, + algorithmReport.prediction === undefined, + ), + ); + } + } - const res = [ - diffusionScore, - ganScore, - proganScore, - admScore, - proganRineScore, - ldmRineScore, - ] + res = res .filter((i) => i !== undefined) .sort((a, b) => b.predictionScore - a.predictionScore); @@ -284,20 +222,33 @@ const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { ]; const colors = ["#00FF00", "#AAFF03", "#FFA903", "#FF0000"]; + /** + * + * @param nddResults + * @returns {NddResult[]} + */ const getNddRows = (nddResults) => { let rows = []; for (let i = 0; i < nddResults.length; i += 1) { const res = nddResults[i]; + let detectionResults = []; + for (const detection of Object.keys(res.detections)) { + const d = new SyntheticImageDetectionAlgorithmResult( + getSyntheticImageDetectionAlgorithmFromApiName(detection), + sanitizeDetectionPercentage(res.detections[detection] * 100), + false, + ); + + detectionResults.push(d); + } + rows.push( new NddResult( i + 1, res.archive_url, res.archive_url, res.origin_urls, - sanitizeDetectionPercentage( - res.detections[Object.keys(res.detections)[0]] * 100, - ), - Object.keys(res.detections)[0], + detectionResults, ), ); } @@ -368,6 +319,17 @@ const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { + + {nd && nd.similar_media && nd.similar_media.length > 0 && ( + + + + Similar images were detected as synthetic. See detection + details for similar images below. + + + + )} {syntheticImageScores.length > 0 ? ( @@ -494,15 +456,6 @@ const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { thresholds={DETECTION_THRESHOLDS} /> - {nd && nd.similar_media && nd.similar_media.length > 0 && ( - - - Similar images were detected as synthetic. See detection - details for similar images below. - - - )} - {resultsHaveErrors && ( {keyword("synthetic_image_detection_algorithms_errors")} diff --git a/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx b/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx index 39aa6052c..2ceb64169 100644 --- a/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx +++ b/src/redux/reducers/tools/syntheticImageDetectionReducer.jsx @@ -9,11 +9,7 @@ const syntheticImageDetectionReducer = (state = defaultState, action) => { switch (action.type) { case "SYNTHETIC_IMAGE_DETECTION_RESET": return { - ...state, - url: "", - result: null, - loading: false, - duplicates: null, + ...defaultState, }; case "SET_SYNTHETIC_IMAGE_DETECTION_LOADING": return { From 9d181b6dec7188f8cca56873490a71ce71091624 Mon Sep 17 00:00:00 2001 From: Valentin Porcellini Date: Fri, 19 Jul 2024 14:57:37 +0200 Subject: [PATCH 03/12] Added the option to open NDD result in a new tab --- .../SyntheticImageDetection/NddDatagrid.jsx | 38 +++++++++++++++---- .../tools/SyntheticImageDetection/index.jsx | 30 ++++++++++++--- .../syntheticImageDetectionResults.jsx | 4 -- .../Shared/StringFileUploadField/index.jsx | 21 +++++++--- src/constants/tools.jsx | 2 +- 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx index 2c01d6aac..3551c1292 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/NddDatagrid.jsx @@ -1,6 +1,6 @@ import * as React from "react"; import Box from "@mui/material/Box"; -import { DataGrid, useGridApiRef } from "@mui/x-data-grid"; +import { DataGrid, GridActionsCellItem, useGridApiRef } from "@mui/x-data-grid"; import Link from "@mui/material/Link"; import { Chip, Grid, Stack, Typography } from "@mui/material"; import { i18nLoadNamespace } from "../../../Shared/Languages/i18nLoadNamespace"; @@ -9,6 +9,7 @@ import { getAlertLabel, getPercentageColorCode, } from "./syntheticImageDetectionResults"; +import { OpenInNew } from "@mui/icons-material"; const NddDataGrid = ({ rows }) => { const keyword = i18nLoadNamespace( @@ -100,7 +101,7 @@ const NddDataGrid = ({ rows }) => { } return ( - + {keyword(params.row.detectionResults[index].name)} ); @@ -122,8 +123,8 @@ const NddDataGrid = ({ rows }) => { field: `detectionName${i + 1}`, headerName: `Algorithm #${i + 1}`, type: "string", - // minWidth: 300, - flex: 1, + minWidth: 120, + // flex: 1, renderCell: (params) => renderAlgorithmName(params, i), }); @@ -131,8 +132,8 @@ const NddDataGrid = ({ rows }) => { field: `detectionRate${i + 1}`, headerName: `Score #${i + 1}`, type: "number", - // minWidth: 300, - flex: 1, + minWidth: 180, + // flex: 1, renderCell: (params) => detectionRateStack(params.row, i), }); } @@ -174,9 +175,28 @@ const NddDataGrid = ({ rows }) => { { field: "imageUrls", headerName: "Image URLs", - flex: 1, + // minWidth: 100, renderCell: (params) => imageUrlsCell(params.value), }, + { + field: "actions", + type: "actions", + width: 80, + getActions: (params) => { + console.log(params); + const url = new URL( + window.location.href + "?url=" + params.row.archiveUrl, + ); + return [ + // eslint-disable-next-line react/jsx-key + } + onClick={() => window.open(url, "_blank", "noopener noreferrer")} + label="Open Analysis" + />, + ]; + }, + }, ]; return ( @@ -200,11 +220,13 @@ const NddDataGrid = ({ rows }) => { }, }, }} + autosizeOnMount={true} autosizeOptions={{ includeHeaders: true, includeOutliers: true, - columns: ["image", "detectionRate1"], + // columns: ["image", "detectionRate1"], expand: true, + outliersFactor: 20, }} pageSizeOptions={[5]} disableRowSelectionOnClick diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 168fe3e09..a4c922cff 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { resetSyntheticImageDetectionImage, @@ -30,8 +30,13 @@ import { setError } from "redux/reducers/errorReducer"; import StringFileUploadField from "../../../Shared/StringFileUploadField"; import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils"; import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; +import { useLocation } from "react-router-dom"; const SyntheticImageDetection = () => { + const location = useLocation(); + const urlParams = new URLSearchParams(location.search); + const urlParam = urlParams.get("url"); + const classes = useMyStyles(); const keyword = i18nLoadNamespace( "components/NavItems/tools/SyntheticImageDetection", @@ -236,15 +241,29 @@ const SyntheticImageDetection = () => { ); }; - const handleSubmit = async () => { + /** + * + * @param url {string} + * @returns {Promise} + */ + const handleSubmit = async (url) => { dispatch(resetSyntheticImageDetectionImage()); - + const urlInput = url ? url : input; const type = - input && typeof input === "string" ? IMAGE_FROM.URL : IMAGE_FROM.UPLOAD; + urlInput && typeof urlInput === "string" + ? IMAGE_FROM.URL + : IMAGE_FROM.UPLOAD; - await getSyntheticImageScores(input, true, dispatch, type, imageFile); + await getSyntheticImageScores(urlInput, true, dispatch, type, imageFile); }; + useEffect(() => { + if (urlParam) { + setInput(urlParam); + handleSubmit(urlParam); + } + }, []); + return ( { fileInputTypesAccepted={"image/*"} handleCloseSelectedFile={handleClose} preprocessLocalFile={preprocessImage} + isParentLoading={isLoading} /> diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 3c4713481..09675ab6e 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -255,10 +255,6 @@ const SyntheticImageDetectionResults = ({ results, url, handleClose, nd }) => { return rows; }; - if (nd) { - console.log(getNddRows(nd.similar_media)); - } - return ( { const fileRef = useRef(null); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState( + isParentLoading !== undefined ? isParentLoading : false, + ); + + useEffect(() => { + if (isParentLoading !== undefined && isLoading !== isParentLoading) { + setIsLoading(isParentLoading); + } + }, [isParentLoading]); return ( @@ -58,7 +68,7 @@ const StringFileUploadField = ({ /> - + diff --git a/src/constants/tools.jsx b/src/constants/tools.jsx index efb0c46dd..3a3a6edaa 100644 --- a/src/constants/tools.jsx +++ b/src/constants/tools.jsx @@ -415,7 +415,7 @@ const imageGif = new Tool(