diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f40ae817ce..db006099ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). ### Added +- Added two new properties to mapping json files. The `colors: [, , ...]` property can be used to specify up to 256 custom colors for the first 256 equivalence classes of the mapping. The `hideUnmappedIds: ` property indicates whether segments that were not mapped should be rendered transparently or not. [#2965](https://github.com/scalableminds/webknossos/pull/2965) - Added a button for refreshing the dataset in the backend cache. [#2975](https://github.com/scalableminds/webknossos/pull/2975) - All dates in webknossos will be shown in the browser's timezone. On hover, a tooltip will show the date in UTC. [#2916](https://github.com/scalableminds/webknossos/pull/2916) ![image](https://user-images.githubusercontent.com/2486553/42888385-74c82bc0-8aa8-11e8-9c3e-7cfc90ce93bc.png) - When merging datasets within a tracing via the merge-modal, the user can choose whether the merge should be executed directly in the currently opened tracing. Alternatively, a new annotation can be created which is accessible via the dashboard (as it has been before). diff --git a/app/assets/javascripts/admin/api_flow_types.js b/app/assets/javascripts/admin/api_flow_types.js index 576dcd53224..847b5ad2ae2 100644 --- a/app/assets/javascripts/admin/api_flow_types.js +++ b/app/assets/javascripts/admin/api_flow_types.js @@ -7,8 +7,6 @@ import type { Vector3, Vector6, Point3 } from "oxalis/constants"; import type { SettingsType, BoundingBoxObjectType, - CategoryType, - ElementClassType, EdgeType, CommentType, TreeGroupType, @@ -17,20 +15,36 @@ import Enum from "Enumjs"; export type APIMessageType = { ["info" | "warning" | "error"]: string }; +type ElementClassType = "uint8" | "uint16" | "uint32"; + export type APIMappingType = { +parent?: string, +name: string, +classes?: Array>, + +colors?: Array, + +hideUnmappedIds?: boolean, }; -export type APIDataLayerType = { +type APIDataLayerBaseType = {| +name: string, - +category: CategoryType, +boundingBox: BoundingBoxObjectType, +resolutions: Array, +elementClass: ElementClassType, +mappings?: Array, -}; +|}; + +type APIColorLayerType = {| + ...APIDataLayerBaseType, + category: "color", +|}; + +type APISegmentationLayerType = {| + ...APIDataLayerBaseType, + category: "segmentation", + largestSegmentId: number, +|}; + +export type APIDataLayerType = APIColorLayerType | APISegmentationLayerType; export type APIDataSourceType = { +id: { diff --git a/app/assets/javascripts/oxalis/api/api_latest.js b/app/assets/javascripts/oxalis/api/api_latest.js index 37c99d0d595..b2d50bf6217 100644 --- a/app/assets/javascripts/oxalis/api/api_latest.js +++ b/app/assets/javascripts/oxalis/api/api_latest.js @@ -542,10 +542,9 @@ class DataApi { /** * Returns the name of the volume tracing layer. - * _Volume tracing only!_ */ getVolumeTracingLayerName(): string { - assertVolume(Store.getState().tracing); + // TODO: Rename method to getSegmentationLayerName() and increase api version const segmentationLayer = this.model.getSegmentationLayer(); assertExists(segmentationLayer, "Segmentation layer not found!"); return segmentationLayer.name; @@ -562,7 +561,11 @@ class DataApi { * * api.setMapping("segmentation", mapping); */ - setMapping(layerName: string, mapping: MappingType) { + setMapping( + layerName: string, + mapping: MappingType, + options?: { colors?: Array, hideUnmappedIds?: boolean } = {}, + ) { if (!Model.isMappingSupported) { throw new Error(messages["mapping.too_few_textures"]); } @@ -573,7 +576,7 @@ class DataApi { if (layerName !== segmentationLayerName) { throw new Error(messages["mapping.unsupported_layer"]); } - Store.dispatch(setMappingAction(_.clone(mapping))); + Store.dispatch(setMappingAction(_.clone(mapping), options.colors, options.hideUnmappedIds)); } /** diff --git a/app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.js b/app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.js index 9a0d54336e2..d1cca921f25 100644 --- a/app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.js +++ b/app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.js @@ -99,6 +99,10 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory { type: "f", value: 0, }, + hideUnmappedIds: { + type: "b", + value: false, + }, globalMousePosition: { type: "v3", value: new THREE.Vector3(0, 0, 0), @@ -177,6 +181,7 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory { const [ mappingTexture, mappingLookupTexture, + mappingColorTexture, ] = segmentationLayer.mappings.getMappingTextures(); this.uniforms[sanitizeName(`${segmentationLayer.name}_mapping_texture`)] = { type: "t", @@ -186,6 +191,10 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory { type: "t", value: mappingLookupTexture, }; + this.uniforms[sanitizeName(`${segmentationLayer.name}_mapping_color_texture`)] = { + type: "t", + value: mappingColorTexture, + }; } // Add weight/color uniforms @@ -270,6 +279,16 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory { ), ); + this.storePropertyUnsubscribers.push( + listenToStoreProperty( + storeState => storeState.temporaryConfiguration.activeMapping.hideUnmappedIds, + hideUnmappedIds => { + this.uniforms.hideUnmappedIds.value = hideUnmappedIds; + }, + true, + ), + ); + this.storePropertyUnsubscribers.push( listenToStoreProperty( storeState => storeState.temporaryConfiguration.viewMode, diff --git a/app/assets/javascripts/oxalis/model/actions/settings_actions.js b/app/assets/javascripts/oxalis/model/actions/settings_actions.js index e8ce749d256..b4316a28ab1 100644 --- a/app/assets/javascripts/oxalis/model/actions/settings_actions.js +++ b/app/assets/javascripts/oxalis/model/actions/settings_actions.js @@ -49,7 +49,12 @@ type SetViewModeActionType = { type: "SET_VIEW_MODE", viewMode: ModeType }; type SetFlightmodeRecordingActionType = { type: "SET_FLIGHTMODE_RECORDING", value: boolean }; type SetControlModeActionType = { type: "SET_CONTROL_MODE", controlMode: ControlModeType }; type SetMappingEnabledActionType = { type: "SET_MAPPING_ENABLED", isMappingEnabled: boolean }; -type SetMappingActionType = { type: "SET_MAPPING", mapping: ?MappingType }; +type SetMappingActionType = { + type: "SET_MAPPING", + mapping: ?MappingType, + mappingColors: ?Array, + hideUnmappedIds: ?boolean, +}; export type SettingActionType = | UpdateUserSettingActionType | UpdateDatasetSettingActionType @@ -145,7 +150,13 @@ export const setMappingEnabledAction = ( isMappingEnabled, }); -export const setMappingAction = (mapping: ?MappingType): SetMappingActionType => ({ +export const setMappingAction = ( + mapping: ?MappingType, + mappingColors: ?Array, + hideUnmappedIds: ?boolean, +): SetMappingActionType => ({ type: "SET_MAPPING", mapping, + mappingColors, + hideUnmappedIds, }); diff --git a/app/assets/javascripts/oxalis/model/bucket_data_handling/data_cube.js b/app/assets/javascripts/oxalis/model/bucket_data_handling/data_cube.js index 4abec9c321b..2dac01fcc43 100644 --- a/app/assets/javascripts/oxalis/model/bucket_data_handling/data_cube.js +++ b/app/assets/javascripts/oxalis/model/bucket_data_handling/data_cube.js @@ -160,12 +160,21 @@ class DataCube { : null; } + shouldHideUnmappedIds(): boolean { + return this.isSegmentation + ? Store.getState().temporaryConfiguration.activeMapping.hideUnmappedIds + : false; + } + mapId(idToMap: number): number { let mappedId = null; const mapping = this.getMapping(); if (mapping != null && this.isMappingEnabled()) { mappedId = mapping[idToMap]; } + if (this.shouldHideUnmappedIds() && mappedId == null) { + mappedId = 0; + } return mappedId != null ? mappedId : idToMap; } diff --git a/app/assets/javascripts/oxalis/model/bucket_data_handling/data_rendering_logic.js b/app/assets/javascripts/oxalis/model/bucket_data_handling/data_rendering_logic.js index e9134fdab50..f2f09f24e85 100644 --- a/app/assets/javascripts/oxalis/model/bucket_data_handling/data_rendering_logic.js +++ b/app/assets/javascripts/oxalis/model/bucket_data_handling/data_rendering_logic.js @@ -108,8 +108,8 @@ function calculateNecessaryTextureCount( } function calculateMappingTextureCount(): number { - // If there is a segmentation layer, we need one lookup and one data texture for mappings - const textureCountForCellMappings = 2; + // If there is a segmentation layer, we need one lookup, one data and one color texture for mappings + const textureCountForCellMappings = 3; return textureCountForCellMappings; } @@ -128,7 +128,6 @@ function deriveSupportedFeatures( } // Count textures needed for mappings separately, because they are not strictly necessary - const notEnoughTexturesForMapping = necessaryTextureCount + calculateMappingTextureCount() > specs.maxTextureCount; if (hasSegmentation && notEnoughTexturesForMapping) { diff --git a/app/assets/javascripts/oxalis/model/bucket_data_handling/mappings.js b/app/assets/javascripts/oxalis/model/bucket_data_handling/mappings.js index 49959543b90..942dcd730a2 100644 --- a/app/assets/javascripts/oxalis/model/bucket_data_handling/mappings.js +++ b/app/assets/javascripts/oxalis/model/bucket_data_handling/mappings.js @@ -15,12 +15,13 @@ import UpdatableTexture from "libs/UpdatableTexture"; import { getRenderer } from "oxalis/controller/renderer"; import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import messages from "messages"; -import { getMappings } from "oxalis/model/accessors/dataset_accessor"; +import { getMappings, getLayerByName } from "oxalis/model/accessors/dataset_accessor"; import type { MappingType } from "oxalis/store"; import type { APIMappingType } from "admin/api_flow_types"; import type DataLayer from "oxalis/model/data_layer"; export const MAPPING_TEXTURE_WIDTH = 4096; +export const MAPPING_COLOR_TEXTURE_WIDTH = 16; type APIMappingsType = { [string]: APIMappingType }; @@ -41,20 +42,21 @@ export function setupGlobalMappingsObject(segmentationLayer: DataLayer) { class Mappings { baseUrl: string; - availableMappings: Array; + layerName: string; mappingTexture: UpdatableTexture; mappingLookupTexture: UpdatableTexture; + mappingColorTexture: UpdatableTexture; constructor(layerName: string) { const { dataset } = Store.getState(); const datasetName = dataset.name; const dataStoreUrl = dataset.dataStore.url; + this.layerName = layerName; this.baseUrl = `${dataStoreUrl}/data/datasets/${datasetName}/layers/${layerName}/mappings/`; - this.availableMappings = getMappings(dataset, layerName); } getMappingNames(): Array { - return this.availableMappings; + return getMappings(Store.getState().dataset, this.layerName); } async activateMapping(mappingName: ?string) { @@ -63,8 +65,12 @@ class Mappings { } else { const fetchedMappings = {}; await this.fetchMappings(mappingName, fetchedMappings); - const mappingObject = this.buildMappingObject(mappingName, fetchedMappings); - Store.dispatch(setMappingAction(mappingObject)); + const { hideUnmappedIds, colors: mappingColors } = fetchedMappings[mappingName]; + // If custom colors are specified for a mapping, assign the mapped ids specifically, so that the first equivalence + // class will get the first color, and so on + const assignNewIds = mappingColors != null && mappingColors.length > 0; + const mappingObject = this.buildMappingObject(mappingName, fetchedMappings, assignNewIds); + Store.dispatch(setMappingAction(mappingObject, mappingColors, hideUnmappedIds)); } } @@ -92,19 +98,36 @@ class Mappings { }); } - buildMappingObject(mappingName: string, fetchedMappings: APIMappingsType): MappingType { + getLargestSegmentId(): number { + const segmentationLayer = getLayerByName(Store.getState().dataset, this.layerName); + if (segmentationLayer.category !== "segmentation") { + throw new Error("Mappings class must be instantiated with a segmentation layer."); + } + return segmentationLayer.largestSegmentId; + } + + buildMappingObject( + mappingName: string, + fetchedMappings: APIMappingsType, + assignNewIds: boolean, + ): MappingType { const mappingObject: MappingType = {}; + const maxId = this.getLargestSegmentId() + 1; + // Initialize to the next multiple of 256 that is larger than maxId + let newMappedId = Math.ceil(maxId / 256) * 256; for (const currentMappingName of this.getMappingChain(mappingName, fetchedMappings)) { const mapping = fetchedMappings[currentMappingName]; ErrorHandling.assertExists(mapping.classes, "Mappings must have been fetched at this point"); + if (mapping.classes) { for (const mappingClass of mapping.classes) { - const minId = _.min(mappingClass); + const minId = assignNewIds ? newMappedId : _.min(mappingClass); const mappedId = mappingObject[minId] || minId; for (const id of mappingClass) { mappingObject[id] = mappedId; } + newMappedId++; } } } @@ -125,23 +148,52 @@ class Mappings { // MAPPING TEXTURES setupMappingTextures() { + const renderer = getRenderer(); this.mappingTexture = createUpdatableTexture( MAPPING_TEXTURE_WIDTH, 4, THREE.UnsignedByteType, - getRenderer(), + renderer, ); this.mappingLookupTexture = createUpdatableTexture( MAPPING_TEXTURE_WIDTH, 4, THREE.UnsignedByteType, - getRenderer(), + renderer, + ); + // Up to 256 (16*16) custom colors can be specified for mappings + this.mappingColorTexture = createUpdatableTexture( + MAPPING_COLOR_TEXTURE_WIDTH, + 1, + THREE.FloatType, + renderer, ); listenToStoreProperty( state => state.temporaryConfiguration.activeMapping.mapping, mapping => this.updateMappingTextures(mapping), ); + + listenToStoreProperty( + state => state.temporaryConfiguration.activeMapping.mappingColors, + mappingColors => this.updateMappingColorTexture(mappingColors), + ); + } + + updateMappingColorTexture(mappingColors: ?Array) { + mappingColors = mappingColors || []; + const maxNumberOfColors = MAPPING_COLOR_TEXTURE_WIDTH ** 2; + const float32Colors = new Float32Array(maxNumberOfColors); + // Initialize the array with -1 + float32Colors.fill(-1); + float32Colors.set(mappingColors.slice(0, maxNumberOfColors)); + this.mappingColorTexture.update( + float32Colors, + 0, + 0, + MAPPING_COLOR_TEXTURE_WIDTH, + MAPPING_COLOR_TEXTURE_WIDTH, + ); } updateMappingTextures(mapping: ?MappingType): void { @@ -195,7 +247,7 @@ class Mappings { if (this.mappingTexture == null) { this.setupMappingTextures(); } - return [this.mappingTexture, this.mappingLookupTexture]; + return [this.mappingTexture, this.mappingLookupTexture, this.mappingColorTexture]; } } diff --git a/app/assets/javascripts/oxalis/model/reducers/settings_reducer.js b/app/assets/javascripts/oxalis/model/reducers/settings_reducer.js index 8312f673bd1..4418d5f7550 100644 --- a/app/assets/javascripts/oxalis/model/reducers/settings_reducer.js +++ b/app/assets/javascripts/oxalis/model/reducers/settings_reducer.js @@ -117,6 +117,8 @@ function SettingsReducer(state: OxalisState, action: ActionType): OxalisState { activeMapping: { mappingSize: { $set: action.mapping != null ? _.size(action.mapping) : 0 }, mapping: { $set: action.mapping }, + mappingColors: { $set: action.mappingColors }, + hideUnmappedIds: { $set: action.hideUnmappedIds }, }, }, }); diff --git a/app/assets/javascripts/oxalis/shaders/main_data_fragment.glsl.js b/app/assets/javascripts/oxalis/shaders/main_data_fragment.glsl.js index f27e7a1bb13..eaf348af1b8 100644 --- a/app/assets/javascripts/oxalis/shaders/main_data_fragment.glsl.js +++ b/app/assets/javascripts/oxalis/shaders/main_data_fragment.glsl.js @@ -1,7 +1,10 @@ // @flow import _ from "lodash"; +import { + MAPPING_TEXTURE_WIDTH, + MAPPING_COLOR_TEXTURE_WIDTH, +} from "oxalis/model/bucket_data_handling/mappings"; import type { Vector3 } from "oxalis/constants"; -import { MAPPING_TEXTURE_WIDTH } from "oxalis/model/bucket_data_handling/mappings"; import { floatsPerLookUpEntry } from "oxalis/model/bucket_data_handling/texture_bucket_manager"; import constants, { OrthoViews, @@ -70,8 +73,10 @@ const int dataTextureCountPerLayer = <%= dataTextureCountPerLayer %>; <% if (isMappingSupported) { %> uniform bool isMappingEnabled; uniform float mappingSize; + uniform bool hideUnmappedIds; uniform sampler2D <%= segmentationName %>_mapping_texture; uniform sampler2D <%= segmentationName %>_mapping_lookup_texture; + uniform sampler2D <%= segmentationName %>_mapping_color_texture; <% } %> <% } %> @@ -119,7 +124,7 @@ ${compileShader( getRelativeCoords, getWorldCoordUVW, getMaybeFilteredColorOrFallback, - convertCellIdToRGB, + hasSegmentation ? convertCellIdToRGB : null, hasSegmentation ? getBrushOverlay : null, hasSegmentation ? getSegmentationId : null, )} @@ -144,9 +149,6 @@ void main() { vec3 mousePosCoords = getRelativeCoords(flooredMousePosUVW, zoomStep); vec4 cellIdUnderMouse = getSegmentationId(mousePosCoords, fallbackCoords, false); - <% } else { %> - vec4 id = vec4(0.0); - vec4 cellIdUnderMouse = vec4(0.0); <% } %> // Get Color Value(s) @@ -191,20 +193,20 @@ void main() { data_color = clamp(data_color, 0.0, 1.0); <% } %> - // Color map (<= to fight rounding mistakes) - if ( length(id) > 0.1 ) { - // Increase cell opacity when cell is hovered - float hoverAlphaIncrement = - // Hover cell only if it's the active one, if the feature is enabled - // and if segmentation opacity is not zero - cellIdUnderMouse == id && highlightHoveredCellId && alpha > 0.0 - ? 0.2 : 0.0; - gl_FragColor = vec4(mix( data_color, convertCellIdToRGB(id), alpha + hoverAlphaIncrement ), 1.0); - } else { - gl_FragColor = vec4(data_color, 1.0); - } + gl_FragColor = vec4(data_color, 1.0); <% if (hasSegmentation) { %> + // Color map (<= to fight rounding mistakes) + if ( length(id) > 0.1 ) { + // Increase cell opacity when cell is hovered + float hoverAlphaIncrement = + // Hover cell only if it's the active one, if the feature is enabled + // and if segmentation opacity is not zero + cellIdUnderMouse == id && highlightHoveredCellId && alpha > 0.0 + ? 0.2 : 0.0; + gl_FragColor = vec4(mix(data_color, convertCellIdToRGB(id), alpha + hoverAlphaIncrement ), 1.0); + } + vec4 brushOverlayColor = getBrushOverlay(worldCoordUVW); brushOverlayColor.xyz = convertCellIdToRGB(activeCellId); gl_FragColor = mix(gl_FragColor, brushOverlayColor, brushOverlayColor.a); @@ -229,6 +231,7 @@ void main() { bucketsPerDim: formatNumberAsGLSLFloat(constants.MAXIMUM_NEEDED_BUCKETS_PER_DIMENSION), l_texture_width: formatNumberAsGLSLFloat(constants.LOOK_UP_TEXTURE_WIDTH), mappingTextureWidth: formatNumberAsGLSLFloat(MAPPING_TEXTURE_WIDTH), + mappingColorTextureWidth: formatNumberAsGLSLFloat(MAPPING_COLOR_TEXTURE_WIDTH), formatNumberAsGLSLFloat, formatVector3AsVec3: vector3 => `vec3(${vector3.map(formatNumberAsGLSLFloat).join(", ")})`, brushToolIndex: formatNumberAsGLSLFloat(volumeToolEnumToIndex(VolumeToolEnum.BRUSH)), diff --git a/app/assets/javascripts/oxalis/shaders/segmentation.glsl.js b/app/assets/javascripts/oxalis/shaders/segmentation.glsl.js index 48da8bdd633..9c8532a94cd 100644 --- a/app/assets/javascripts/oxalis/shaders/segmentation.glsl.js +++ b/app/assets/javascripts/oxalis/shaders/segmentation.glsl.js @@ -5,12 +5,31 @@ import { binarySearchIndex } from "./mappings.glsl"; import { getRgbaAtIndex } from "./texture_access.glsl"; export const convertCellIdToRGB: ShaderModuleType = { - requirements: [hsvToRgb], + requirements: [hsvToRgb, getRgbaAtIndex], code: ` vec3 convertCellIdToRGB(vec4 id) { float golden_ratio = 0.618033988749895; float lastEightBits = id.r; - vec4 HSV = vec4( mod( lastEightBits * golden_ratio, 1.0), 1.0, 1.0, 1.0 ); + float value = mod( lastEightBits * golden_ratio, 1.0); + + <% if (isMappingSupported) { %> + // If the first element of the mapping colors texture is still the initialized + // value of -1, no mapping colors have been specified + bool hasCustomMappingColors = getRgbaAtIndex( + <%= segmentationName %>_mapping_color_texture, + <%= mappingColorTextureWidth %>, + 0.0 + ).r != -1.0; + if (isMappingEnabled && hasCustomMappingColors) { + value = getRgbaAtIndex( + <%= segmentationName %>_mapping_color_texture, + <%= mappingColorTextureWidth %>, + lastEightBits + ).r; + } + <% } %> + + vec4 HSV = vec4( value, 1.0, 1.0, 1.0 ); return hsvToRgb(HSV); } `, @@ -72,6 +91,8 @@ export const getSegmentationId: ShaderModuleType = { <%= mappingTextureWidth %>, index ); + } else if (hideUnmappedIds) { + volume_color = vec4(0.0); } } <% } %> diff --git a/app/assets/javascripts/oxalis/store.js b/app/assets/javascripts/oxalis/store.js index be52b7fc9b4..f707a27d0dd 100644 --- a/app/assets/javascripts/oxalis/store.js +++ b/app/assets/javascripts/oxalis/store.js @@ -113,9 +113,6 @@ export type VolumeCellType = { export type VolumeCellMapType = { [number]: VolumeCellType }; -export type CategoryType = "color" | "segmentation"; -export type ElementClassType = "uint8" | "uint16" | "uint32"; - export type DataLayerType = APIDataLayerType; export type RestrictionsType = APIRestrictionsType; @@ -229,6 +226,8 @@ export type TemporaryConfigurationType = { +brushSize: number, +activeMapping: { +mapping: ?MappingType, + +mappingColors: ?Array, + +hideUnmappedIds: boolean, +isMappingEnabled: boolean, +mappingSize: number, }, @@ -359,6 +358,8 @@ export const defaultState: OxalisState = { brushSize: 50, activeMapping: { mapping: null, + mappingColors: null, + hideUnmappedIds: false, isMappingEnabled: false, mappingSize: 0, }, diff --git a/app/assets/javascripts/oxalis/view/right-menu/mapping_info_view.js b/app/assets/javascripts/oxalis/view/right-menu/mapping_info_view.js index c643fe7f70f..018bae9ae50 100644 --- a/app/assets/javascripts/oxalis/view/right-menu/mapping_info_view.js +++ b/app/assets/javascripts/oxalis/view/right-menu/mapping_info_view.js @@ -24,21 +24,22 @@ type Props = { mousePosition: ?Vector2, isMappingEnabled: boolean, mapping: ?MappingType, + mappingColors: ?Array, setMappingEnabled: boolean => void, activeViewport: OrthoViewType, activeCellId: number, }; // This function mirrors convertCellIdToRGB in the fragment shader of the rendering plane -const convertCellIdToHSV = id => { - if (id === 0) { - return "white"; - } +const convertCellIdToHSV = (id: number, customColors: ?Array) => { + if (id === 0) return "white"; + const goldenRatio = 0.618033988749895; const lastEightBits = id & (2 ** 8 - 1); - const value = ((lastEightBits * goldenRatio) % 1.0) * 360; + const computedColor = (lastEightBits * goldenRatio) % 1.0; + const value = customColors != null ? customColors[lastEightBits] || 0 : computedColor; - return `hsla(${value}, 100%, 50%, 0.15)`; + return `hsla(${value * 360}, 100%, 50%, 0.15)`; }; class MappingInfoView extends Component { @@ -65,6 +66,7 @@ class MappingInfoView extends Component { renderIdTable() { const cube = this.getSegmentationCube(); const hasMapping = this.props.mapping != null; + const customColors = this.props.isMappingEnabled ? this.props.mappingColors : null; let globalMousePosition; if (this.props.mousePosition && this.props.activeViewport !== OrthoViews.TDView) { @@ -109,7 +111,9 @@ class MappingInfoView extends Component { {idInfo.unmapped} ), mapped: ( - {idInfo.mapped} + + {idInfo.mapped} + ), })); @@ -165,6 +169,7 @@ function mapStateToProps(state: OxalisState) { zoomStep: getRequestLogZoomStep(state), isMappingEnabled: state.temporaryConfiguration.activeMapping.isMappingEnabled, mapping: state.temporaryConfiguration.activeMapping.mapping, + mappingColors: state.temporaryConfiguration.activeMapping.mappingColors, mousePosition: state.temporaryConfiguration.mousePosition, activeViewport: state.viewModeData.plane.activeViewport, activeCellId: state.tracing.type === "volume" ? state.tracing.activeCellId : 0, diff --git a/app/assets/javascripts/test/model/binary/data_rendering_logic.spec.js b/app/assets/javascripts/test/model/binary/data_rendering_logic.spec.js index 25e8147f413..421f9fe8116 100644 --- a/app/assets/javascripts/test/model/binary/data_rendering_logic.spec.js +++ b/app/assets/javascripts/test/model/binary/data_rendering_logic.spec.js @@ -121,7 +121,12 @@ test("Basic support (no segmentation): all specs", t => { test("Basic support + volume: min specs", t => { const computeDataTexturesSetupPartial = computeDataTexturesSetupCurried(minSpecs, true); - testSupportFlags(t, computeDataTexturesSetupPartial([grayscaleLayer1, volumeLayer1]), true, true); + testSupportFlags( + t, + computeDataTexturesSetupPartial([grayscaleLayer1, volumeLayer1]), + true, + false, + ); testSupportFlags( t, diff --git a/app/assets/javascripts/test/shaders/shader_syntax.spec.js b/app/assets/javascripts/test/shaders/shader_syntax.spec.js index 4d7c7e43f9a..63b18f46552 100644 --- a/app/assets/javascripts/test/shaders/shader_syntax.spec.js +++ b/app/assets/javascripts/test/shaders/shader_syntax.spec.js @@ -24,7 +24,27 @@ test("Shader syntax: Ortho Mode", t => { t.is(parseResult.log.errorCount, 0); }); -test("Shader syntax: Ortho Mode + Segmentation", t => { +test("Shader syntax: Ortho Mode + Segmentation - Mapping", t => { + const code = getMainFragmentShader({ + colorLayerNames: ["color_layer_1", "color_layer_2"], + hasSegmentation: true, + segmentationName: "segmentationLayer", + segmentationPackingDegree: 1, + isRgb: false, + isMappingSupported: false, + dataTextureCountPerLayer: 3, + resolutions, + datasetScale: [1, 1, 1], + isOrthogonal: true, + }); + + const parseResult = glslParser.check(code); + + t.is(parseResult.log.warningCount, 0); + t.is(parseResult.log.errorCount, 0); +}); + +test("Shader syntax: Ortho Mode + Segmentation + Mapping", t => { const code = getMainFragmentShader({ colorLayerNames: ["color_layer_1", "color_layer_2"], hasSegmentation: true,