Skip to content
Snippets Groups Projects
Commit d112bfc5 authored by Xiao Gui's avatar Xiao Gui
Browse files

feat: map preview auto threshold

feat: introducing other color maps than jet
parent 3af19128
No related branches found
No related tags found
No related merge requests found
......@@ -2,4 +2,7 @@
## New feature
- update dataset preview functionality, allow the previewing of png
\ No newline at end of file
- update dataset preview functionality, allow the previewing of png
- improved the previewing of maps
- parse min and max, if these metadata are provided
- allowing for color maps other than jet
......@@ -11,6 +11,8 @@ import { HttpClient } from "@angular/common/http"
import { DS_PREVIEW_URL, getShader, PMAP_DEFAULT_CONFIG } from 'src/util/constants'
import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, INgLayerInterface } from "./services/state/ngViewerState.store.helper"
import { ARIA_LABELS } from 'common/constants'
import { NgLayersService } from "src/ui/layerbrowser/ngLayerService.service"
import { EnumColorMapName } from "./util/colorMaps"
const PREVIEW_FILE_TYPES_NO_UI = [
EnumPreviewFileTypes.NIFTI,
......@@ -165,6 +167,7 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{
constructor(
private store$: Store<any>,
private http: HttpClient,
private layersService: NgLayersService,
@Optional() @Inject(ACTION_TO_WIDGET_TOKEN) private actionOnWidget: TypeActionToWidget<any>
){
if (!this.actionOnWidget) console.warn(`actionOnWidget not provided in DatasetPreviewGlue. Did you forget to provide it?`)
......@@ -227,31 +230,47 @@ export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{
).subscribe(([ { prvToShow, prvToDismiss }, templateSelected ]) => {
// TODO consider where to check validity of previewed nifti file
for (const prv of prvToShow) {
const { url, filename, volumeMetadata = {} } = prv
const { min, max } = volumeMetadata || {}
const { url, filename, name, volumeMetadata = {} } = prv
const { min, max, colormap = EnumColorMapName.VIRIDIS } = volumeMetadata || {}
const previewFileId = DatasetPreviewGlue.GetDatasetPreviewId(prv)
const shaderObj = {
...PMAP_DEFAULT_CONFIG,
...{ colormap },
...( typeof min !== 'undefined' ? { lowThreshold: min } : {} ),
...( max ? { highThreshold: max } : { highThreshold: 1 } )
}
const layer = {
name: filename,
// name: filename,
name: name || filename,
id: previewFileId,
source : `nifti://${url}`,
mixability : 'nonmixable',
shader : getShader({
...PMAP_DEFAULT_CONFIG,
...( typeof min !== 'undefined' ? { lowThreshold: min } : {} ),
...( max ? { highThreshold: max } : {} )
}),
shader : getShader(shaderObj),
annotation: `${DATASET_PREVIEW_ANNOTATION} ${filename}`
}
const { name: layerName } = layer
const { colormap: cmap, lowThreshold, highThreshold, removeBg } = shaderObj
this.layersService.highThresholdMap.set(layerName, highThreshold)
this.layersService.lowThresholdMap.set(layerName, lowThreshold)
this.layersService.colorMapMap.set(layerName, cmap)
this.layersService.removeBgMap.set(layerName, removeBg)
this.store$.dispatch(
ngViewerActionAddNgLayer({ layer })
)
}
for (const prv of prvToDismiss) {
const { url, filename } = prv
const { url, filename, name } = prv
const previewFileId = DatasetPreviewGlue.GetDatasetPreviewId(prv)
const layer = {
name: filename,
name: name || filename,
id: previewFileId,
source : `nifti://${url}`,
mixability : 'nonmixable',
......
......@@ -2,7 +2,7 @@ import { Component, Input, OnChanges, ChangeDetectionStrategy, Optional, Inject
import { NgLayersService } from "../ngLayerService.service";
import { MatSliderChange } from "@angular/material/slider";
import { MatSlideToggleChange } from "@angular/material/slide-toggle";
import { COLORMAP_IS_JET, getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants";
import { getShader } from "src/util/constants";
export const VIEWER_INJECTION_TOKEN = `VIEWER_INJECTION_TOKEN`
......@@ -28,19 +28,12 @@ export class LayerDetailComponent implements OnChanges{
ngOnChanges(){
if (!this.layerName) return
const isPmap = (this.fragmentMain.value as string).includes(COLORMAP_IS_JET)
const { colormap, lowThreshold, removeBg } = PMAP_DEFAULT_CONFIG
if (isPmap) {
this.colormap = colormap
this.lowThreshold = lowThreshold
this.removeBg = removeBg
}
this.lowThreshold = this.layersService.lowThresholdMap.get(this.layerName) || this.lowThreshold
this.highThreshold = this.layersService.highThresholdMap.get(this.layerName) || this.highThreshold
this.brightness = this.layersService.brightnessMap.get(this.layerName) || this.brightness
this.contrast = this.layersService.contrastMap.get(this.layerName) || this.contrast
this.removeBg = this.layersService.removeBgMap.get(this.layerName) || this.removeBg
this.colormap = this.layersService.colorMapMap.get(this.layerName) || this.colormap
}
public lowThreshold: number = 0
......
import { Injectable } from "@angular/core";
import { EnumColorMapName } from "src/util/colorMaps";
@Injectable({
providedIn: 'root'
......@@ -10,4 +11,5 @@ export class NgLayersService{
public brightnessMap: Map<string, number> = new Map()
public contrastMap: Map<string, number> = new Map()
public removeBgMap: Map<string, boolean> = new Map()
public colorMapMap: Map<string, EnumColorMapName> = new Map()
}
......@@ -69,7 +69,7 @@
</div>
<div [hidden]>
<div class="d-none">
<layer-browser
(nonBaseLayersChanged)="handleNonbaseLayerEvent($event)"
#layerBrowser>
......
export const COLORMAP_IS_DEFAULT = `// iav-colormap-default`
export const COLORMAP_IS_JET = `// iav-colormap-is-jet`
export const COLORMAP_IS_VIRIDIS = `// iav-colormap-is-viridis`
export const COLORMAP_IS_MAGMA = `// iav-colormap-is-magma`
export const COLORMAP_IS_PLASMA = `// iav-colormap-is-plasma`
export const COLORMAP_IS_INFERNO = `// iav-colormap-is-inferno`
export const COLORMAP_IS_GREYSCALE = `// iav-colormap-is-greyscale`
export enum EnumColorMapName{
JET='jet',
VIRIDIS='viridis',
PLASMA='plasma',
MAGMA='magma',
INFERNO='inferno',
GREYSCALE='greyscale',
}
interface IColorMap{
/**
* header
*/
header: string
/**
* appended before void main() {} block
*/
premain: string
/**
* appended in void main(){} block
*
* input:
*
* float x;
*
* populate:
*
* vec3 rgb;
*/
main: string
}
export const mapKeyColorMap = new Map<EnumColorMapName, IColorMap>([
[ EnumColorMapName.JET, {
header: COLORMAP_IS_JET,
/**
* The MIT License (MIT)
Copyright (c) 2015 kbinani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* <https://github.com/kbinani/colormap-shaders/blob/master/shaders/glsl/MATLAB_jet.frag>
*/
premain: `
float colormap_red(float x) {
if (x < 0.7) {
return 4.0 * x - 1.5;
} else {
return -4.0 * x + 4.5;
}
}
float colormap_green(float x) {
if (x < 0.5) {
return 4.0 * x - 0.5;
} else {
return -4.0 * x + 3.5;
}
}
float colormap_blue(float x) {
if (x < 0.3) {
return 4.0 * x + 0.5;
} else {
return -4.0 * x + 2.5;
}
}
`,
main: `
float r = clamp(colormap_red(x), 0.0, 1.0);
float g = clamp(colormap_green(x), 0.0, 1.0);
float b = clamp(colormap_blue(x), 0.0, 1.0);
rgb=vec3(r,g,b);
`
} ],
[ EnumColorMapName.VIRIDIS, {
header: COLORMAP_IS_VIRIDIS,
/**
* created by mattz CC/0
* https://www.shadertoy.com/view/WlfXRN
*/
premain: `
vec3 viridis(float t) {
const vec3 c0 = vec3(0.2777273272234177, 0.005407344544966578, 0.3340998053353061);
const vec3 c1 = vec3(0.1050930431085774, 1.404613529898575, 1.384590162594685);
const vec3 c2 = vec3(-0.3308618287255563, 0.214847559468213, 0.09509516302823659);
const vec3 c3 = vec3(-4.634230498983486, -5.799100973351585, -19.33244095627987);
const vec3 c4 = vec3(6.228269936347081, 14.17993336680509, 56.69055260068105);
const vec3 c5 = vec3(4.776384997670288, -13.74514537774601, -65.35303263337234);
const vec3 c6 = vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832);
return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
}
`,
main: 'rgb=viridis(x);'
} ],
[ EnumColorMapName.PLASMA, {
header: COLORMAP_IS_PLASMA,
/**
* created by mattz CC/0
* https://www.shadertoy.com/view/WlfXRN
*/
premain: `
vec3 plasma(float t) {
const vec3 c0 = vec3(0.05873234392399702, 0.02333670892565664, 0.5433401826748754);
const vec3 c1 = vec3(2.176514634195958, 0.2383834171260182, 0.7539604599784036);
const vec3 c2 = vec3(-2.689460476458034, -7.455851135738909, 3.110799939717086);
const vec3 c3 = vec3(6.130348345893603, 42.3461881477227, -28.51885465332158);
const vec3 c4 = vec3(-11.10743619062271, -82.66631109428045, 60.13984767418263);
const vec3 c5 = vec3(10.02306557647065, 71.41361770095349, -54.07218655560067);
const vec3 c6 = vec3(-3.658713842777788, -22.93153465461149, 18.19190778539828);
return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
}
`,
main: 'rgb=plasma(x);'
} ],
[ EnumColorMapName.MAGMA, {
header: COLORMAP_IS_MAGMA,
/**
* created by mattz CC/0
* https://www.shadertoy.com/view/WlfXRN
*/
premain: `
vec3 magma(float t) {
const vec3 c0 = vec3(-0.002136485053939582, -0.000749655052795221, -0.005386127855323933);
const vec3 c1 = vec3(0.2516605407371642, 0.6775232436837668, 2.494026599312351);
const vec3 c2 = vec3(8.353717279216625, -3.577719514958484, 0.3144679030132573);
const vec3 c3 = vec3(-27.66873308576866, 14.26473078096533, -13.64921318813922);
const vec3 c4 = vec3(52.17613981234068, -27.94360607168351, 12.94416944238394);
const vec3 c5 = vec3(-50.76852536473588, 29.04658282127291, 4.23415299384598);
const vec3 c6 = vec3(18.65570506591883, -11.48977351997711, -5.601961508734096);
return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
}
`,
main: 'rgb=magma(x);'
} ],
[ EnumColorMapName.INFERNO, {
header: '',
/**
* created by mattz CC/0
* https://www.shadertoy.com/view/WlfXRN
*/
premain: `
vec3 inferno(float t) {
const vec3 c0 = vec3(0.0002189403691192265, 0.001651004631001012, -0.01948089843709184);
const vec3 c1 = vec3(0.1065134194856116, 0.5639564367884091, 3.932712388889277);
const vec3 c2 = vec3(11.60249308247187, -3.972853965665698, -15.9423941062914);
const vec3 c3 = vec3(-41.70399613139459, 17.43639888205313, 44.35414519872813);
const vec3 c4 = vec3(77.162935699427, -33.40235894210092, -81.80730925738993);
const vec3 c5 = vec3(-71.31942824499214, 32.62606426397723, 73.20951985803202);
const vec3 c6 = vec3(25.13112622477341, -12.24266895238567, -23.07032500287172);
return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
}
`,
main: 'rgb=inferno(x);'
} ],
[ EnumColorMapName.GREYSCALE, {
header: COLORMAP_IS_GREYSCALE,
premain: '',
main: 'rgb=vec3(x, x, x);'
} ]
])
......@@ -68,40 +68,41 @@ export const getHttpHeader: () => HttpHeaders = () => {
return header
}
const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;`
const CM_DEFAULT = `float r = x; float g = x; float b = x;`
export const COLORMAP_IS_JET = `// iav-colormap-is-jet`
export const COLORMAP_IS_DEFAULT = `// iav-colormap-default`
import { EnumColorMapName, mapKeyColorMap } from './colorMaps'
export const getShader = ({
colormap = null,
colormap = EnumColorMapName.GREYSCALE,
lowThreshold = 0,
highThreshold = 1,
brightness = 0,
contrast = 0,
removeBg = false
} = {}): string => {
const header = colormap === 'jet' ? COLORMAP_IS_JET : COLORMAP_IS_DEFAULT
const colormapGlsl = colormap === 'jet' ? CM_MATLAB_JET : CM_DEFAULT
const { header, main, premain } = mapKeyColorMap.get(colormap) || (() => {
console.warn(`colormap ${colormap} not found. Using default colormap instead`)
return mapKeyColorMap.get(EnumColorMapName.GREYSCALE)
})()
// so that if lowthreshold is defined to be 0, at least some background removal will be done
const _lowThreshold = lowThreshold + 1e-10
return `${header}
${premain}
void main() {
float raw_x = toNormalized(getDataValue());
float x = (raw_x - ${_lowThreshold.toFixed(10)}) / (${highThreshold - _lowThreshold}) ${ brightness > 0 ? '+' : '-' } ${Math.abs(brightness).toFixed(10)};
${ removeBg ? 'if(x>1.0){emitTransparent();}else if(x<0.0){emitTransparent();}else{' : '' }
${colormapGlsl}
emitRGB(vec3(r, g, b)*exp(${contrast.toFixed(10)}));
vec3 rgb;
${main}
emitRGB(rgb*exp(${contrast.toFixed(10)}));
${ removeBg ? '}' : '' }
}
`
}
export const PMAP_DEFAULT_CONFIG = {
colormap: 'jet',
colormap: EnumColorMapName.VIRIDIS,
lowThreshold: 0.05,
removeBg: true
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment