Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Colors for mappings #2965

Merged
merged 14 commits into from
Jul 30, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions app/assets/javascripts/admin/api_flow_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import type { Vector3, Vector6, Point3 } from "oxalis/constants";
import type {
SettingsType,
BoundingBoxObjectType,
CategoryType,
ElementClassType,
EdgeType,
CommentType,
TreeGroupType,
Expand All @@ -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<Array<number>>,
+colors?: Array<number>,
+hideUnmappedIds?: boolean,
};

export type APIDataLayerType = {
type APIDataLayerBaseType = {|
+name: string,
+category: CategoryType,
+boundingBox: BoundingBoxObjectType,
+resolutions: Array<Vector3>,
+elementClass: ElementClassType,
+mappings?: Array<string>,
};
|};

type APIColorLayerType = {|
...APIDataLayerBaseType,
category: "color",
|};

type APISegmentationLayerType = {|
...APIDataLayerBaseType,
category: "segmentation",
largestSegmentId: number,
|};

export type APIDataLayerType = APIColorLayerType | APISegmentationLayerType;

export type APIDataSourceType = {
+id: {
Expand Down
11 changes: 7 additions & 4 deletions app/assets/javascripts/oxalis/api/api_latest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -562,7 +561,11 @@ class DataApi {
*
* api.setMapping("segmentation", mapping);
*/
setMapping(layerName: string, mapping: MappingType) {
setMapping(
layerName: string,
mapping: MappingType,
options?: { colors?: Array<number>, hideUnmappedIds?: boolean } = {},
) {
if (!Model.isMappingSupported) {
throw new Error(messages["mapping.too_few_textures"]);
}
Expand All @@ -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));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -177,6 +181,7 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory {
const [
mappingTexture,
mappingLookupTexture,
mappingColorTexture,
] = segmentationLayer.mappings.getMappingTextures();
this.uniforms[sanitizeName(`${segmentationLayer.name}_mapping_texture`)] = {
type: "t",
Expand All @@ -186,6 +191,10 @@ class PlaneMaterialFactory extends AbstractPlaneMaterialFactory {
type: "t",
value: mappingLookupTexture,
};
this.uniforms[sanitizeName(`${segmentationLayer.name}_mapping_color_texture`)] = {
type: "t",
value: mappingColorTexture,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope the increased texture uniform count will not become a problem for older hardware :/ Can you test on browserstack whether iPad still works? 🙈

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mappings will be disallowed (and the textures won't be attached, see the check a couple of lines above which is not visible here) if there are not enough textures available (Model.isMappingSupported), so this shouldn't be a problem, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's right. However, the uniform is always defined in JS-land. I'm not sure whether this has implications on some systems. But probably not. We'll see :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniform "definition" is inside a if (segmentationLayer != null && Model.isMappingSupported) { check, so it should not have any implications :)

};
}

// Add weight/color uniforms
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 13 additions & 2 deletions app/assets/javascripts/oxalis/model/actions/settings_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>,
hideUnmappedIds: ?boolean,
};
export type SettingActionType =
| UpdateUserSettingActionType
| UpdateDatasetSettingActionType
Expand Down Expand Up @@ -145,7 +150,13 @@ export const setMappingEnabledAction = (
isMappingEnabled,
});

export const setMappingAction = (mapping: ?MappingType): SetMappingActionType => ({
export const setMappingAction = (
mapping: ?MappingType,
mappingColors: ?Array<number>,
hideUnmappedIds: ?boolean,
): SetMappingActionType => ({
type: "SET_MAPPING",
mapping,
mappingColors,
hideUnmappedIds,
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ function calculateNecessaryTextureCount<Layer>(
}

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;
}

Expand All @@ -128,7 +128,6 @@ function deriveSupportedFeatures<Layer>(
}

// Count textures needed for mappings separately, because they are not strictly necessary

const notEnoughTexturesForMapping =
necessaryTextureCount + calculateMappingTextureCount() > specs.maxTextureCount;
if (hasSegmentation && notEnoughTexturesForMapping) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand All @@ -41,20 +42,21 @@ export function setupGlobalMappingsObject(segmentationLayer: DataLayer) {

class Mappings {
baseUrl: string;
availableMappings: Array<string>;
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<string> {
return this.availableMappings;
return getMappings(Store.getState().dataset, this.layerName);
}

async activateMapping(mappingName: ?string) {
Expand All @@ -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));
}
}

Expand Down Expand Up @@ -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 segmenation layer.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

segmentation

}
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++;
}
}
}
Expand All @@ -125,23 +148,54 @@ 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<number>) {
mappingColors = mappingColors || [];
const float32Colors = new Float32Array(
MAPPING_COLOR_TEXTURE_WIDTH * MAPPING_COLOR_TEXTURE_WIDTH,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAPPING_COLOR_TEXTURE_WIDTH ** 2 is shorter :)

);
const maxNumberOfColors = MAPPING_COLOR_TEXTURE_WIDTH ** 2;
// 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 {
Expand Down Expand Up @@ -195,7 +249,7 @@ class Mappings {
if (this.mappingTexture == null) {
this.setupMappingTextures();
}
return [this.mappingTexture, this.mappingLookupTexture];
return [this.mappingTexture, this.mappingLookupTexture, this.mappingColorTexture];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
},
});
Expand Down
Loading