Skip to content

Commit

Permalink
Merge pull request #267 from KPMP/develop
Browse files Browse the repository at this point in the history
Atlas 2024 Q3 Release
  • Loading branch information
zwright authored Oct 2, 2024
2 parents bd36854 + 756043a commit c855f7d
Show file tree
Hide file tree
Showing 8 changed files with 535 additions and 223 deletions.
15 changes: 14 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hubble-web",
"version": "2.4.0",
"version": "2.5.0",
"private": true,
"homepage": "/spatial-viewer",
"baseURL": "/spatial-viewer",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/components/SpatialViewer/ImageDatasetList.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ class ImageDatasetList extends Component {
<span
onClick={()=>{
this.props.clearFilters()
this.props.clearSearch()
}}>
<FontAwesomeIcon alt="Clear All Filters" className="fa-light fa-trash-can" icon={faTrashCan} /> Clear Filters
</span>
Expand Down
8 changes: 7 additions & 1 deletion src/components/SpatialViewer/ImageDatasetListSubContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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}
Expand Down
179 changes: 179 additions & 0 deletions src/components/SpatialViewer/segmentationViewConfig.json
Original file line number Diff line number Diff line change
@@ -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": "<SEGMENTATION_MASK_NAME>",
"url": "<SEGMENTATION_MASK_URL>",
"fileType": "obsSegmentations.ome-tiff",
"options": {
"obsTypesFromChannelNames": true,
"coordinateTransformations": [
{
"type": "scale",
"scale": [
"<PHYSICAL_SIZE_X>",
"<PHYSICAL_SIZE_Y>",
1,
1,
1
]
}
]
},
"coordinationValues": {
"fileUid": "<SEGMENTATION_MASK_NAME>"
}
},
{
"name": "<IMAGE_NAME>",
"url": "<IMAGE_URL>",
"fileType": "image.ome-tiff",
"coordinationValues": {
"fileUid": "<IMAGE_NAME>"
}
}
]
}
],
"coordinationSpace": {
"dataset": {
"A": "A"
},
"spatialChannelOpacity": "<SPATIAL_CHANNEL_OPACITY>",
"imageLayer": {
"A": "<IMAGE_NAME>"
},
"fileUid": {
"A": "<IMAGE_NAME>",
"B": "<SEGMENTATION_MASK_NAME>"
},
"photometricInterpretation": {
"A": "RGB"
},
"spatialTargetC": "<SPATIAL_TARGET_C>",
"spatialChannelColor": "<SPATIAL_CHANNEL_COLOR>",
"spatialChannelVisible": "<SPATIAL_CHANNEL_VISIBLE>",
"spatialSegmentationFilled": "<FILLED>",
"spatialSegmentationStrokeWidth": "<STROKE_WIDTH>",
"segmentationLayer": {
"A": "Segmentation Masks"
},
"segmentationChannel": "<SEGMENTATION_CHANNEL>",
"obsType": "<OBS_TYPE>",
"obsColorEncoding": "<OBS_COLOR_ENCODING>",
"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": "<COORD_SEGMENTATION_CHANNEL>"
},
"segmentationChannel": {
"obsType": "<COORD_OBS_TYPE>",
"spatialTargetC": "<COORD_SPATIAL_TARGET_C>",
"spatialChannelColor": "<COORD_SPATIAL_CHANNEL_COLOR>",
"spatialChannelOpacity": "<COORD_SPATIAL_CHANNEL_OPACITY>",
"spatialChannelVisible": "<COORD_SPATIAL_CHANNEL_VISIBLE>",
"spatialSegmentationFilled": "<COORD_FILLED>",
"spatialSegmentationStrokeWidth": "<COORD_STROKE_WIDTH>",
"obsColorEncoding": "<COORD_OBS_COLOR_ENCODING>"
}
}
}
},
"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"
}
96 changes: 95 additions & 1 deletion src/components/SpatialViewer/viewConfigHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
Expand All @@ -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('"<PHYSICAL_SIZE_X>"', physicalSizeX);
stringifiedConfig = stringifiedConfig.replace('"<PHYSICAL_SIZE_Y>"', 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('"<SPATIAL_CHANNEL_OPACITY>"', JSON.stringify(spatialChannelOpacity))
.replace('"<SPATIAL_TARGET_C>"', JSON.stringify(spatialTargetC))
.replace('"<SPATIAL_CHANNEL_COLOR>"', JSON.stringify(spatialChannelColor))
.replace('"<SPATIAL_CHANNEL_VISIBLE>"', JSON.stringify(spatialChannelVisible))
.replace('"<SEGMENTATION_CHANNEL>"', JSON.stringify(segmentationChannel))
.replace('"<OBS_TYPE>"', JSON.stringify(segmentationChannel))
.replace('"<FILLED>"', JSON.stringify(filled))
.replace('"<STROKE_WIDTH>"', JSON.stringify(strokeWidth))
.replace('"<OBS_COLOR_ENCODING>"', JSON.stringify(obsColorEncoding))
.replace('"<COORD_SEGMENTATION_CHANNEL>"', JSON.stringify(coordSegmentationChannel))
.replace('"<COORD_OBS_TYPE>"', JSON.stringify(coordsArray))
.replace('"<COORD_FILLED>"', JSON.stringify(coordsArray))
.replace('"<COORD_STROKE_WIDTH>"', JSON.stringify(coordsArray))
.replace('"<COORD_SPATIAL_TARGET_C>"', JSON.stringify(coordSpatialTargetC))
.replace('"<COORD_SPATIAL_CHANNEL_COLOR>"', JSON.stringify(coordSpatialTargetC))
.replace('"<COORD_SPATIAL_CHANNEL_OPACITY>"', JSON.stringify(coordSpatialTargetC))
.replace('"<COORD_SPATIAL_CHANNEL_VISIBLE>"', JSON.stringify(coordSpatialTargetC))
.replace('"<COORD_OBS_COLOR_ENCODING>"', JSON.stringify(coordsArray))
return stringifiedConfig;
}



export const populateViewConfig = async (viewConfig, selectedDataset) => {
Expand All @@ -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(/<DATA_FILE_URL>/gi, dataUrl);

if (selectedDataset["configtype"] === "Segmentation Masks & Pathomics Vectors") {
stringifiedConfig = stringifiedConfig.replace('<SEGMENTATION_MASK_NAME>', selectedDataset["filename"]);
stringifiedConfig = stringifiedConfig.replace('<SEGMENTATION_MASK_URL>', imageUrlResponse.data);
stringifiedConfig = await populateSegmentationConfig(stringifiedConfig, dataUrl.data, imageUrlResponse.data);
selectedDataset = relatedFiles[0];
imageUrlResponse = dataUrl;
}
}
stringifiedConfig = stringifiedConfig.replace('<IMAGE_NAME>', selectedDataset["filename"]);
stringifiedConfig = stringifiedConfig.replace('<IMAGE_URL>', imageUrlResponse.data);
Expand Down
Loading

0 comments on commit c855f7d

Please sign in to comment.