Skip to content

Commit

Permalink
Merge pull request #679 from AFP-Medialab/hiya-null-fix
Browse files Browse the repository at this point in the history
Hiya null values fix
  • Loading branch information
Sallaa authored Dec 4, 2024
2 parents 7037db3 + b41dd3a commit 60fa94e
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 77 deletions.
67 changes: 54 additions & 13 deletions src/components/NavItems/tools/Loccus/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import StringFileUploadField from "../../../Shared/StringFileUploadField";
import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils";

import { v4 as uuidv4 } from "uuid";
import { useMutation } from "@tanstack/react-query";

const Loccus = () => {
const classes = useMyStyles();
Expand All @@ -44,10 +45,13 @@ const Loccus = () => {
const AUDIO_FILE_DEFAULT_STATE = undefined;

const role = useSelector((state) => state.userSession.user.roles);
const isLoading = useSelector(
(state) => state.syntheticAudioDetection.loading,
);

const result = useSelector((state) => state.syntheticAudioDetection.result);

const isInconclusive = useSelector(
(state) => state.syntheticAudioDetection.isInconclusive,
);

const url = useSelector((state) => state.syntheticAudioDetection.url);
const chunks = useSelector((state) => state.syntheticAudioDetection.chunks);
const authenticatedRequest = useAuthenticatedRequest();
Expand All @@ -56,9 +60,29 @@ const Loccus = () => {

const dispatch = useDispatch();

/**
* Returns true iff more than 50% of the chunks' results are null in the array, else returns false
*/
const isResultInconclusive = (result) => {
if (!result || result.length === 0)
return new Error(
`[isResultInconclusive] Error: the result object is not defined.`,
);

let nullChunks = 0;

const chunks = result.length;

for (const resultChunk of result) {
if (resultChunk.score === null) nullChunks++;
}

return nullChunks / chunks >= 0.5;
};

const useGetVoiceCloningScore = async (url, processURL, dispatch) => {
if (!processURL && !url && !audioFile) {
return;
throw new Error("No URL or audio file");
}

const blobToDataUrl = (blob) =>
Expand All @@ -80,17 +104,15 @@ const Loccus = () => {
try {
blob = (await axios.get(url, { responseType: "blob" })).data ?? null;
} catch (e) {
//TODO: Handle Error
console.log(e);
console.error(e);
throw new Error(e);
}
}

const b64InputFile = blob
? decodeURIComponent(await blobToBase64(blob))
: await blobToBase64(audioFile);

dispatch(setLoccusLoading(true));

const handleError = (e) => {
dispatch(setError(e));
dispatch(setLoccusLoading(false));
Expand Down Expand Up @@ -124,12 +146,11 @@ const Loccus = () => {
res = await authenticatedRequest(config);

if (!res || !res.data || res.data.message) {
// TODO: handle error
return;
throw new Error("No data");
}

if (!res.data.state || res.data.state !== "available") {
// TODO: Handle Error
throw new Error("The file is not available.");
return;
}

Expand Down Expand Up @@ -179,24 +200,30 @@ const Loccus = () => {

const res3 = await authenticatedRequest(config3);

const isInconclusive = isResultInconclusive(res3.data);

dispatch(
setLoccusResult({
url: audioFile ? URL.createObjectURL(audioFile) : url,
result: res2.data,
chunks: res3.data,
isInconclusive: isInconclusive,
}),
);
} catch (error) {
console.log(error);
if (error.message.includes("canceled")) {
handleError(keyword("loccus_error_timeout"));
throw new Error(keyword("loccus_error_timeout"));
} else {
handleError(error.response?.data?.message ?? error.message);
throw new Error(error.response?.data?.message ?? error.message);
}
}
};

const handleClose = () => {
getAnalysisResultsForAudio.reset();
setInput("");
setAudioFile(AUDIO_FILE_DEFAULT_STATE);
dispatch(resetLoccusAudio());
Expand Down Expand Up @@ -294,9 +321,17 @@ const Loccus = () => {
);
}

const getAnalysisResultsForAudio = useMutation({
mutationFn: () => {
return useGetVoiceCloningScore(input, true, dispatch);
},
onSuccess: (data) => {},
});

const handleSubmit = async () => {
dispatch(resetLoccusAudio());
await useGetVoiceCloningScore(input, true, dispatch);

await getAnalysisResultsForAudio.mutate();
};

return (
Expand Down Expand Up @@ -349,10 +384,11 @@ const Loccus = () => {
fileInputTypesAccepted={"audio/*"}
handleCloseSelectedFile={handleClose}
preprocessLocalFile={preprocessLocalFile}
isParentLoading={getAnalysisResultsForAudio.isPending}
/>
</form>
<Box m={2} />
{isLoading && (
{getAnalysisResultsForAudio.isPending && (
<Box mt={3}>
<LinearProgress />
</Box>
Expand All @@ -362,9 +398,14 @@ const Loccus = () => {

<Box m={3} />

{getAnalysisResultsForAudio.isError && (
<Alert severity="error">{keyword("loccus_generic_error")}</Alert>
)}

{result && (
<LoccusResults
result={result}
isInconclusive={isInconclusive}
url={url}
handleClose={handleClose}
chunks={chunks}
Expand Down
136 changes: 75 additions & 61 deletions src/components/NavItems/tools/Loccus/loccusResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils";
import GaugeChartModalExplanation from "../../../Shared/GaugeChartResults/GaugeChartModalExplanation";
import { ROLES } from "../../../../constants/roles";

const LoccusResults = (props) => {
const LoccusResults = ({
result,
isInconclusive,
url,
handleClose,
chunks,
}) => {
dayjs.extend(duration);

const keyword = i18nLoadNamespace("components/NavItems/tools/Loccus");
Expand All @@ -52,9 +58,6 @@ const LoccusResults = (props) => {

const role = useSelector((state) => state.userSession.user.roles);

const result = props.result;
const url = props.url;

const [voiceCloningScore, setVoiceCloningScore] = useState(null);
const [voiceRecordingScore, setVoiceRecordingScore] = useState(null);

Expand Down Expand Up @@ -240,6 +243,7 @@ const LoccusResults = (props) => {
}

const newVoiceCloningScore = (1 - result.subscores.synthesis) * 100;

if (voiceCloningScore !== newVoiceCloningScore)
setVoiceCloningScore(newVoiceCloningScore);

Expand Down Expand Up @@ -296,7 +300,7 @@ const LoccusResults = (props) => {
style={{ borderRadius: "4px 4p x 0px 0px" }}
title={keyword("loccus_title")}
action={
<IconButton aria-label="close" onClick={props.handleClose}>
<IconButton aria-label="close" onClick={handleClose}>
<Close sx={{ color: "white" }} />
</IconButton>
}
Expand Down Expand Up @@ -328,7 +332,7 @@ const LoccusResults = (props) => {
<Grid2 ref={chunksChartRef} width="100%" height="300px">
<Chart
type={"line"}
data={getChartDataFromChunks(props.chunks)}
data={getChartDataFromChunks(chunks)}
options={chartConfig}
/>
</Grid2>
Expand Down Expand Up @@ -364,77 +368,87 @@ const LoccusResults = (props) => {
<Typography variant="h5">
{keyword("loccus_voice_cloning_detection_title")}
</Typography>
<Stack
direction={{ sm: "column", md: "row" }}
alignItems={{ sm: "start", md: "center" }}
justifyContent="center"
width="100%"
>

{!isInconclusive && (
<Stack
direction="column"
direction={{ sm: "column", md: "row" }}
alignItems={{ sm: "start", md: "center" }}
justifyContent="center"
alignItems="center"
spacing={0}
ref={gaugeChartRef}
width="100%"
>
<GaugeChart
id={"gauge-chart"}
animate={false}
nrOfLevels={4}
textColor={"black"}
arcsLength={[0.1, 0.2, 0.3, 0.4]}
percent={voiceCloningScore / 100}
style={{ width: 250 }}
/>
<Stack
direction="row"
direction="column"
justifyContent="center"
alignItems="center"
spacing={10}
spacing={0}
ref={gaugeChartRef}
>
<Typography variant="subtitle2">
{keyword("loccus_gauge_no_detection")}
</Typography>
<Typography variant="subtitle2">
{keyword("loccus_gauge_detection")}
</Typography>
<GaugeChart
id={"gauge-chart"}
animate={false}
nrOfLevels={4}
textColor={"black"}
arcsLength={[0.1, 0.2, 0.3, 0.4]}
percent={voiceCloningScore / 100}
style={{ width: 250 }}
/>

<Stack
direction="row"
justifyContent="center"
alignItems="center"
spacing={10}
>
<Typography variant="subtitle2">
{keyword("loccus_gauge_no_detection")}
</Typography>
<Typography variant="subtitle2">
{keyword("loccus_gauge_detection")}
</Typography>
</Stack>
</Stack>
<Box alignSelf={{ sm: "flex-start", md: "flex-end" }}>
<Tooltip title={keyword("loccus_download_gauge_button")}>
<IconButton
color="primary"
aria-label="download chart"
onClick={async () =>
await exportReactElementAsJpg(
gaugeChartRef,
"gauge_chart",
)
}
>
<Download />
</IconButton>
</Tooltip>
</Box>
</Stack>
<Box alignSelf={{ sm: "flex-start", md: "flex-end" }}>
<Tooltip title={keyword("loccus_download_gauge_button")}>
<IconButton
color="primary"
aria-label="download chart"
onClick={async () =>
await exportReactElementAsJpg(
gaugeChartRef,
"gauge_chart",
)
}
>
<Download />
</IconButton>
</Tooltip>
</Box>
</Stack>

<GaugeChartModalExplanation
keyword={keyword}
keywordsArr={keywords}
keywordLink={"loccus_scale_explanation_link"}
keywordModalTitle={"loccus_scale_modal_explanation_title"}
colors={colors}
/>
)}

{!isInconclusive && (
<GaugeChartModalExplanation
keyword={keyword}
keywordsArr={keywords}
keywordLink={"loccus_scale_explanation_link"}
keywordModalTitle={"loccus_scale_modal_explanation_title"}
colors={colors}
/>
)}

<CustomAlertScore
score={voiceCloningScore}
detectionType={DETECTION_TYPES.VOICE_CLONING}
toolName={"Loccus"}
thresholds={DETECTION_THRESHOLDS}
isInconclusive={isInconclusive}
/>
<Typography>
{keyword("loccus_cloning_additional_explanation_text")}
</Typography>

{!isInconclusive && (
<Typography>
{keyword("loccus_cloning_additional_explanation_text")}
</Typography>
)}
</Stack>

{role.includes(ROLES.EXTRA_FEATURE) && (
Expand Down
Loading

0 comments on commit 60fa94e

Please sign in to comment.