Skip to content

Commit

Permalink
Merge pull request #2827 from scalableminds/make-bbox-persisted
Browse files Browse the repository at this point in the history
add 'boundingBox' to NmlParser and NmlWriter
  • Loading branch information
rschwanhold authored Jul 5, 2018
2 parents cac15bf + a78b548 commit 219bcaa
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 61 deletions.
25 changes: 15 additions & 10 deletions app/assets/javascripts/oxalis/model/helpers/nml_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Store from "oxalis/store";
import Date from "libs/date";
import DiffableMap from "libs/diffable_map";
import EdgeCollection from "oxalis/model/edge_collection";
import { convertFrontendBoundingBoxToServer } from "oxalis/model/reducers/reducer_helpers";
import type {
OxalisState,
SkeletonTracingType,
Expand All @@ -17,6 +18,7 @@ import type {
TemporaryMutableTreeMapType,
TreeGroupType,
} from "oxalis/store";
import type { BoundingBoxType } from "oxalis/constants";
import type { APIBuildInfoType } from "admin/api_flow_types";

// NML Defaults
Expand Down Expand Up @@ -140,10 +142,21 @@ function serializeMetaInformation(state: OxalisState, buildInfo: APIBuildInfoTyp
]);
}

function serializeBoundingBox(bb: ?BoundingBoxType, name: string): string {
const serverBoundingBox = convertFrontendBoundingBoxToServer(bb);
if (serverBoundingBox != null) {
const { topLeft, width, height, depth } = serverBoundingBox;
const [topLeftX, topLeftY, topLeftZ] = topLeft;
return serializeTag(name, { topLeftX, topLeftY, topLeftZ, width, height, depth });
}
return "";
}

function serializeParameters(state: OxalisState): Array<string> {
const editPosition = getPosition(state.flycam).map(Math.round);
const editRotation = getRotation(state.flycam);
const userBB = state.tracing.userBoundingBox;
const taskBB = state.tracing.boundingBox;
return [
"<parameters>",
...indent(
Expand Down Expand Up @@ -174,16 +187,8 @@ function serializeParameters(state: OxalisState): Array<string> {
zRot: editRotation[2],
}),
serializeTag("zoomLevel", { zoom: state.flycam.zoomStep }),
userBB != null
? serializeTag("userBoundingBox", {
topLeftX: userBB.min[0],
topLeftY: userBB.min[1],
topLeftZ: userBB.min[2],
width: userBB.max[0] - userBB.min[0],
height: userBB.max[1] - userBB.min[1],
depth: userBB.max[2] - userBB.min[2],
})
: "",
serializeBoundingBox(userBB, "userBoundingBox"),
serializeBoundingBox(taskBB, "taskBoundingBox"),
]),
),
"</parameters>",
Expand Down
119 changes: 78 additions & 41 deletions app/assets/javascripts/test/libs/nml.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ const tracing = {
allowAccess: true,
allowDownload: true,
},
boundingBox: {
min: [0, 0, 0],
max: [500, 500, 500],
},
userBoundingBox: {
min: [5, 5, 5],
max: [250, 250, 250],
},
};

const initialState = _.extend({}, defaultState, {
Expand All @@ -123,10 +131,16 @@ const initialState = _.extend({}, defaultState, {
},
});

async function throwsAsyncParseError(t, fn) {
async function testThatParserThrowsWithState(t, invalidState, key) {
// Serialize the NML using the invalidState, then parse it again, which should throw an NMLParseError
const nmlWithInvalidContent = serializeToNml(invalidState, invalidState.tracing, buildInfo);
await throwsAsyncParseError(t, () => parseNml(nmlWithInvalidContent), key);
}

async function throwsAsyncParseError(t, fn, key) {
try {
await fn.call();
t.fail(`Test did not throw, calling the following function: ${fn.toString()}`);
t.fail(`Test did not throw, calling the function with the following key: ${key}`);
} catch (e) {
if (e.name === "NmlParseError") {
t.true(true);
Expand Down Expand Up @@ -183,6 +197,21 @@ test("NML Parser should throw errors for invalid nmls", async t => {
},
},
});
const invalidSelfEdgeState = update(initialState, {
tracing: {
trees: {
"2": {
edges: {
$set: EdgeCollection.loadFromArray([
{ source: 4, target: 5 },
{ source: 5, target: 6 },
{ source: 6, target: 6 },
]),
},
},
},
},
});
const duplicateEdgeState = update(initialState, {
tracing: {
trees: {
Expand All @@ -198,6 +227,43 @@ test("NML Parser should throw errors for invalid nmls", async t => {
},
},
});
const duplicateNodeState = update(initialState, {
tracing: {
trees: {
"1": {
nodes: {
$set: new DiffableMap([
[0, createDummyNode(0)],
[1, createDummyNode(1)],
[2, createDummyNode(2)],
[4, createDummyNode(4)],
[7, createDummyNode(7)],
]),
},
},
"2": {
nodes: {
$set: new DiffableMap([
[4, createDummyNode(4)],
[5, createDummyNode(5)],
[6, createDummyNode(6)],
]),
},
},
},
},
});
const duplicateTreeState = update(initialState, {
tracing: {
trees: {
"2": {
treeId: {
$set: 1,
},
},
},
},
});
const disconnectedTreeState = update(initialState, {
tracing: {
trees: { "2": { edges: { $set: EdgeCollection.loadFromArray([{ source: 4, target: 5 }]) } } },
Expand Down Expand Up @@ -225,46 +291,17 @@ test("NML Parser should throw errors for invalid nmls", async t => {
},
},
});
const nmlWithInvalidComment = serializeToNml(
invalidCommentState,
invalidCommentState.tracing,
buildInfo,
);
const nmlWithInvalidBranchPoint = serializeToNml(
invalidBranchPointState,
invalidBranchPointState.tracing,
buildInfo,
);
const nmlWithInvalidEdge = serializeToNml(invalidEdgeState, invalidEdgeState.tracing, buildInfo);
const nmlWithDuplicateEdge = serializeToNml(
duplicateEdgeState,
duplicateEdgeState.tracing,
buildInfo,
);
const nmlWithDisconnectedTree = serializeToNml(
disconnectedTreeState,
disconnectedTreeState.tracing,
buildInfo,
);
const nmlWithMissingGroup = serializeToNml(
missingGroupIdState,
missingGroupIdState.tracing,
buildInfo,
);
const nmlWithDuplicateGroup = serializeToNml(
duplicateGroupIdState,
duplicateGroupIdState.tracing,
buildInfo,
);

// TODO AVAs t.throws doesn't properly work with async functions yet, see https://github.com/avajs/ava/issues/1371
await throwsAsyncParseError(t, () => parseNml(nmlWithInvalidComment));
await throwsAsyncParseError(t, () => parseNml(nmlWithInvalidBranchPoint));
await throwsAsyncParseError(t, () => parseNml(nmlWithInvalidEdge));
await throwsAsyncParseError(t, () => parseNml(nmlWithDuplicateEdge));
await throwsAsyncParseError(t, () => parseNml(nmlWithDisconnectedTree));
await throwsAsyncParseError(t, () => parseNml(nmlWithMissingGroup));
await throwsAsyncParseError(t, () => parseNml(nmlWithDuplicateGroup));
await testThatParserThrowsWithState(t, invalidCommentState, "invalidComment");
await testThatParserThrowsWithState(t, invalidBranchPointState, "invalidBranchPoint");
await testThatParserThrowsWithState(t, invalidEdgeState, "invalidEdge");
await testThatParserThrowsWithState(t, invalidSelfEdgeState, "invalidSelfEdge");
await testThatParserThrowsWithState(t, duplicateEdgeState, "duplicateEdge");
await testThatParserThrowsWithState(t, duplicateNodeState, "duplicateNode");
await testThatParserThrowsWithState(t, duplicateTreeState, "duplicateTree");
await testThatParserThrowsWithState(t, disconnectedTreeState, "disconnectedTree");
await testThatParserThrowsWithState(t, missingGroupIdState, "missingGroupId");
await testThatParserThrowsWithState(t, duplicateGroupIdState, "duplicateGroupId");
});

test("addTreesAndGroups reducer should assign new node and tree ids", t => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Generated by [AVA](https://ava.li).
<editPosition x="0" y="0" z="0" />␊
<editRotation xRot="0" yRot="0" zRot="180" />␊
<zoomLevel zoom="1.3" />␊
<userBoundingBox topLeftX="5" topLeftY="5" topLeftZ="5" width="245" height="245" depth="245" />␊
<taskBoundingBox topLeftX="0" topLeftY="0" topLeftZ="0" width="500" height="500" depth="500" />␊
</parameters>␊
<thing id="1" color.r="23" color.g="23" color.b="23" color.a="1" name="TestTree-0" groupId="">␊
<nodes>␊
Expand Down
Binary file not shown.
16 changes: 9 additions & 7 deletions app/controllers/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ class TaskController @Inject() (val messagesApi: MessagesApi)
}
}

private def buildFullParams(nmlParams: NmlTaskParameters, tracing: SkeletonTracing, fileName: String, description: Option[String]) = {
private def buildFullParams(nmlFormParams: NmlTaskParameters, tracing: SkeletonTracing, fileName: String, description: Option[String]) = {
val parsedNmlTracingBoundingBox = tracing.boundingBox.map(b => BoundingBox(b.topLeft, b.width, b.height, b.depth))
val bbox = if(nmlFormParams.boundingBox.isDefined) nmlFormParams.boundingBox else parsedNmlTracingBoundingBox
TaskParameters(
nmlParams.taskTypeId,
nmlParams.neededExperience,
nmlParams.openInstances,
nmlParams.projectName,
nmlParams.scriptId,
nmlParams.boundingBox,
nmlFormParams.taskTypeId,
nmlFormParams.neededExperience,
nmlFormParams.openInstances,
nmlFormParams.projectName,
nmlFormParams.scriptId,
bbox,
tracing.dataSetName,
tracing.editPosition,
tracing.editRotation,
Expand Down
7 changes: 4 additions & 3 deletions app/models/annotation/nml/NmlParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits {
val editPosition = parseEditPosition(parameters \ "editPosition").getOrElse(SkeletonTracingDefaults.editPosition)
val editRotation = parseEditRotation(parameters \ "editRotation").getOrElse(SkeletonTracingDefaults.editRotation)
val zoomLevel = parseZoomLevel(parameters \ "zoomLevel").getOrElse(SkeletonTracingDefaults.zoomLevel)
val userBoundingBox = parseUserBoundingBox(parameters \ "userBoundingBox")
val userBoundingBox = parseBoundingBox(parameters \ "userBoundingBox")
val taskBoundingBox = parseBoundingBox(parameters \ "taskBoundingBox")

logger.debug(s"Parsed NML file. Trees: ${trees.size}, Volumes: ${volumes.size}")

if (volumes.size >= 1) {
(Right(VolumeTracing(None, BoundingBox.empty, time, dataSetName, editPosition, editRotation, ElementClass.uint32, None, 0, 0, zoomLevel), volumes.head.location), description)
} else {
(Left(SkeletonTracing(dataSetName, trees, time, None, activeNodeId,
(Left(SkeletonTracing(dataSetName, trees, time, taskBoundingBox, activeNodeId,
editPosition, editRotation, zoomLevel, version = 0, userBoundingBox, treeGroups)), description)
}
}
Expand Down Expand Up @@ -106,7 +107,7 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits {
treeNodes.flatMap(treeNode => parseTree(treeNode, branchPoints, comments))
}

private def parseUserBoundingBox(node: NodeSeq) = {
private def parseBoundingBox(node: NodeSeq) = {
node.headOption.flatMap(bb =>
for {
topLeftX <- (node \ "@topLeftX").text.toIntOpt
Expand Down
10 changes: 10 additions & 0 deletions app/models/annotation/nml/NmlWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ object NmlWriter extends FoxImplicits {
writer.writeAttribute("depth", b.depth.toString)
}
}
tracing.boundingBox.map { b =>
Xml.withinElementSync("taskBoundingBox") {
writer.writeAttribute("topLeftX", b.topLeft.x.toString)
writer.writeAttribute("topLeftY", b.topLeft.y.toString)
writer.writeAttribute("topLeftZ", b.topLeft.z.toString)
writer.writeAttribute("width", b.width.toString)
writer.writeAttribute("height", b.height.toString)
writer.writeAttribute("depth", b.depth.toString)
}
}
}

def writeParametersAsXml(tracing: VolumeTracing, description: String, scale: Option[Scale])(implicit writer: XMLStreamWriter) = {
Expand Down

0 comments on commit 219bcaa

Please sign in to comment.