Skip to content

Commit

Permalink
Pull study works, except for the last part.
Browse files Browse the repository at this point in the history
  • Loading branch information
jennydaman committed Sep 25, 2024
1 parent 0811d4e commit 76c5a75
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 57 deletions.
11 changes: 11 additions & 0 deletions src/api/lonk/seriesMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ class SeriesMap<T> {
private keyOf(pacs_name: string, SeriesInstanceUID: string): string {
return JSON.stringify({ SeriesInstanceUID, pacs_name });
}

/**
* Get the entries `[pacs_name, SeriesInstanceUID, value]`
*/
public entries(): [string, string, T][] {
// when we upgrade to TS 5.9, use Iterator.map instead of Array.map
return Array.from(this.map.entries()).map(([key, value]) => {
const { pacs_name, SeriesInstanceUID } = JSON.parse(key);
return [pacs_name, SeriesInstanceUID, value];
});
}
}

export default SeriesMap;
169 changes: 136 additions & 33 deletions src/components/Pacs/PacsController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ import {
StudyKey,
} from "./types.ts";
import { DEFAULT_PREFERENCES } from "./defaultPreferences.ts";
import { zipPacsNameAndSeriesUids } from "./helpers.ts";
import { toStudyKey, zipPacsNameAndSeriesUids } from "./helpers.ts";
import { useImmer } from "use-immer";
import SeriesMap from "../../api/lonk/seriesMap.ts";
import { useLonk } from "../../api/lonk";
import { produce, WritableDraft } from "immer";
import {
isFromPacs,
sameSeriesInstanceUidAs,
sameStudyInstanceUidAs,
} from "./curry.ts";
import { Study } from "../../api/pfdcm/models.ts";

type PacsControllerProps = {
getPfdcmClient: () => PfdcmClient;
Expand Down Expand Up @@ -101,29 +107,19 @@ const PacsController: React.FC<PacsControllerProps> = ({
// STATE
// ========================================

const [{ service, query }, setPacsQuery] = React.useState<{
service?: string;
query?: PACSqueryCore;
}>({});

/**
* Indicates a fatal error with the WebSocket.
*/
const [wsError, setWsError] = React.useState<React.ReactNode | null>(null);
// TODO create a settings component for changing preferences
const [preferences, setPreferences] = React.useState(DEFAULT_PREFERENCES);

/**
* The state of DICOM series, according to LONK.
*/
const [receiveState, setReceiveState] = useImmer<ReceiveState>(
new SeriesMap(),
);
/**
* Studies which have their series visible on-screen.
*/
const [expandedStudies, setExpandedStudies] = React.useState<
ReadonlyArray<StudyKey>
>([]);

/**
* List of PACS queries which the user wants to pull.
Expand Down Expand Up @@ -164,9 +160,14 @@ const PacsController: React.FC<PacsControllerProps> = ({
);

// ========================================
// QUERIES AND DATA
// PFDCM QUERIES AND DATA
// ========================================

const [{ service, query }, setPacsQuery] = React.useState<{
service?: string;
query?: PACSqueryCore;
}>({});

/**
* List of PACS servers which PFDCM can talk to.
*/
Expand All @@ -186,6 +187,26 @@ const PacsController: React.FC<PacsControllerProps> = ({
queryFn:
service && query ? () => pfdcmClient.query(service, query) : skipToken,
});

// ========================================
// EXPANDED STUDIES AND SERIES STATE
// ========================================

/**
* Studies which have their series visible on-screen.
*/
const [expandedStudies, setExpandedStudies] = useImmer<
ReadonlyArray<StudyKey>
>([]);

/**
* The StudyInstanceUIDs of all expanded studies.
*/
const expandedStudyUids = React.useMemo(
() => expandedStudies.map((s) => s.StudyInstanceUID),
[expandedStudies],
);

/**
* List of series which are currently visible on-screen.
*/
Expand All @@ -194,6 +215,61 @@ const PacsController: React.FC<PacsControllerProps> = ({
[expandedStudies, pfdcmStudies.data],
);

const changeExpandedStudies = React.useCallback(
(pacs_name: string, StudyInstanceUIDs: ReadonlyArray<string>) => {
setExpandedStudies(
StudyInstanceUIDs.map((StudyInstanceUID) => ({
StudyInstanceUID,
pacs_name,
})),
);
},
[setExpandedStudies],
);

const appendExpandedStudies = React.useCallback(
(studies: Pick<Study, "StudyInstanceUID" | "RetrieveAETitle">[]) =>
setExpandedStudies((draft) => {
draft.push(...studies.map(toStudyKey));
}),
[setExpandedStudies],
);

/**
* Expand the studies of the query.
*/
const expandStudiesFor = React.useCallback(
(pacs_name: string, query: PACSqueryCore) => {
if (!pfdcmStudies.data) {
throw new Error(
"Expanding studies is not currently possible because we do not " +
"have data from PFDCM yet.",
);
}
if (query.seriesInstanceUID) {
const studies = pfdcmStudies.data
.filter(isFromPacs(pacs_name))
.flatMap((study) => study.series)
.filter(sameSeriesInstanceUidAs(query));
appendExpandedStudies(studies);
return;
}
if (!query.seriesInstanceUID && query.studyInstanceUID) {
const studies = pfdcmStudies.data
.filter(isFromPacs(pacs_name))
.map((s) => s.study)
.filter(sameStudyInstanceUidAs(query));
appendExpandedStudies(studies);
return;
}
},
[pfdcmStudies.data, appendExpandedStudies],
);

// ========================================
// CUBE QUERIES AND DATA
// ========================================

/**
* Check whether CUBE has any of the series that are expanded.
*/
Expand Down Expand Up @@ -227,6 +303,37 @@ const PacsController: React.FC<PacsControllerProps> = ({
})),
[expandedSeries, cubeSeriesQuery],
);
//
// /**
// * Poll CUBE for the existence of DICOM series which have been reported as
// * "done" by LONK. It is necessary to poll CUBE because there will be a delay
// * between when LONK reports the series as "done" and when CUBE will run the
// * celery task of finally registering the series.
// */
// const finalCheckNeedingSeries = useQueries({
// queries: React.useMemo(
// () =>
// receiveState.entries().map(([pacs_name, SeriesInstanceUID, state]) => ({
// queryKey: [
// "finalCheckNeedingSeries",
// pacs_name,
// SeriesInstanceUID,
// state,
// ],
// queryFn: async () => {
// const search = { pacs_name, SeriesInstanceUID, limit: 1 };
// const list = await chrisClient.getPACSSeriesList(search);
// const items = list.getItems() as ReadonlyArray<PACSSeries>;
// if (items.length === 0) {
// throw Error("not found");
// }
// return items[0];
// },
// enabled: state.done, // TODO CANCEL POLLING ONCE WE FOUND THE FILE.
// })),
// [receiveState],
// ),
// });

/**
* Combined states of PFDCM, LONK, and CUBE into one object.
Expand Down Expand Up @@ -356,24 +463,13 @@ const PacsController: React.FC<PacsControllerProps> = ({
[setPacsQuery],
);

const onStudyExpand = React.useCallback(
(pacs_name: string, StudyInstanceUIDs: ReadonlyArray<string>) => {
setExpandedStudies(
StudyInstanceUIDs.map((StudyInstanceUID) => ({
StudyInstanceUID,
pacs_name,
})),
);
},
[setExpandedStudies],
);

// ========================================
// PACS RETRIEVAL
// ========================================

const onRetrieve = React.useCallback(
(service: string, query: PACSqueryCore) => {
expandStudiesFor(service, query);
setPullRequests((draft) => {
// indicate that the user requests for something to be retrieved.
draft.push({
Expand All @@ -383,7 +479,7 @@ const PacsController: React.FC<PacsControllerProps> = ({
});
});
},
[setPullRequests],
[setPullRequests, expandStudiesFor],
);

/**
Expand Down Expand Up @@ -437,12 +533,9 @@ const PacsController: React.FC<PacsControllerProps> = ({
if (pullRequest.state !== RequestState.NOT_REQUESTED) {
return false;
}
if (!studies) {
return false;
}
if (
pullRequest.query.studyInstanceUID &&
!pullRequest.query.seriesInstanceUID
!("seriesInstanceUID" in pullRequest.query)
) {
return shouldPullStudy(
pullRequest.service,
Expand All @@ -457,7 +550,7 @@ const PacsController: React.FC<PacsControllerProps> = ({
}
return false;
},
[studies],
[shouldPullStudy, shouldPullSeries],
);

/**
Expand All @@ -474,13 +567,22 @@ const PacsController: React.FC<PacsControllerProps> = ({
updatePullRequestState(service, query, { error: error }),
onSuccess: (_, { service, query }) =>
updatePullRequestState(service, query, { state: RequestState.REQUESTED }),
onSettled: (data, error, variables, context) => {
console.dir({
event: "settled",
data,
error,
variables,
context,
});
},
});

React.useEffect(() => {
pullRequests
.filter(shouldSendPullRequest)
.forEach((pr) => pullFromPacs.mutate(pr));
}, [pullRequests]);
}, [pullRequests, shouldSendPullRequest]);

// ========================================
// EFFECTS
Expand Down Expand Up @@ -524,7 +626,8 @@ const PacsController: React.FC<PacsControllerProps> = ({
services={pfdcmServices.data}
onSubmit={onSubmit}
onRetrieve={onRetrieve}
onStudyExpand={onStudyExpand}
expandedStudyUids={expandedStudyUids}
onStudyExpand={changeExpandedStudies}
isLoadingStudies={pfdcmStudies.isLoading}
/>
) : (
Expand Down
25 changes: 15 additions & 10 deletions src/components/Pacs/PacsView.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React from "react";
import PacsInput, { PacsInputProps } from "./components/PacsInput.tsx";
import PacsStudiesView from "./components/PacsStudiesView.tsx";
import PacsStudiesView, {
PacsStudiesViewProps,
} from "./components/PacsStudiesView.tsx";
import { getDefaultPacsService } from "./components/helpers.ts";
import { useSearchParams } from "react-router-dom";
import { PACSqueryCore } from "../../api/pfdcm";
import { Empty, Flex, Spin } from "antd";
import { IPacsState } from "./types.ts";

type PacsViewProps = Pick<PacsInputProps, "services" | "onSubmit"> & {
onRetrieve: (service: string, query: PACSqueryCore) => void;
onStudyExpand: (
service: string,
StudyInstanceUIDs: ReadonlyArray<string>,
) => void;
state: IPacsState;
isLoadingStudies?: boolean;
};
type PacsViewProps = Pick<PacsInputProps, "services" | "onSubmit"> &
Pick<PacsStudiesViewProps, "expandedStudyUids"> & {
onRetrieve: (service: string, query: PACSqueryCore) => void;
onStudyExpand: (
service: string,
StudyInstanceUIDs: ReadonlyArray<string>,
) => void;
state: IPacsState;
isLoadingStudies?: boolean;
};

/**
* PACS Query and Retrieve view component.
Expand All @@ -28,6 +31,7 @@ const PacsView: React.FC<PacsViewProps> = ({
services,
onSubmit,
onRetrieve,
expandedStudyUids,
onStudyExpand,
isLoadingStudies,
}) => {
Expand Down Expand Up @@ -69,6 +73,7 @@ const PacsView: React.FC<PacsViewProps> = ({
preferences={preferences}
studies={studies}
onRetrieve={curriedOnRetrieve}
expandedStudyUids={expandedStudyUids}
onStudyExpand={curriedOnStudyExpand}
/>
</Spin>
Expand Down
6 changes: 6 additions & 0 deletions src/components/Pacs/components/PacsStudiesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ type PacsStudiesViewProps = {
preferences: PacsPreferences;
studies: PacsStudyState[];
onRetrieve: (query: PACSqueryCore) => void;
/**
* List of StudyInstanceUIDs which should be expanded.
*/
expandedStudyUids?: string[];
onStudyExpand?: (StudyInstanceUIDs: ReadonlyArray<string>) => void;
};

const PacsStudiesView: React.FC<PacsStudiesViewProps> = ({
studies,
onRetrieve,
expandedStudyUids,
onStudyExpand,
preferences,
}) => {
Expand Down Expand Up @@ -66,6 +71,7 @@ const PacsStudiesView: React.FC<PacsStudiesViewProps> = ({
studies.length === 1 ? [studies[0].info.StudyInstanceUID] : []
}
onChange={onChange}
activeKey={expandedStudyUids}
/>
<Typography>
{numPatients === 1 ? "1 patient, " : `${numPatients} patients, `}
Expand Down
Loading

0 comments on commit 76c5a75

Please sign in to comment.