diff --git a/schema.graphql b/schema.graphql index 997ac06e..1224ae92 100644 --- a/schema.graphql +++ b/schema.graphql @@ -214,6 +214,7 @@ input UpdateSearchInput { anatomicalRegion: String cdsFinished: AWSDateTime cdsStarted: AWSDateTime + channel: Int completedBatches: Int computedMIPs: [String] dataThreshold: Int diff --git a/src/components/CustomSearch/ColorDepthSearchStep.jsx b/src/components/CustomSearch/ColorDepthSearchStep.jsx index 67626cab..64c3317b 100644 --- a/src/components/CustomSearch/ColorDepthSearchStep.jsx +++ b/src/components/CustomSearch/ColorDepthSearchStep.jsx @@ -1,9 +1,12 @@ -import React from "react"; +import React, { useContext } from "react"; import PropTypes from "prop-types"; import { formatRelative, formatDistanceStrict } from "date-fns"; import StepTitle from "./StepTitle"; +import LibraryFormatter from "../LibraryFormatter"; +import { AppContext } from "../../containers/AppContext"; -export default function ColorDepthSearchStep({ started, finished, state }) { +export default function ColorDepthSearchStep({ started, finished, state, librariesCountsMap }) { + const { appState } = useContext(AppContext); let searchEnd = ""; let duration = ""; @@ -14,11 +17,25 @@ export default function ColorDepthSearchStep({ started, finished, state }) { } } + const hasLibraries = librariesCountsMap && Object.keys(librariesCountsMap).length > 0; + return ( <>

{searchEnd}

{duration}

+ {hasLibraries ? ( + <> +

Libraries searched:

+ {Object.entries(librariesCountsMap).map(([library, count]) => ( +

+ ({count}) +

+ ))} + + ) : appState.debug ? ( +

Libraries data not available for this search

+ ) : null} ); } @@ -27,9 +44,11 @@ ColorDepthSearchStep.propTypes = { state: PropTypes.string.isRequired, finished: PropTypes.string, started: PropTypes.string, + librariesCountsMap: PropTypes.object, }; ColorDepthSearchStep.defaultProps = { finished: null, - started: null + started: null, + librariesCountsMap: null, }; diff --git a/src/components/CustomSearch/ImageAlignmentStep.jsx b/src/components/CustomSearch/ImageAlignmentStep.jsx index 7549fc94..22a74cf1 100644 --- a/src/components/CustomSearch/ImageAlignmentStep.jsx +++ b/src/components/CustomSearch/ImageAlignmentStep.jsx @@ -10,6 +10,19 @@ import HelpButton from "../Help/HelpButton"; export default function ImageAlignmentStep({ state, search }) { const [thumbnailUrl, setThumbnailUrl] = useState(null); const [movieUrl, setMovieUrl] = useState(null); + const [alignedVolume, setAlignmentFile] = useState(null); + + + useEffect(() =>{ + if (search.alignedVolume) { + const alignedVolumeUrl = `${search.searchDir}/${search.alignedVolume}`; + signedLink(alignedVolumeUrl).then((result) => { + setAlignmentFile(result); + }); + } else { + setAlignmentFile(null); + } + }, [search.searchDir, search.alignedVolume]); useEffect(() => { if (search.alignmentMovie) { @@ -80,6 +93,16 @@ export default function ImageAlignmentStep({ state, search }) { ) : ( "" )} + + {alignedVolume ? ( +
+ + Download Alignment File + +
+ ) : ( + "" + )} ); } else if (search.alignmentErrorMessage || search.errorMessage) { diff --git a/src/components/CustomSearch/SearchSteps.jsx b/src/components/CustomSearch/SearchSteps.jsx index 1b37dfc9..b597af6b 100644 --- a/src/components/CustomSearch/SearchSteps.jsx +++ b/src/components/CustomSearch/SearchSteps.jsx @@ -34,6 +34,15 @@ export default function SearchSteps({ search }) { currentStep = 5; } + let librariesCountsMap = null; + if (search.librariesCountsMap) { + try { + librariesCountsMap = JSON.parse(search.librariesCountsMap); + } catch (error) { + console.error("Failed to parse librariesCountsMap:", error); + } + } + return (
@@ -67,6 +76,7 @@ export default function SearchSteps({ search }) { started={search.cdsStarted} finished={search.cdsFinished} state={stepState(3, currentStep, errorMessage)} + librariesCountsMap={librariesCountsMap} /> diff --git a/src/components/MaskSelection.jsx b/src/components/MaskSelection.jsx index 640f24fa..2b158dea 100644 --- a/src/components/MaskSelection.jsx +++ b/src/components/MaskSelection.jsx @@ -127,7 +127,7 @@ export default function MaskSelection({ match }) { }); } - const maskDetails = { searchMask: maskName, id: searchMeta.id }; + const maskDetails = { searchMask: maskName, channel, id: searchMeta.id }; const searchParams = { ...searchFormData, ...maskDetails }; API.graphql( graphqlOperation(mutations.updateSearch, { input: searchParams }), @@ -216,7 +216,7 @@ export default function MaskSelection({ match }) { wrapperCol={{ span: 16 }} name="basic" initialValues={{ - selectedLibraries: searchMeta.searchType || getDefaultLibrary(appState?.dataConfig?.stores, searchMeta.anatomicalRegion) || undefined, + selectedLibraries: [searchMeta.searchType || getDefaultLibrary(appState?.dataConfig?.stores, searchMeta.anatomicalRegion)].filter(Boolean), dataThreshold: searchMeta.dataThreshold || 100, maskThreshold: searchMeta.maskThreshold || 100, xyShift: searchMeta.xyShift || 0, diff --git a/src/components/MatchModal/Download3D.jsx b/src/components/MatchModal/Download3D.jsx index 4b711d2e..ceac8c30 100644 --- a/src/components/MatchModal/Download3D.jsx +++ b/src/components/MatchModal/Download3D.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { useParams } from "react-router-dom"; import { Row, Col, Typography } from "antd"; @@ -7,6 +7,7 @@ import FileExclamationOutlined from "@ant-design/icons/FileExclamationOutlined"; import { faExternalLink } from "@fortawesome/pro-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import ViewIn3DButton from "./ViewIn3DButton"; +import { signedLink } from "../../libs/awsLib"; const { Title, Text, Paragraph } = Typography; @@ -39,6 +40,18 @@ function getH5JLink(construct) { export default function Download3D(props) { const { selectedMatch, mask, isLM } = props; const { algorithm } = useParams(); + const [alignedVolumeUrl, setAlignedVolumeUrl] = useState(null); + + useEffect(() => { + if (mask.alignedVolume && mask.searchDir) { + const volumePath = `${mask.searchDir}/${mask.alignedVolume}`; + signedLink(volumePath).then((result) => { + setAlignedVolumeUrl(result); + }); + } else { + setAlignedVolumeUrl(null); + } + }, [mask.searchDir, mask.alignedVolume]); // If the match has either an AlignedBodySWC or a VisuallyLosslessStack // file, then we need to make it available for download, the same needs @@ -53,11 +66,20 @@ export default function Download3D(props) { const downloadLinks = ( <> {!mask.precomputed ? ( - - We don't have - a 3D representation of your uploaded file. You will need to generate - one from your original imagery. - + alignedVolumeUrl ? ( + + {" "} + + Download Aligned Volume (H5J) + + + ) : ( + + We don't have + a 3D representation of your uploaded file. You will need to generate + one from your original imagery. + + ) ) : ( "" )} @@ -75,7 +97,7 @@ export default function Download3D(props) { return ( <> - {algorithm !== "pppm" && !mask.identityId ? ( + {algorithm !== "pppm" && (!mask.identityId || mask.alignedVolume) ? (

- View the match in our online volume viewer{" "} diff --git a/src/components/MatchModal/Summary.jsx b/src/components/MatchModal/Summary.jsx index d934a38b..5217e790 100644 --- a/src/components/MatchModal/Summary.jsx +++ b/src/components/MatchModal/Summary.jsx @@ -57,7 +57,7 @@ export default function Summary(props) { > {appState.compactMeta ? "Show" : "Hide"} Match Info - {algorithm !== "pppm" && !mask.identityId ? ( + {algorithm !== "pppm" && (!mask.identityId || mask.alignedVolume) ? ( { - const swc = isLM ? getSWCLink(mask) : getSWCLink(match.image); - signedPublicLink(swc).then((signed) => { - setSignedSwc(signed); - }); + const swc = isCustomUpload + ? getSWCLink(match.image) + : isLM ? getSWCLink(mask) : getSWCLink(match.image); - if (swc && h5j) { + if (swc) { + signedPublicLink(swc).then((signed) => { + setSignedSwc(signed); + }); + } + + if (isCustomUpload) { + const volumePath = `${mask.searchDir}/${mask.alignedVolume}`; + signedLink(volumePath).then((signed) => { + setSignedH5j(signed); + if (swc) { + setDisabled(false); + } + }); + } else if (swc && h5j) { setDisabled(false); } - }, [isLM, mask, match, h5j]); + }, [isLM, isCustomUpload, mask, match, h5j]); + + const h5jUrl = isCustomUpload ? signedH5j : h5j; // must encode the signedSWC, so that it makes it to the volume viewer in the // correct state for loading the swc const volViewerLink = `${config.volumeViewer}?ref=${encodeURIComponent( ref - )}&h5j=${encodeURIComponent(h5j)}&swc=${encodeURIComponent( + )}&h5j=${encodeURIComponent(h5jUrl)}&swc=${encodeURIComponent( signedSwc )}&ch=${channel}&mx=${mirrored}`; diff --git a/src/components/Results.jsx b/src/components/Results.jsx index 8944ac1a..baa05e75 100644 --- a/src/components/Results.jsx +++ b/src/components/Results.jsx @@ -95,7 +95,7 @@ export default function Results({ match }) { fr.readAsText(results.Body); }) .catch(error => { - if (error.response.status === 404) { + if (error.response && error.response.status === 404) { setSearchResults({ results: [] }); } else { message.error({ diff --git a/src/graphql/mutations.js b/src/graphql/mutations.js index 45256024..8c218b69 100644 --- a/src/graphql/mutations.js +++ b/src/graphql/mutations.js @@ -39,6 +39,7 @@ export const updateSearch = /* GraphQL */ ` identityId owner step + channel upload uploadThumbnail searchDir diff --git a/src/graphql/queries.js b/src/graphql/queries.js index e2db2746..0966a18e 100644 --- a/src/graphql/queries.js +++ b/src/graphql/queries.js @@ -14,6 +14,7 @@ export const getSearch = /* GraphQL */ ` alignmentErrorMessage alignmentScore alignmentMovie + alignedVolume step anatomicalRegion algorithm @@ -27,6 +28,7 @@ export const getSearch = /* GraphQL */ ` voxelX voxelY voxelZ + channel referenceChannel alignStarted alignFinished @@ -56,7 +58,9 @@ export const listSearches = /* GraphQL */ ` alignmentErrorMessage alignmentScore alignmentMovie - anatomicalRegion + alignedVolume + anatomicalRegion + channel cdsStarted cdsFinished step @@ -70,6 +74,7 @@ export const listSearches = /* GraphQL */ ` alignFinished alignStarted anatomicalRegion + librariesCountsMap } nextToken } @@ -95,7 +100,9 @@ export const listItemsByOwner = ` alignmentErrorMessage alignmentScore alignmentMovie - anatomicalRegion + alignedVolume + anatomicalRegion + channel cdsStarted cdsFinished step @@ -109,6 +116,7 @@ export const listItemsByOwner = ` alignFinished alignStarted anatomicalRegion + librariesCountsMap } nextToken diff --git a/src/graphql/subscriptions.js b/src/graphql/subscriptions.js index 2becd8a7..190d734f 100644 --- a/src/graphql/subscriptions.js +++ b/src/graphql/subscriptions.js @@ -24,8 +24,10 @@ export const onCreateSearch = /* GraphQL */ ` anatomicalRegion alignmentScore alignmentMovie + alignedVolume alignStarted alignFinished + librariesCountsMap } } `; @@ -60,8 +62,10 @@ export const onUpdateSearch = /* GraphQL */ ` anatomicalRegion alignmentScore alignmentMovie + alignedVolume alignFinished alignStarted + librariesCountsMap } } `;