Skip to content

Commit 3e95d77

Browse files
authored
Merge pull request #83 from JaneliaSciComp/add_alignment_download_to_search_results_if_present
add alignment download to search results if present
2 parents 29d2455 + 72298b2 commit 3e95d77

12 files changed

Lines changed: 137 additions & 28 deletions

‎schema.graphql‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ input UpdateSearchInput {
214214
anatomicalRegion: String
215215
cdsFinished: AWSDateTime
216216
cdsStarted: AWSDateTime
217+
channel: Int
217218
completedBatches: Int
218219
computedMIPs: [String]
219220
dataThreshold: Int

‎src/components/CustomSearch/ColorDepthSearchStep.jsx‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import React from "react";
1+
import React, { useContext } from "react";
22
import PropTypes from "prop-types";
33
import { formatRelative, formatDistanceStrict } from "date-fns";
44
import StepTitle from "./StepTitle";
5+
import LibraryFormatter from "../LibraryFormatter";
6+
import { AppContext } from "../../containers/AppContext";
57

6-
export default function ColorDepthSearchStep({ started, finished, state }) {
8+
export default function ColorDepthSearchStep({ started, finished, state, librariesCountsMap }) {
9+
const { appState } = useContext(AppContext);
710
let searchEnd = "";
811
let duration = "";
912

@@ -14,11 +17,25 @@ export default function ColorDepthSearchStep({ started, finished, state }) {
1417
}
1518
}
1619

20+
const hasLibraries = librariesCountsMap && Object.keys(librariesCountsMap).length > 0;
21+
1722
return (
1823
<>
1924
<StepTitle state={state} step={4} text="Color Depth Search" />
2025
<p style={{ marginTop: "1em" }}>{searchEnd}</p>
2126
<p style={{ marginTop: "1em" }}>{duration}</p>
27+
{hasLibraries ? (
28+
<>
29+
<p style={{ marginTop: "1em" }}>Libraries searched:</p>
30+
{Object.entries(librariesCountsMap).map(([library, count]) => (
31+
<p key={library} style={{ marginTop: "0.5em", marginLeft: "1em" }}>
32+
<LibraryFormatter type={library} /> ({count})
33+
</p>
34+
))}
35+
</>
36+
) : appState.debug ? (
37+
<p style={{ marginTop: "1em", fontStyle: "italic", color: "#999" }}>Libraries data not available for this search</p>
38+
) : null}
2239
</>
2340
);
2441
}
@@ -27,9 +44,11 @@ ColorDepthSearchStep.propTypes = {
2744
state: PropTypes.string.isRequired,
2845
finished: PropTypes.string,
2946
started: PropTypes.string,
47+
librariesCountsMap: PropTypes.object,
3048
};
3149

3250
ColorDepthSearchStep.defaultProps = {
3351
finished: null,
34-
started: null
52+
started: null,
53+
librariesCountsMap: null,
3554
};

‎src/components/CustomSearch/ImageAlignmentStep.jsx‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ import HelpButton from "../Help/HelpButton";
1010
export default function ImageAlignmentStep({ state, search }) {
1111
const [thumbnailUrl, setThumbnailUrl] = useState(null);
1212
const [movieUrl, setMovieUrl] = useState(null);
13+
const [alignedVolume, setAlignmentFile] = useState(null);
14+
15+
16+
useEffect(() =>{
17+
if (search.alignedVolume) {
18+
const alignedVolumeUrl = `${search.searchDir}/${search.alignedVolume}`;
19+
signedLink(alignedVolumeUrl).then((result) => {
20+
setAlignmentFile(result);
21+
});
22+
} else {
23+
setAlignmentFile(null);
24+
}
25+
}, [search.searchDir, search.alignedVolume]);
1326

1427
useEffect(() => {
1528
if (search.alignmentMovie) {
@@ -80,6 +93,16 @@ export default function ImageAlignmentStep({ state, search }) {
8093
) : (
8194
""
8295
)}
96+
97+
{alignedVolume ? (
98+
<div>
99+
<a href={alignedVolume} download>
100+
Download Alignment File
101+
</a>
102+
</div>
103+
) : (
104+
""
105+
)}
83106
</>
84107
);
85108
} else if (search.alignmentErrorMessage || search.errorMessage) {

‎src/components/CustomSearch/SearchSteps.jsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ export default function SearchSteps({ search }) {
3434
currentStep = 5;
3535
}
3636

37+
let librariesCountsMap = null;
38+
if (search.librariesCountsMap) {
39+
try {
40+
librariesCountsMap = JSON.parse(search.librariesCountsMap);
41+
} catch (error) {
42+
console.error("Failed to parse librariesCountsMap:", error);
43+
}
44+
}
45+
3746
return (
3847
<div className="searchSteps">
3948
<Row>
@@ -67,6 +76,7 @@ export default function SearchSteps({ search }) {
6776
started={search.cdsStarted}
6877
finished={search.cdsFinished}
6978
state={stepState(3, currentStep, errorMessage)}
79+
librariesCountsMap={librariesCountsMap}
7080
/>
7181
</Col>
7282
<Col xs={4}>

‎src/components/MaskSelection.jsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default function MaskSelection({ match }) {
127127
});
128128
}
129129

130-
const maskDetails = { searchMask: maskName, id: searchMeta.id };
130+
const maskDetails = { searchMask: maskName, channel, id: searchMeta.id };
131131
const searchParams = { ...searchFormData, ...maskDetails };
132132
API.graphql(
133133
graphqlOperation(mutations.updateSearch, { input: searchParams }),
@@ -216,7 +216,7 @@ export default function MaskSelection({ match }) {
216216
wrapperCol={{ span: 16 }}
217217
name="basic"
218218
initialValues={{
219-
selectedLibraries: searchMeta.searchType || getDefaultLibrary(appState?.dataConfig?.stores, searchMeta.anatomicalRegion) || undefined,
219+
selectedLibraries: [searchMeta.searchType || getDefaultLibrary(appState?.dataConfig?.stores, searchMeta.anatomicalRegion)].filter(Boolean),
220220
dataThreshold: searchMeta.dataThreshold || 100,
221221
maskThreshold: searchMeta.maskThreshold || 100,
222222
xyShift: searchMeta.xyShift || 0,

‎src/components/MatchModal/Download3D.jsx‎

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useState, useEffect } from "react";
22
import PropTypes from "prop-types";
33
import { useParams } from "react-router-dom";
44
import { Row, Col, Typography } from "antd";
@@ -7,6 +7,7 @@ import FileExclamationOutlined from "@ant-design/icons/FileExclamationOutlined";
77
import { faExternalLink } from "@fortawesome/pro-regular-svg-icons";
88
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
99
import ViewIn3DButton from "./ViewIn3DButton";
10+
import { signedLink } from "../../libs/awsLib";
1011

1112
const { Title, Text, Paragraph } = Typography;
1213

@@ -39,6 +40,18 @@ function getH5JLink(construct) {
3940
export default function Download3D(props) {
4041
const { selectedMatch, mask, isLM } = props;
4142
const { algorithm } = useParams();
43+
const [alignedVolumeUrl, setAlignedVolumeUrl] = useState(null);
44+
45+
useEffect(() => {
46+
if (mask.alignedVolume && mask.searchDir) {
47+
const volumePath = `${mask.searchDir}/${mask.alignedVolume}`;
48+
signedLink(volumePath).then((result) => {
49+
setAlignedVolumeUrl(result);
50+
});
51+
} else {
52+
setAlignedVolumeUrl(null);
53+
}
54+
}, [mask.searchDir, mask.alignedVolume]);
4255

4356
// If the match has either an AlignedBodySWC or a VisuallyLosslessStack
4457
// file, then we need to make it available for download, the same needs
@@ -53,11 +66,20 @@ export default function Download3D(props) {
5366
const downloadLinks = (
5467
<>
5568
{!mask.precomputed ? (
56-
<Text type="danger">
57-
<FileExclamationOutlined style={fileIconStyles} /> We don&apos;t have
58-
a 3D representation of your uploaded file. You will need to generate
59-
one from your original imagery.
60-
</Text>
69+
alignedVolumeUrl ? (
70+
<Paragraph>
71+
<FileImageOutlined style={fileIconStyles} />{" "}
72+
<a href={alignedVolumeUrl} download>
73+
Download Aligned Volume (H5J)
74+
</a>
75+
</Paragraph>
76+
) : (
77+
<Text type="danger">
78+
<FileExclamationOutlined style={fileIconStyles} /> We don&apos;t have
79+
a 3D representation of your uploaded file. You will need to generate
80+
one from your original imagery.
81+
</Text>
82+
)
6183
) : (
6284
""
6385
)}
@@ -75,7 +97,7 @@ export default function Download3D(props) {
7597

7698
return (
7799
<>
78-
{algorithm !== "pppm" && !mask.identityId ? (
100+
{algorithm !== "pppm" && (!mask.identityId || mask.alignedVolume) ? (
79101
<p>
80102
<ViewIn3DButton isLM={isLM} mask={mask} match={selectedMatch} /> -
81103
View the match in our online volume viewer{" "}

‎src/components/MatchModal/Summary.jsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function Summary(props) {
5757
>
5858
{appState.compactMeta ? "Show" : "Hide"} Match Info
5959
</Button>
60-
{algorithm !== "pppm" && !mask.identityId ? (
60+
{algorithm !== "pppm" && (!mask.identityId || mask.alignedVolume) ? (
6161
<ViewIn3DButton
6262
style={{ float: "right", marginRight: "0.5em" }}
6363
isLM={isLM}

‎src/components/MatchModal/ViewIn3DButton.jsx‎

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Button } from "antd";
44
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
55
import { faCube } from "@fortawesome/pro-regular-svg-icons";
66
import config from "../../config";
7-
import { signedPublicLink } from "../../libs/awsLib";
7+
import { signedPublicLink, signedLink } from "../../libs/awsLib";
88

99
function getH5JLink(construct) {
1010
const imageStack = construct?.files?.VisuallyLosslessStack;
@@ -18,30 +18,51 @@ function getSWCLink(construct) {
1818

1919
export default function ViewIn3DButton({ isLM, match, mask, style }) {
2020
const [signedSwc, setSignedSwc] = React.useState(undefined);
21+
const [signedH5j, setSignedH5j] = React.useState(undefined);
2122
const [isDisabled, setDisabled] = React.useState(true);
2223
const ref = window.location;
23-
const h5j = isLM ? getH5JLink(match.image) : getH5JLink(mask);
24-
const channel = isLM
25-
? parseInt(match.image.channel, 10)
26-
: parseInt(mask.channel, 10);
24+
const isCustomUpload = Boolean(mask.identityId && mask.alignedVolume);
25+
const h5j = isCustomUpload
26+
? null
27+
: isLM ? getH5JLink(match.image) : getH5JLink(mask);
28+
const channel = isCustomUpload
29+
? parseInt(mask.channel, 10) || 0
30+
: isLM
31+
? parseInt(match.image.channel, 10)
32+
: parseInt(mask.channel, 10);
2733
const { mirrored } = match;
2834

2935
React.useEffect(() => {
30-
const swc = isLM ? getSWCLink(mask) : getSWCLink(match.image);
31-
signedPublicLink(swc).then((signed) => {
32-
setSignedSwc(signed);
33-
});
36+
const swc = isCustomUpload
37+
? getSWCLink(match.image)
38+
: isLM ? getSWCLink(mask) : getSWCLink(match.image);
3439

35-
if (swc && h5j) {
40+
if (swc) {
41+
signedPublicLink(swc).then((signed) => {
42+
setSignedSwc(signed);
43+
});
44+
}
45+
46+
if (isCustomUpload) {
47+
const volumePath = `${mask.searchDir}/${mask.alignedVolume}`;
48+
signedLink(volumePath).then((signed) => {
49+
setSignedH5j(signed);
50+
if (swc) {
51+
setDisabled(false);
52+
}
53+
});
54+
} else if (swc && h5j) {
3655
setDisabled(false);
3756
}
38-
}, [isLM, mask, match, h5j]);
57+
}, [isLM, isCustomUpload, mask, match, h5j]);
58+
59+
const h5jUrl = isCustomUpload ? signedH5j : h5j;
3960

4061
// must encode the signedSWC, so that it makes it to the volume viewer in the
4162
// correct state for loading the swc
4263
const volViewerLink = `${config.volumeViewer}?ref=${encodeURIComponent(
4364
ref
44-
)}&h5j=${encodeURIComponent(h5j)}&swc=${encodeURIComponent(
65+
)}&h5j=${encodeURIComponent(h5jUrl)}&swc=${encodeURIComponent(
4566
signedSwc
4667
)}&ch=${channel}&mx=${mirrored}`;
4768

‎src/components/Results.jsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export default function Results({ match }) {
9595
fr.readAsText(results.Body);
9696
})
9797
.catch(error => {
98-
if (error.response.status === 404) {
98+
if (error.response && error.response.status === 404) {
9999
setSearchResults({ results: [] });
100100
} else {
101101
message.error({

‎src/graphql/mutations.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const updateSearch = /* GraphQL */ `
3939
identityId
4040
owner
4141
step
42+
channel
4243
upload
4344
uploadThumbnail
4445
searchDir

0 commit comments

Comments
 (0)