Skip to content

Commit

Permalink
Merge pull request #311 from Pixilib/salim2
Browse files Browse the repository at this point in the history
Salim2
  • Loading branch information
salimkanoun authored Sep 7, 2024
2 parents 7b78246 + 39d4367 commit 9e81b52
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 41 deletions.
4 changes: 2 additions & 2 deletions src/content/series/SeriesRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const SeriesRoot: React.FC<SeriesRootProps> = ({ studyId }) => {
(id) => deleteSeries(id),
[],
{
onSuccess: (_, variables) => {
toastSuccess('Series deleted successfully' + variables);
onSuccess: () => {
toastSuccess('Series deleted successfully');
refetchSeries();
},
onError: (error: any) => {
Expand Down
113 changes: 113 additions & 0 deletions src/content/studies/AiStudy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Component for a modal to preview a study
* @name PreviewStudy
*/

import React, { useState } from "react";
import { getSeriesOfStudy } from "../../services/orthanc";
import { Colors, useCustomMutation, useCustomQuery, useCustomToast } from "../../utils";

import { Button, Modal, ProgressCircle, Spinner } from "../../ui";
import { ProcessingJob, Series } from "../../utils/types";
import { createProcessingJob, getProcessingJob } from "../../services/processing";

type AIStudyProps = {
studyId: string;
onClose: () => void;
show: boolean;
}

const AiStudy: React.FC<AIStudyProps> = ({ studyId, onClose, show }) => {
const { toastSuccess, toastError } = useCustomToast()

const [selectedSeries, setSelectedSeries] = useState([]);
const [jobId, setJobId] = useState<string | null>(null);

const { data: series, isLoading } = useCustomQuery(
['study', studyId, 'series'],
() => getSeriesOfStudy(studyId),
{
select: (instances: Series[]) => {
return instances.sort((a, b) => a.mainDicomTags?.seriesDescription?.localeCompare(b.mainDicomTags?.seriesDescription ?? "") ?? 0)
}
}
)

const { data: jobData } = useCustomQuery<ProcessingJob[]>(
['processing', jobId ?? ''],
() => getProcessingJob(jobId),
{
enabled: jobId != null,
refetchInterval: 2000
}
)

const { mutate: createProcessingJobMutate } = useCustomMutation(
({ jobType, jobPayload }) => createProcessingJob(jobType, jobPayload),
[[]],
{
onSuccess: (jobId) => {
toastSuccess("Job Created " + jobId)
setJobId(jobId)
}
}
)

const handleSeriesClick = (seriesId: string) => {
if (selectedSeries.includes(seriesId)) {
const newSelected = selectedSeries.filter((id) => id !== seriesId)
setSelectedSeries(newSelected)

} else {
setSelectedSeries(previousSeries => [...previousSeries, seriesId])
}
}

const handleExecute = () => {
if (selectedSeries.length !== 2) {
toastError('Select only 2 series, PT and CT')
return;
}
createProcessingJobMutate({
jobType: 'tmtv',
jobPayload: {
CtOrthancSeriesId: selectedSeries[0],
PtOrthancSeriesId: selectedSeries[1],
SendMaskToOrthancAs: ['seg'],
WithFragmentedMask: true,
}
})
}

console.log(jobData)
if (isLoading) return <Spinner />

return (
<Modal show={show} size='xl'>
<Modal.Header onClose={onClose} >
<Modal.Title>AI Inference</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className={"flex justify-center gap-3"}>
{
series?.map((series: Series) => {
return (
<Button key={series.id} color={selectedSeries.includes(series.id) ? Colors.success : Colors.secondary} onClick={() => handleSeriesClick(series.id)} >
{series.mainDicomTags.modality} - {series.mainDicomTags.seriesDescription}
</Button>
)
})
}
</div>
</Modal.Body>
<Modal.Footer>
<div className={"flex justify-center"}>
<Button color={Colors.success} onClick={handleExecute}>Execute TMTV Model</Button>
{jobData && jobData.map(job => <ProgressCircle progress={job.progress} text={job.state} />)}
</div>
</Modal.Footer>
</Modal>
);
};

export default AiStudy;
26 changes: 17 additions & 9 deletions src/content/studies/StudyActions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// StudyActions.tsx
import React from 'react';
import { FaEdit, FaEye, FaTrash } from "react-icons/fa";
import { FaEdit as EditIcon, FaEye as EyeIcon, FaTrash as TrashIcon } from "react-icons/fa";
import { GiBrain as BrainIcon } from 'react-icons/gi'
import { StudyMainDicomTags } from "../../utils/types";
import DropdownButton from '../../ui/menu/DropDownButton';
import OhifViewerLink from '../OhifViewerLink';
Expand All @@ -15,34 +16,41 @@ const StudyActions: React.FC<StudyActionsProps> = ({ study, onActionClick }) =>
const options = [
{
label: '',
icon: <FaEye />,
icon: <EyeIcon />,
color: 'green',
component: <OhifViewerLink studyInstanceUID={study.studyInstanceUID} />
},
{
label: '',
icon: <FaEye />,
icon: <EyeIcon />,
color: 'green',
component: <StoneViewerLink studyInstanceUID={study.studyInstanceUID} />
},
{
label: 'Modify',
icon: <FaEdit />,
icon: <EditIcon />,
color: 'orange',
action: () => onActionClick('edit', study.id)
},
{
label: 'Delete',
icon: <FaTrash />,
color: 'red',
action: () => onActionClick('delete', study.id)
label: 'AI',
icon: <BrainIcon />,
color: 'green',
action: () => onActionClick('ai', study.id)
},
{
label: 'Preview Study',
icon: <FaEye />,
icon: <EyeIcon />,
color: 'green',
action: () => onActionClick('preview', study.id)
},
{
label: 'Delete',
icon: <TrashIcon />,
color: 'red',
action: () => onActionClick('delete', study.id)
},

];

const handleClick = (e: React.MouseEvent) => {
Expand Down
16 changes: 16 additions & 0 deletions src/content/studies/StudyRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PreviewStudy from './PreviewStudy';
import { useConfirm } from '../../services/ConfirmContextProvider';
import { useCustomToast } from '../../utils/toastify';
import Patient from '../../model/Patient';
import AiStudy from './AiStudy';

type StudyRootProps = {
patient: Patient;
Expand All @@ -16,6 +17,7 @@ type StudyRootProps = {

const StudyRoot: React.FC<StudyRootProps> = ({ patient, onStudyUpdated, onStudySelected }) => {
const [editingStudy, setEditingStudy] = useState<string | null>(null);
const [aiStudyId, setAIStudyId] = useState<string | null>(null);
const [previewStudyId, setPreviewStudyId] = useState<string | null>(null);

const { confirm } = useConfirm();
Expand Down Expand Up @@ -60,6 +62,10 @@ const StudyRoot: React.FC<StudyRootProps> = ({ patient, onStudyUpdated, onStudyS
setPreviewStudyId(studyId);
}

const handleAIStudy=(studyId : string) => {
setAIStudyId(studyId)
}

const handleStudyAction = (action: string, studyId: string) => {
switch (action) {
case 'edit':
Expand All @@ -71,6 +77,9 @@ const StudyRoot: React.FC<StudyRootProps> = ({ patient, onStudyUpdated, onStudyS
case 'preview':
handlePreviewStudy(studyId);
break;
case 'ai':
handleAIStudy(studyId);
break;
default:
break;
}
Expand Down Expand Up @@ -103,6 +112,13 @@ const StudyRoot: React.FC<StudyRootProps> = ({ patient, onStudyUpdated, onStudyS
show={!!previewStudyId}
/>
)}
{aiStudyId && (
<AiStudy
studyId={aiStudyId}
onClose={() => setAIStudyId(null)}
show={!!aiStudyId}
/>
)}
</div>
);
};
Expand Down
7 changes: 7 additions & 0 deletions src/services/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ export const getToken = () => {
return store?.getState()?.user.token
}

export const handleAxiosError = (error: any) => {
if (error.response) {
throw error.response;
}
throw error;
}

export default axios
39 changes: 39 additions & 0 deletions src/services/processing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ProcessingJob } from "../utils/types";
import axios, { handleAxiosError } from "./axios";

export const createProcessingJob = (
jobType: string,
jobPayload: Record<string, any>
): Promise<string> => {
const payload = {
JobType: jobType,
TmtvJob: jobPayload,
};

return axios
.post(`/api/processing`, payload)
.then((response) => response.data.JobId)
.catch(handleAxiosError);
};

export const getProcessingJob = (jobId: string): Promise<ProcessingJob[]> => {
return axios
.get(`/api/processing/` + jobId)
.then((response) => {
const data = response.data;
return data.map((jobdata) => ({
progress: jobdata.Progress,
state: jobdata.State,
id: jobdata.Id,
results: jobdata.Results,
}));
})
.catch(handleAxiosError);
};

export const deleteProcessingJob = (jobId: string): Promise<void> => {
return axios
.delete(`/api/processing/` + jobId)
.then((response) => undefined)
.catch(handleAxiosError);
};
2 changes: 1 addition & 1 deletion src/ui/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const AccordionHeader = ({ children, className = "", variant = "default" }: Acco


return (
<div className={"w-full rounded-lg shadow-md flex flex justify-between items-center p-4 cursor-pointer " + getVariantClasses() + " " + className} >
<div className={"w-full rounded-lg shadow-md flex justify-between items-center p-4 cursor-pointer mb-2 " + getVariantClasses() + " " + className} >
{children}
</div>
)
Expand Down
65 changes: 36 additions & 29 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ export type OrthancJob = {
type: string;
progress: number;
state: StateJob | string;
completionTime :string;
content : Record<string,any>
creationTime : string;
effectiveRuntime :number;
errorCode :number
errorDescription :string;
errorDetails : string;
priority : number;
timestamp :string;
completionTime: string;
content: Record<string, any>;
creationTime: string;
effectiveRuntime: number;
errorCode: number;
errorDescription: string;
errorDetails: string;
priority: number;
timestamp: string;
};

export type Role = {
Expand Down Expand Up @@ -135,6 +135,13 @@ export type SignInResponse = {
userId: number;
};

export type ProcessingJob = {
progress: number;
state: string;
id: string;
results : Record<string,any>
};

export type Peer = {
name: string;
password: string;
Expand All @@ -151,7 +158,7 @@ export type Modality = {
};

export type ModalityExtended = {
name : string;
name: string;
aet: string;
allowEcho: boolean;
allowEventReport: boolean;
Expand Down Expand Up @@ -209,7 +216,7 @@ export type QueryPayload = {

export type FindPayload = QueryPayload & {
Labels: string[];
LabelsConstraint : string
LabelsConstraint: string;
};

export type ExtendedQueryPayload = {
Expand Down Expand Up @@ -281,24 +288,24 @@ export type StudyMainDicomTags = {
};

export type Instances = {
fileSize : number
fileUuid : string
id : string
indexInSeries : number
labels : string[]
mainDicomTags : {
acquisitionNumber : string|null
imageComments : string|null
imageOrientationPatient: string|null
imagePositionPatient : string|null
instanceCreationDate :string|null
instanceCreationTime :string|null
instanceNumber : string|null
sopInstanceUID : string|null
}
parentSeries :string
type :string
}
fileSize: number;
fileUuid: string;
id: string;
indexInSeries: number;
labels: string[];
mainDicomTags: {
acquisitionNumber: string | null;
imageComments: string | null;
imageOrientationPatient: string | null;
imagePositionPatient: string | null;
instanceCreationDate: string | null;
instanceCreationTime: string | null;
instanceNumber: string | null;
sopInstanceUID: string | null;
};
parentSeries: string;
type: string;
};

export type Series = {
expectedNumberOfInstances: number | null;
Expand Down

0 comments on commit 9e81b52

Please sign in to comment.