diff --git a/changelog.md b/changelog.md index 60d2770c..30310025 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,19 @@ # Changelog -## Release 2.4 [unreleased] +## Release 2.5 (unreleased) +Brief summary of what's in this release: + +### Breaking changes +Breaking changes include any database updates needed, if we need to edit any files on system (like .env or certs, etc). Things that are outside of the code itself that need changed for the system to work. + +### Non-breaking changes +Just a place to keep track of things that have changed in the code that we may want to pay special attention to when smoke testing, etc. +- fixed a bug where the participant filters wouldn't go back to their default state after clicking the Clear Filters button + + +---- + +## Release 2.4 (released 07/08/2024) Brief summary of what's in this release: - introduction of this changelog - update the version in the package.json diff --git a/package.json b/package.json index 0c46270c..85b4842d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hubble-web", - "version": "2.4.0", + "version": "2.5.0", "private": true, "homepage": "/spatial-viewer", "baseURL": "/spatial-viewer", @@ -23,10 +23,12 @@ "bootstrap": "5.2.3", "bootstrap-5-css-only": "5.1.3", "history": "5.1.0", + "@hms-dbmi/viv": "0.16.1", "immutability-helper": "3.1.1", "isomorphic-unfetch": "3.1.0", "kpmp-common-components": "1.2.14", "kpmp-common-styles": "1.0.13", + "mathjs": "13.1.0", "react": "17.0.2", "react-dnd": "15.1.1", "react-dnd-html5-backend": "15.1.2", @@ -55,7 +57,7 @@ "scripts": { "start": "npm-run-all -p start-js --max_old_space_size=8192", "build": "npm-run-all build-css build-js", - "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!vitessce|@devexpress|@elastic|react-ga4)'", + "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!vitessce|@devexpress|@elastic|react-ga4|@hms-dbmi|@deck.gl|@vivjs)'", "eject": "react-scripts eject", "start-js": "craco --max_old_space_size=8192 start", "build-js": "craco --max_old_space_size=8192 build", diff --git a/src/components/SpatialViewer/ImageDatasetList.js b/src/components/SpatialViewer/ImageDatasetList.js index 4e2157e9..f19f2cd2 100755 --- a/src/components/SpatialViewer/ImageDatasetList.js +++ b/src/components/SpatialViewer/ImageDatasetList.js @@ -306,6 +306,7 @@ class ImageDatasetList extends Component { { this.props.clearFilters() + this.props.clearSearch() }}> Clear Filters diff --git a/src/components/SpatialViewer/ImageDatasetListSubContainer.js b/src/components/SpatialViewer/ImageDatasetListSubContainer.js index ca26b4ae..104d8927 100755 --- a/src/components/SpatialViewer/ImageDatasetListSubContainer.js +++ b/src/components/SpatialViewer/ImageDatasetListSubContainer.js @@ -12,11 +12,16 @@ class ImageDatasetListSubContainer extends Component { this.state = { activeFilterTab: 'DATASET', reportCardOpen: false, - filterTabActive: true + filterTabActive: true, + search: "" }; } + clearSearch = () => { + this.setState({search: null}) + } + openReportCard = () => { this.setState({reportCardOpen: true}) handleGoogleAnalyticsEvent( @@ -65,6 +70,7 @@ class ImageDatasetListSubContainer extends Component { setResultsPerPage={setResultsPerPage} removeFilter={removeFilter} clearFilters={clearFilters} + clearSearch={this.clearSearch} setActiveFilterTab={this.setActiveFilterTab} activeFilterTab={this.state.activeFilterTab} filterTabActive={this.state.filterTabActive} diff --git a/src/components/SpatialViewer/segmentationViewConfig.json b/src/components/SpatialViewer/segmentationViewConfig.json new file mode 100644 index 00000000..004359e5 --- /dev/null +++ b/src/components/SpatialViewer/segmentationViewConfig.json @@ -0,0 +1,179 @@ +{ + "version": "1.0.16", + "name": "KPMP Spatial Viewer", + "description": "Segmentation Masks & Pathomics Vectors", + "datasets": [ + { + "uid": "A", + "name": "My dataset", + "files": [ + { + "name": "", + "url": "", + "fileType": "obsSegmentations.ome-tiff", + "options": { + "obsTypesFromChannelNames": true, + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + "", + "", + 1, + 1, + 1 + ] + } + ] + }, + "coordinationValues": { + "fileUid": "" + } + }, + { + "name": "", + "url": "", + "fileType": "image.ome-tiff", + "coordinationValues": { + "fileUid": "" + } + } + ] + } + ], + "coordinationSpace": { + "dataset": { + "A": "A" + }, + "spatialChannelOpacity": "", + "imageLayer": { + "A": "" + }, + "fileUid": { + "A": "", + "B": "" + }, + "photometricInterpretation": { + "A": "RGB" + }, + "spatialTargetC": "", + "spatialChannelColor": "", + "spatialChannelVisible": "", + "spatialSegmentationFilled": "", + "spatialSegmentationStrokeWidth": "", + "segmentationLayer": { + "A": "Segmentation Masks" + }, + "segmentationChannel": "", + "obsType": "", + "obsColorEncoding": "", + "metaCoordinationScopes": { + "A": { + "spatialZoom": "A", + "imageLayer": [ + "A" + ], + "segmentationLayer": [ + "A" + ] + } + }, + "metaCoordinationScopesBy": { + "A": { + "imageLayer": { + "fileUid": { + "A": "A" + }, + "photometricInterpretation": { + "A": "A" + }, + "imageChannel": { + "A": [ + "A", + "B", + "C" + ] + } + }, + "imageChannel": { + "spatialTargetC": { + "A": "A", + "B": "B", + "C": "C" + }, + "spatialChannelColor": { + "A": "A", + "B": "B", + "C": "C" + }, + "spatialChannelVisible": { + "A": "A", + "B": "B", + "C": "C" + }, + "spatialChannelOpacity": { + "A": "A", + "B": "B", + "C": "C" + }, + "spatialChannelWindow": { + "A": "A", + "B": "B", + "C": "C" + } + }, + "segmentationLayer": { + "fileUid": { + "A": "B" + }, + "segmentationChannel": "" + }, + "segmentationChannel": { + "obsType": "", + "spatialTargetC": "", + "spatialChannelColor": "", + "spatialChannelOpacity": "", + "spatialChannelVisible": "", + "spatialSegmentationFilled": "", + "spatialSegmentationStrokeWidth": "", + "obsColorEncoding": "" + } + } + } + }, + "layout": [ + { + "component": "spatialBeta", + "coordinationScopes": { + "dataset": "A", + "metaCoordinationScopes": [ + "A" + ], + "metaCoordinationScopesBy": [ + "A" + ] + }, + "x": 0, + "y": 0, + "w": 6, + "h": 12 + }, + { + "component": "layerControllerBeta", + "coordinationScopes": { + "dataset": "A", + "metaCoordinationScopes": [ + "A" + ], + "metaCoordinationScopesBy": [ + "A" + ] + }, + "x": 6, + "y": 0, + "w": 6, + "h": 12 + } + ], + "initStrategy": "auto" +} \ No newline at end of file diff --git a/src/components/SpatialViewer/viewConfigHelper.js b/src/components/SpatialViewer/viewConfigHelper.js index f188fa4a..319cd3b3 100644 --- a/src/components/SpatialViewer/viewConfigHelper.js +++ b/src/components/SpatialViewer/viewConfigHelper.js @@ -2,7 +2,10 @@ import lmViewConfig from './lightMicroscopyViewConfig.json'; import threeDCytometryViewConfig from './threeDCytometryViewConfig.json'; import threeDCytometryViewNoChannelsConfig from './threeDCytometryViewNoChannelsConfig.json'; import stViewConfig from './spatialTranscriptomicsViewConfig.json' +import segmentationConfig from './segmentationViewConfig.json'; import { getFileLink } from "../../helpers/Api"; +import { loadOmeTiff } from '@hms-dbmi/viv'; +import { unit } from 'mathjs'; export const getViewConfig = (type) => { switch (type) { @@ -16,6 +19,8 @@ export const getViewConfig = (type) => { return lmViewConfig; case 'Spatial Transcriptomics': return stViewConfig; + case 'Segmentation Masks & Pathomics Vectors': + return segmentationConfig; default: return threeDCytometryViewConfig } @@ -33,6 +38,84 @@ export const getDatasetInfo = (selectedDataset) => { return datasetInfo; } +const populateSegmentationConfig = async (stringifiedConfig, wsiUrl, maskUrl) => { + const wsiLoader = await loadOmeTiff(wsiUrl); + const maskLoader = await loadOmeTiff(maskUrl); + const physicalSizeX = unit(wsiLoader.metadata.Pixels.PhysicalSizeX, (wsiLoader.metadata.Pixels.PhysicalSizeXUnit.replace(/[µ|?]/g, 'u'))).to("um").toNumber(); + const physicalSizeY = unit(wsiLoader.metadata.Pixels.PhysicalSizeY, (wsiLoader.metadata.Pixels.PhysicalSizeYUnit.replace(/[µ|?]/g, 'u'))).to("um").toNumber(); + stringifiedConfig = stringifiedConfig.replace('""', physicalSizeX); + stringifiedConfig = stringifiedConfig.replace('""', physicalSizeY); + + let spatialChannelOpacity = {"A": 1, "B": 1, "C": 1}; + let spatialTargetC = {"A": 0, "B": 1, "C": 2}; + let spatialChannelColor = {"A": [255,0,0], "B": [0,255,0], "C": [0,0,255]}; + let spatialChannelVisible = {"A": true, "B": true, "C": true}; + var segmentationChannel = {}, obsColorEncoding = {}, filled = {}, + strokeWidth = {}, coordsArray = {}, coordSpatialTargetC = {}; + let coordSegmentationChannel = {"A": []}; + maskLoader.metadata.Pixels.Channels.forEach((channel, i) => { + let indexFromA = String.fromCharCode(65+i); + let indexFromD = String.fromCharCode(68+i); + spatialChannelOpacity[indexFromD] = 0.75; + spatialTargetC[indexFromD] = i; + let color = []; + switch (channel.Name.toLowerCase()) { + case "non-globally-sclerotic glomeruli": + color = [239, 226, 82]; // yellow + break; + case "globally-sclerotic glomeruli": + color = [228, 158, 37]; // light orange + break; + case "tubules": + color = [91, 181, 231]; // light blue + break; + case "arteries/arterioles": + color = [202, 122, 166]; // pink + break; + case "ptc": + case "peritubular-capillaries": + color = [22, 157, 116]; // green + break; + case "ifta": + case "interstitial fibrosis and tubular atrophy": + color = [211, 94, 26]; // dark orange + break; + case "cortex": + default: + color = [255, 255, 255]; // white + break; + } + spatialChannelColor[indexFromD] = color; + spatialChannelVisible[indexFromD] = false; + segmentationChannel[indexFromA] = channel.Name; + obsColorEncoding[indexFromA] = "spatialChannelColor"; + filled[indexFromA] = true; + strokeWidth[indexFromA] = 1; + coordSegmentationChannel["A"].push(indexFromA); + coordsArray[indexFromA] = indexFromA; + coordSpatialTargetC[indexFromA] = indexFromD; + }) + stringifiedConfig = stringifiedConfig.replace('""', JSON.stringify(spatialChannelOpacity)) + .replace('""', JSON.stringify(spatialTargetC)) + .replace('""', JSON.stringify(spatialChannelColor)) + .replace('""', JSON.stringify(spatialChannelVisible)) + .replace('""', JSON.stringify(segmentationChannel)) + .replace('""', JSON.stringify(segmentationChannel)) + .replace('""', JSON.stringify(filled)) + .replace('""', JSON.stringify(strokeWidth)) + .replace('""', JSON.stringify(obsColorEncoding)) + .replace('""', JSON.stringify(coordSegmentationChannel)) + .replace('""', JSON.stringify(coordsArray)) + .replace('""', JSON.stringify(coordsArray)) + .replace('""', JSON.stringify(coordsArray)) + .replace('""', JSON.stringify(coordSpatialTargetC)) + .replace('""', JSON.stringify(coordSpatialTargetC)) + .replace('""', JSON.stringify(coordSpatialTargetC)) + .replace('""', JSON.stringify(coordSpatialTargetC)) + .replace('""', JSON.stringify(coordsArray)) + return stringifiedConfig; +} + export const populateViewConfig = async (viewConfig, selectedDataset) => { @@ -43,8 +126,19 @@ export const populateViewConfig = async (viewConfig, selectedDataset) => { selectedDataset['relatedfiles'].forEach(function (item, index) { relatedFiles.push(JSON.parse(item)); }); - let dataUrl = getPublicFileLink(selectedDataset["packageid"], relatedFiles[0]['filename']); + let ext = relatedFiles[0]['filename'].split('.').pop(); + let dataUrl = (ext === "zarr") + ? getPublicFileLink(selectedDataset["packageid"], relatedFiles[0]['filename']) + : await getFileLink(relatedFiles[0]['packageid'] + "/" + relatedFiles[0]['filename']); stringifiedConfig = stringifiedConfig.replace(//gi, dataUrl); + + if (selectedDataset["configtype"] === "Segmentation Masks & Pathomics Vectors") { + stringifiedConfig = stringifiedConfig.replace('', selectedDataset["filename"]); + stringifiedConfig = stringifiedConfig.replace('', imageUrlResponse.data); + stringifiedConfig = await populateSegmentationConfig(stringifiedConfig, dataUrl.data, imageUrlResponse.data); + selectedDataset = relatedFiles[0]; + imageUrlResponse = dataUrl; + } } stringifiedConfig = stringifiedConfig.replace('', selectedDataset["filename"]); stringifiedConfig = stringifiedConfig.replace('', imageUrlResponse.data); diff --git a/src/components/SpatialViewer/viewConfigHelper.test-old.js b/src/components/SpatialViewer/viewConfigHelper.test-old.js new file mode 100644 index 00000000..ed4adabb --- /dev/null +++ b/src/components/SpatialViewer/viewConfigHelper.test-old.js @@ -0,0 +1,235 @@ +// import { +// getViewConfig, +// populateViewConfig, +// getDatasetInfo, +// getImageTypeTooltipCopy, +// getPublicFileLink +// } from './viewConfigHelper'; +// import lmViewConfig from './lightMicroscopyViewConfig.json'; +// import threeDCytometryViewConfig from './threeDCytometryViewConfig.json'; +// import threeDCytometryViewNoChannelsConfig from './threeDCytometryViewNoChannelsConfig.json'; +// import stViewConfig from './spatialTranscriptomicsViewConfig.json'; +// import * as helpers from '../../helpers/Api'; + +// export const mockDatabase = { +// collections: { +// get: jest.fn(), +// }, +// }; + +// jest.mock('@hms-dbmi/viv', () => ({ +// ...jest.requireActual('@hms-dbmi/viv'), +// Modal: jest.fn(), +// Database: jest.fn(() => mockDatabase), +// Q: { +// sqlite: { +// openDatabase: jest.fn(), +// }, +// }, +// })); + +// describe('getViewConfig', () => { +// it('should return 3dCyto config when 3D Cytometry', () => { +// let config = getViewConfig('3D Cytometry'); +// let expectedConfig = threeDCytometryViewConfig; + +// expect(config).toEqual(expectedConfig); + +// }); +// it ('should return light microscopy config when Light Microscopic Whole Slide Images', () => { +// let config = getViewConfig('Light Microscopic Whole Slide Images'); +// let expectedConfig = lmViewConfig; + +// expect(config).toEqual(expectedConfig); +// }); +// it ('should return spatial transcriptopmics config when Spatial Transcriptomics', () => { +// let config = getViewConfig('Spatial Transcriptomics'); +// let expectedConfig = stViewConfig; + +// expect(config).toEqual(expectedConfig); +// }); +// it ('should return no channel 3dcyto config when given 3d cyto no channel', () => { +// let config = getViewConfig('3D Tissue Imaging and Cytometry No Channels'); +// let expectedConfig = threeDCytometryViewNoChannelsConfig; + +// expect(config).toEqual(expectedConfig); +// }); +// it ('should return 3dcyto config when CODEX', () => { +// let config = getViewConfig('CODEX'); +// let expectedConfig = threeDCytometryViewConfig; + +// expect(config).toEqual(expectedConfig); +// }); +// it ('should default to 3dcyto when unknown type', () => { +// let config = getViewConfig('garbage'); +// let expectedConfig = threeDCytometryViewConfig; + +// expect(config).toEqual(expectedConfig); +// }); +// }); + +// describe ('populateViewConfig', () => { +// beforeEach(() => { + +// let mockUtilFunction = jest.spyOn(helpers, 'getFileLink').mockImplementation(() => { +// let result = {}; +// result.data='url/returned/from/service'; +// return result; +// }); +// }); + +// it('should replace all of the placeholder values with the values passed in', async () => { +// let selectedDataset = { +// 'filename': 'imageName.tiff', +// 'packageid': '123', +// 'imagetype': 'stuff', +// 'relatedfiles': [] +// }; +// let result = await populateViewConfig(threeDCytometryViewConfig, selectedDataset); +// let resultString = JSON.stringify(result); +// let index = resultString.search('<*>'); + +// expect(index).toBe(-1); + +// expect(result.datasets[0].files[0].options.images[0].name).toEqual('imageName.tiff'); +// expect(result.datasets[0].files[0].options.images[0].url).toEqual('url/returned/from/service'); +// expect(result.description).toEqual('stuff'); +// }); + +// it('should replace all of the placeholder values with the values passed in for spatial transcriptomics', async () => { +// let selectedDataset = { +// 'filename': 'imageName.tiff', +// 'packageid': '123', +// 'imagetype': 'stuff', +// 'relatedfiles': ['{"filename": "file.zarr"}'] +// }; +// let result = await populateViewConfig(stViewConfig, selectedDataset); +// let resultString = JSON.stringify(result); +// let index = resultString.search('<*>'); + +// expect(index).toBe(-1); + +// expect(result.datasets[0].files[2].options.images[0].name).toEqual('imageName.tiff'); +// expect(result.datasets[0].files[2].options.images[0].url).toEqual('url/returned/from/service'); +// expect(result.datasets[0].files[0].url).toEqual('https://kpmp-knowledge-environment-public.s3.amazonaws.com/123/derived/file.zarr'); +// expect(result.datasets[0].files[1].url).toEqual('https://kpmp-knowledge-environment-public.s3.amazonaws.com/123/derived/file.zarr'); +// expect(result.description).toEqual('stuff'); +// }); + +// it('should handle missing Image Type', async () => { +// let selectedDataset = { +// 'filename': 'imageName.tiff', +// 'relatedfiles': [] +// }; +// let result = await populateViewConfig(threeDCytometryViewConfig, selectedDataset); +// let resultString = JSON.stringify(result); +// let index = resultString.search('<*>'); +// expect(index).toBe(-1); +// expect(result.description).toEqual(''); +// }); + +// }); + +// describe ('getDatasetInfo', () => { +// it('should return whole slide image string with level included', () => { +// const selectedDataset = { +// "datatype": "Light Microscopic Whole Slide Images", +// "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", +// "level": "L12" +// } + +// let datasetInfo = getDatasetInfo(selectedDataset); +// let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain (L12)"; + +// expect(datasetInfo).toBe(expectedInfo); +// }); +// it('should return whole slide image string without level included', () => { +// const selectedDataset = { +// "datatype": "Light Microscopic Whole Slide Images", +// "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", +// } + +// let datasetInfo = getDatasetInfo(selectedDataset); +// let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain"; + +// expect(datasetInfo).toBe(expectedInfo); +// }); +// it('should return a Label-free auto-fluorescent image', () => { +// const selectedDataset = { +// "datatype": "Label-free auto-fluorescent image", +// "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", +// } + +// let datasetInfo = getDatasetInfo(selectedDataset); +// let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain"; + +// expect(datasetInfo).toBe(expectedInfo); +// }); +// it('should return an empty string if image type not present for 3d Cyto', () => { +// const selectedDataset = { +// "datatype": "Label-free auto-fluorescent image", +// } + +// let datasetInfo = getDatasetInfo(selectedDataset); +// let expectedInfo = ""; + +// expect(datasetInfo).toBe(expectedInfo); +// }); +// it('should return an empty string if image type not present for Whole slide image', () => { +// const selectedDataset = { +// "datatype": "Light Microscopic Whole Slide Images", +// } + +// let datasetInfo = getDatasetInfo(selectedDataset); +// let expectedInfo = ""; + +// expect(datasetInfo).toBe(expectedInfo); +// }); +// }) + +// describe('getPublicFileLink',() => { +// it('should generate the url', () => { +// let fileLink = getPublicFileLink("12345", "filename"); +// expect(fileLink).toBe('https://kpmp-knowledge-environment-public.s3.amazonaws.com/12345/derived/filename'); +// }); +// }); + +// describe('getImageTypeTooltipCopy',() => { +// it('should return empty when copy not available', () => { +// const expectedCopy = ''; +// const copy = getImageTypeTooltipCopy(''); +// expect(copy).toBe(expectedCopy); +// }); + +// it('should return empty when copy not available', () => { +// const expectedCopy = ''; +// const copy = getImageTypeTooltipCopy('AS(DJ9asdjasd'); +// expect(copy).toBe(expectedCopy); +// }); + +// it('should return copy for RGB max projection of 8-channel immunofluorescence image volume', () => { +// const expectedCopy = '8-channel volume combined into a single maximum projection and converted to RGB color space.'; +// const copy = getImageTypeTooltipCopy('RGB max projection of 8-channel immunofluorescence image volume'); +// expect(copy).toBe(expectedCopy); +// }); + +// it('should return copy for Composite max projection of 8-channel immunofluorescence image volume', () => { +// const expectedCopy = '8-channel volume combined into a single maximum projection; composite image consists of 8 channels.'; +// const copy = getImageTypeTooltipCopy('Composite max projection of 8-channel immunofluorescence image volume'); +// expect(copy).toBe(expectedCopy); +// }); + +// it('should return copy for Composite 3D 8-channel immunofluorescence image volume', () => { +// const expectedCopy = '3D volume completely represented as a stack of individual, 8-channel images. Every focal plane image and every channel can be independently inspected.'; +// const copy = getImageTypeTooltipCopy('Composite 3D 8-channel immunofluorescence image volume'); +// expect(copy).toBe(expectedCopy); +// }); + +// it('should return copy for RGB max projection of 2-channel (autofluorescence and second harmonic generation) image volume', () => { +// const expectedCopy = 'Projection of 3D volume collected prior to labeling; channels cannot be controlled.'; +// const copy = getImageTypeTooltipCopy('RGB max projection of 2-channel (autofluorescence and second harmonic generation) image volume'); +// expect(copy).toBe(expectedCopy); +// }); + +// }); + diff --git a/src/components/SpatialViewer/viewConfigHelper.test.js b/src/components/SpatialViewer/viewConfigHelper.test.js deleted file mode 100644 index 0fc5b552..00000000 --- a/src/components/SpatialViewer/viewConfigHelper.test.js +++ /dev/null @@ -1,218 +0,0 @@ -import { - getViewConfig, - populateViewConfig, - getDatasetInfo, - getImageTypeTooltipCopy, - getPublicFileLink -} from './viewConfigHelper'; -import lmViewConfig from './lightMicroscopyViewConfig.json'; -import threeDCytometryViewConfig from './threeDCytometryViewConfig.json'; -import threeDCytometryViewNoChannelsConfig from './threeDCytometryViewNoChannelsConfig.json'; -import stViewConfig from './spatialTranscriptomicsViewConfig.json'; -import * as helpers from '../../helpers/Api'; - -describe('getViewConfig', () => { - it('should return 3dCyto config when 3D Cytometry', () => { - let config = getViewConfig('3D Cytometry'); - let expectedConfig = threeDCytometryViewConfig; - - expect(config).toEqual(expectedConfig); - - }); - it ('should return light microscopy config when Light Microscopic Whole Slide Images', () => { - let config = getViewConfig('Light Microscopic Whole Slide Images'); - let expectedConfig = lmViewConfig; - - expect(config).toEqual(expectedConfig); - }); - it ('should return spatial transcriptopmics config when Spatial Transcriptomics', () => { - let config = getViewConfig('Spatial Transcriptomics'); - let expectedConfig = stViewConfig; - - expect(config).toEqual(expectedConfig); - }); - it ('should return no channel 3dcyto config when given 3d cyto no channel', () => { - let config = getViewConfig('3D Tissue Imaging and Cytometry No Channels'); - let expectedConfig = threeDCytometryViewNoChannelsConfig; - - expect(config).toEqual(expectedConfig); - }); - it ('should return 3dcyto config when CODEX', () => { - let config = getViewConfig('CODEX'); - let expectedConfig = threeDCytometryViewConfig; - - expect(config).toEqual(expectedConfig); - }); - it ('should default to 3dcyto when unknown type', () => { - let config = getViewConfig('garbage'); - let expectedConfig = threeDCytometryViewConfig; - - expect(config).toEqual(expectedConfig); - }); -}); - -describe ('populateViewConfig', () => { - beforeEach(() => { - - let mockUtilFunction = jest.spyOn(helpers, 'getFileLink').mockImplementation(() => { - let result = {}; - result.data='url/returned/from/service'; - return result; - }); - }); - - it('should replace all of the placeholder values with the values passed in', async () => { - let selectedDataset = { - 'filename': 'imageName.tiff', - 'packageid': '123', - 'imagetype': 'stuff', - 'relatedfiles': [] - }; - let result = await populateViewConfig(threeDCytometryViewConfig, selectedDataset); - let resultString = JSON.stringify(result); - let index = resultString.search('<*>'); - - expect(index).toBe(-1); - - expect(result.datasets[0].files[0].options.images[0].name).toEqual('imageName.tiff'); - expect(result.datasets[0].files[0].options.images[0].url).toEqual('url/returned/from/service'); - expect(result.description).toEqual('stuff'); - }); - - it('should replace all of the placeholder values with the values passed in for spatial transcriptomics', async () => { - let selectedDataset = { - 'filename': 'imageName.tiff', - 'packageid': '123', - 'imagetype': 'stuff', - 'relatedfiles': ['{"filename": "file.zarr"}'] - }; - let result = await populateViewConfig(stViewConfig, selectedDataset); - let resultString = JSON.stringify(result); - let index = resultString.search('<*>'); - - expect(index).toBe(-1); - - expect(result.datasets[0].files[2].options.images[0].name).toEqual('imageName.tiff'); - expect(result.datasets[0].files[2].options.images[0].url).toEqual('url/returned/from/service'); - expect(result.datasets[0].files[0].url).toEqual('https://kpmp-knowledge-environment-public.s3.amazonaws.com/123/derived/file.zarr'); - expect(result.datasets[0].files[1].url).toEqual('https://kpmp-knowledge-environment-public.s3.amazonaws.com/123/derived/file.zarr'); - expect(result.description).toEqual('stuff'); - }); - - it('should handle missing Image Type', async () => { - let selectedDataset = { - 'filename': 'imageName.tiff', - 'relatedfiles': [] - }; - let result = await populateViewConfig(threeDCytometryViewConfig, selectedDataset); - let resultString = JSON.stringify(result); - let index = resultString.search('<*>'); - expect(index).toBe(-1); - expect(result.description).toEqual(''); - }); - -}); - -describe ('getDatasetInfo', () => { - it('should return whole slide image string with level included', () => { - const selectedDataset = { - "datatype": "Light Microscopic Whole Slide Images", - "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", - "level": "L12" - } - - let datasetInfo = getDatasetInfo(selectedDataset); - let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain (L12)"; - - expect(datasetInfo).toBe(expectedInfo); - }); - it('should return whole slide image string without level included', () => { - const selectedDataset = { - "datatype": "Light Microscopic Whole Slide Images", - "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", - } - - let datasetInfo = getDatasetInfo(selectedDataset); - let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain"; - - expect(datasetInfo).toBe(expectedInfo); - }); - it('should return a Label-free auto-fluorescent image', () => { - const selectedDataset = { - "datatype": "Label-free auto-fluorescent image", - "imagetype": "Jones' Methenamine Silver (SIL) histochemical stain", - } - - let datasetInfo = getDatasetInfo(selectedDataset); - let expectedInfo = "Jones' Methenamine Silver (SIL) histochemical stain"; - - expect(datasetInfo).toBe(expectedInfo); - }); - it('should return an empty string if image type not present for 3d Cyto', () => { - const selectedDataset = { - "datatype": "Label-free auto-fluorescent image", - } - - let datasetInfo = getDatasetInfo(selectedDataset); - let expectedInfo = ""; - - expect(datasetInfo).toBe(expectedInfo); - }); - it('should return an empty string if image type not present for Whole slide image', () => { - const selectedDataset = { - "datatype": "Light Microscopic Whole Slide Images", - } - - let datasetInfo = getDatasetInfo(selectedDataset); - let expectedInfo = ""; - - expect(datasetInfo).toBe(expectedInfo); - }); -}) - -describe('getPublicFileLink',() => { - it('should generate the url', () => { - let fileLink = getPublicFileLink("12345", "filename"); - expect(fileLink).toBe('https://kpmp-knowledge-environment-public.s3.amazonaws.com/12345/derived/filename'); - }); -}); - -describe('getImageTypeTooltipCopy',() => { - it('should return empty when copy not available', () => { - const expectedCopy = ''; - const copy = getImageTypeTooltipCopy(''); - expect(copy).toBe(expectedCopy); - }); - - it('should return empty when copy not available', () => { - const expectedCopy = ''; - const copy = getImageTypeTooltipCopy('AS(DJ9asdjasd'); - expect(copy).toBe(expectedCopy); - }); - - it('should return copy for RGB max projection of 8-channel immunofluorescence image volume', () => { - const expectedCopy = '8-channel volume combined into a single maximum projection and converted to RGB color space.'; - const copy = getImageTypeTooltipCopy('RGB max projection of 8-channel immunofluorescence image volume'); - expect(copy).toBe(expectedCopy); - }); - - it('should return copy for Composite max projection of 8-channel immunofluorescence image volume', () => { - const expectedCopy = '8-channel volume combined into a single maximum projection; composite image consists of 8 channels.'; - const copy = getImageTypeTooltipCopy('Composite max projection of 8-channel immunofluorescence image volume'); - expect(copy).toBe(expectedCopy); - }); - - it('should return copy for Composite 3D 8-channel immunofluorescence image volume', () => { - const expectedCopy = '3D volume completely represented as a stack of individual, 8-channel images. Every focal plane image and every channel can be independently inspected.'; - const copy = getImageTypeTooltipCopy('Composite 3D 8-channel immunofluorescence image volume'); - expect(copy).toBe(expectedCopy); - }); - - it('should return copy for RGB max projection of 2-channel (autofluorescence and second harmonic generation) image volume', () => { - const expectedCopy = 'Projection of 3D volume collected prior to labeling; channels cannot be controlled.'; - const copy = getImageTypeTooltipCopy('RGB max projection of 2-channel (autofluorescence and second harmonic generation) image volume'); - expect(copy).toBe(expectedCopy); - }); - - }); -