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

Add measurement tools #7258

Merged
merged 34 commits into from
Oct 12, 2023
Merged

Add measurement tools #7258

merged 34 commits into from
Oct 12, 2023

Conversation

MichaelBuessemeyer
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer commented Aug 10, 2023

URL of deployed dev instance (used for testing):

Steps to test:

  • Open an annotation of any dataset

  • Select the new measurement tool in the toolbar (the new tool should be the right most)

  • Try measuring a path. Setting path point can be done via left click. Setting the last point via double-/or right-click and terminating the measurement by hitting escape or another right click.

  • Try copying the measured length from the tooltip via the copy icon.

  • Start measuring and terminating it while still in the process using escape. This should not lead to any errors or unexpected behaviour. (if so, I have a regression bug 🙈)

  • Select the area measurement tool. Draw an area via right click. The area should always be closed by another highlighting line to highlight that the measured area is a closed area connecting the current end and start point.

  • Upon releasing the left mouse drag, the highlighted line should be gone and the connecting line should be in the normal color.

  • Potential bug: see below: When hitting escape while measuring an area, the area measurement tool is not correctly reset.

  • Please report any unexpected behavior.

  • Test the tracing tool. It should still work as expected as this PR fiddled around with the helper geometry used by the tracing tool.

TODOs / Bugs:

  • [ ] Add fill area for area measurement tool We decided against that as this is quite the hurdle and thus went for always having a highlighted connection line between the start and end of the drawn area to highlight that the user is measuring an area.
  • When finished marking the area the tooltip can be moved around by left clicking.

Known issue / to discuss:

  • When canceling the area measurement via pressing escape while the mouse is still pressed down and then continuing dragging the mouse, the tooltip reappears and the measurement continues. Should this be tackled or is this misuse of the tool and the user can be expected to redo the measurement? In this scenario, no exception is thrown but the measured area is not correctly canceled upon hitting escape. So keeping it as it is right now, is acceptable imo.

Issues:


(Please delete unneeded items, merge only when none are left open)

@MichaelBuessemeyer MichaelBuessemeyer changed the title WIP: Add measurement tool WIP: Add measurement tools Aug 24, 2023
@MichaelBuessemeyer
Copy link
Contributor Author

Just for information purposes:

The algorithm used for the calculation of the area of the polygon of the area measurement tool works correctly, when the point are exactly on a line: The algorithm was taken from: https://www.mathopenref.com/coordpolygonarea2.html

function polygonArea(X, Y, numPoints) 
{ 
area = 0;   // Accumulates area 
j = numPoints-1; 

for (i=0; i<numPoints; i++)
{ area +=  (X[j]+X[i]) * (Y[j]-Y[i]); 
  j = i;  //j is previous vertex to i
}
  return area/2;
}
// The triangle reaches from 1,1 to 4,1 to 1,3 and back to 1,1. Thus the area is h*w/2 = 2*3/2 = 3
X = [1,2,3,4,2.5,1,1]
Y = [1,1,1,1,2,3,2]
polygonArea(X,Y,7)
=> -3
// The result is correct (and negative due to the points being in counter clockwise order). 
// The implementation of the algorithm in this pr is secure against this flaw by using the absolute value of the calculated area.

@MichaelBuessemeyer MichaelBuessemeyer marked this pull request as ready for review September 1, 2023 09:55
Comment on lines 795 to 804
const guardFromResettedTool = (func: Function) => {
// TODO: Problem typing is lost
return (...args: any) => {
if (lineMeasurementGeometry.isResetted && isMeasuring) {
isMeasuring = false;
return;
}
func(...args);
};
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of the following three functions need to be guarded against the user having used escape while still in the process of measuring, to interrupt the measurement. When escape was pressed, the lineMeasurementGeometry carries that information in the field isResetted.

The guarding function takes care of guarding these three functions against the user using escape while still measuring. But the typing is lost (as far as I would guess) and the code ready quite quirky IMO. Maybe you have a better idea?

Comment on lines 108 to 109
LINE_MEASUREMENT: "pointer",
AREA_MEASUREMENT: "pointer",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you think of a better suitable mouse pointer attribute for the new tools?

@MichaelBuessemeyer MichaelBuessemeyer changed the title WIP: Add measurement tools Add measurement tools Sep 1, 2023
@MichaelBuessemeyer
Copy link
Contributor Author

@philippotto This pr should now be ready for the first review. After the CI is done, I will also ask for further UX feedback in slack.

- allow panning in line measurement tool
- add custom mouse cursors
- use area unit for area measurement tool
Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

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

Great stuff :) Will test now!

frontend/javascripts/libs/format_utils.ts Outdated Show resolved Hide resolved
Comment on lines 95 to 104
function mapStateToProps(state: OxalisState): Props {
return {
position: state.uiInformation.measurementTooltipPosition,
flycam: state.flycam,
activeTool: state.uiInformation.activeTool,
datasetScale: state.dataset.dataSource.scale,
};
}

const connector = connect(mapStateToProps);
Copy link
Member

Choose a reason for hiding this comment

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

since this is a function components, I'd rather use useSelector to access the store.

@@ -780,6 +784,171 @@ export class QuickSelectTool {
static onToolDeselected() {}
}

export class LineMeasurementTool {
static DOUBLE_CLICK_TIME_THRESHOLD = 600;
static getPlaneMouseControls(_planeId: OrthoView): any {
Copy link
Member

Choose a reason for hiding this comment

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

getPlaneMouseControls of the area measurement tool doesn't accept that parameter. maybe make these interfaces consistent?

return;
}
const currentTime = Date.now();
if (currentTime - lastLeftClickTime <= this.DOUBLE_CLICK_TIME_THRESHOLD) {
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't it make sense to check that the two clicks were registered roughly at the same spot?

Comment on lines +878 to +879
lineMeasurementGeometry.reset();
lineMeasurementGeometry.hide();
Copy link
Member

Choose a reason for hiding this comment

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

There are several places where reset and hide is called. How about adding a resetAndHide helper method?

@@ -61,6 +107,43 @@ export class ContourGeometry {
mesh.geometry.attributes.position.needsUpdate = true;
mesh.geometry.setDrawRange(0, this.vertexBuffer.getLength());
mesh.geometry.computeBoundingSphere();
this.connectingLine.geometry.attributes.position.needsUpdate = true;
// this.connectingLine.geometry.setDrawRange(0, 2);
Copy link
Member

Choose a reason for hiding this comment

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

remove?

this.finalizeMesh();
}

setTopPoint(pos: Vector3) {
Copy link
Member

Choose a reason for hiding this comment

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

I think I don't really understand what a "top point" is. maybe add a comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment and renamed the method to updateLatestPointPosition.

By top point I meant the latest added point to the array of points (like the latest element on a stack is called top element).

@@ -231,3 +314,113 @@ export class QuickSelectGeometry {
rectangle.material.alphaMap = null;
}
}

export class LineMeasurementGeometry {
color: THREE.Color;
Copy link
Member

Choose a reason for hiding this comment

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

maybe add a class comment explaining that this is used for displaying "line segments" (not sure whether this is the right term?).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I called it connected line segments 🤷

}

setStartPoint(pos: Vector3, initialOrthoView: OrthoView) {
this.wasReset = false;
Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest to either add a comment explaining that this method expects an empty vertexBuffer or add this.vertexBuffer.clear() to this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch 👍, I went for the 2nd suggestion, as this is more bullet proof against potential bugs imo.

@philippotto
Copy link
Member

Overall it feels pretty good 👍 My testing results:

  • line tool: fast clicking is sometimes registered as double click (see my other suggestion to compare the clicked positions)
  • area tool shows units without ² and the line tool shows them with ² :trollface:
  • the measured area seems wrong (see my other code comment)
  • I'd change "Finish Measurement and reset" in the status bar to "Finish Measurement" since it's not clear what reset actually means

Please remind me again why the measurement geometries are hidden as soon as the user moves? Is it because it would be hard to stick the tooltip to the geometry? If this is the case, one could also think about moving the tooltip content simply into the toolbar 🤔 But I think we'd need actual user feedback for that. Right now, I imagine it to be confusing potentially (could be percepted as bug, too).
@normanrz do you have an opinion about the second paragraph?

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Thanks for your feedback @philippotto: I think I addressed everything now.

Please remind me again why the measurement geometries are hidden as soon as the user moves?

you mentioned hiding the tool as soon as the camera moves here and here

Is it because it would be hard to stick the tooltip to the geometry?

Jup, that is the case 👍

Let's wait for @normanrz answer 😺

frontend/javascripts/libs/format_utils.ts Outdated Show resolved Hide resolved
@@ -231,3 +314,113 @@ export class QuickSelectGeometry {
rectangle.material.alphaMap = null;
}
}

export class LineMeasurementGeometry {
color: THREE.Color;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I called it connected line segments 🤷

}

setStartPoint(pos: Vector3, initialOrthoView: OrthoView) {
this.wasReset = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch 👍, I went for the 2nd suggestion, as this is more bullet proof against potential bugs imo.

this.finalizeMesh();
}

setTopPoint(pos: Vector3) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment and renamed the method to updateLatestPointPosition.

By top point I meant the latest added point to the array of points (like the latest element on a stack is called top element).

@normanrz
Copy link
Member

Please remind me again why the measurement geometries are hidden as soon as the user moves? Is it because it would be hard to stick the tooltip to the geometry? If this is the case, one could also think about moving the tooltip content simply into the toolbar 🤔 But I think we'd need actual user feedback for that. Right now, I imagine it to be confusing potentially (could be percepted as bug, too).
@normanrz do you have an opinion about the second paragraph?

I think keeping the lines on move would be nice. But how would a user remove them again?

Also, Alt+Mouse Move doesn't work for me in this tool mode. That feels like a bug.

@MichaelBuessemeyer
Copy link
Contributor Author

I think keeping the lines on move would be nice.

I misunderstood your statement a little 😅 Now the lines are kept (unless the user moves in the z direction of the viewport) and the tooltip follows the line / area mesurement

@MichaelBuessemeyer
Copy link
Contributor Author

But how would a user remove them again?

Via right or double left click.

Also, Alt+Mouse Move doesn't work for me in this tool mode. That feels like a bug.

For me this works. Maybe the viewports weren't the active part of the wk webpage when you tested? 🤔

@philippotto
Copy link
Member

The sticky tooltip is quite cool 👍 However, I ran into some bugs:

  • like @normanrz, alt+mousemove doesn't work for me while using the line tool
  • alt+mousemove for the area tool works sort of. it changes to the proper tool and does the moving, but the tool still registers new points around the mouse.
  • I got into a weird state where the measurement tools got broken. I can reproduce this by starting measuring in XY and then adding points in YZ. see video.

minor problems:

  • when moving the mouse fast, I can move it into the tooltip which is a bit weird since I cannot pinpoint the next click in the area of the tooltip. not sure, how to solve this. as a user I can always leave the tooltip and then hidden space is visible again. so, it's not a big issue, but I ran into this quite quickly, which is why I'm writing this.

I recorded a video for these issues:

  • the video starts with the broken state where I cannot measure anything. I recover this be reloading the page
  • then I show that alt+mousemove doesnt work for the line tool
  • then I show that the mouse can get caught up in the tooltip
  • then I show alt+mousemove for the area tool
  • in the end, I try to reproduce the weird state again. it was a bit difficult, but at 1:40 I managed to break it. afterwards, I cannot fix it again except for reloading the page. I would expect that switching the tools, resets all the relevant internal state.
measurement-bugs.mp4

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

like @normanrz, alt+mousemove doesn't work for me while using the line tool

Ahh, ok now I understand what you mean: It is impossible to move around while measuring with a line 👍. Previously I only tested moving with alt when I was not actively measuring a line.
The alt moving should now be fixed for both measurement tools during the measurement and also while not measuring.

alt+mousemove for the area tool works sort of. it changes to the proper tool and does the moving, but the tool still registers new points around the mouse.

This should be fixed now. The tool should register new points while alt is pressed.

I got into a weird state where the measurement tools got broken. I can reproduce this by starting measuring in XY and then adding points in YZ. see video.

I think this was due to an instant reset of the measurement caused by the distance_measurement_tooltip. See my comment :)

when moving the mouse fast, I can move it into the tooltip which is a bit weird since I cannot pinpoint the next click in the area of the tooltip. not sure, how to solve this. as a user I can always leave the tooltip and then hidden space is visible again. so, it's not a big issue, but I ran into this quite quickly, which is why I'm writing this.

While measuring, the tooltip now ignores all pointer event, which mitigates this problem as the inputcatcher get the pointer event, even when the mouse hits the tooltip. Thanks to @fm3 for the solution.
This is done by saving whether the measurement tool is measuring at the moment in the store.

I added a tooltip to the distance option for the context menu as the entry could be misunderstood. See for reference https://scm.slack.com/archives/C5AKLAV0B/p1695894445554669

useEffect(() => {
if (
position != null &&
Math.floor(currentPosition[thirdDim]) !== Math.floor(position[thirdDim])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is the "trick" hopefully fixing the bug you described. In case the flycam is not at an exact numeric position, the fraction of it causes the !== to be false. This is due to the value stored in position being already floored by the measurement tool (which sets the value state.uiInformation.measurementToolInfo.lastMeasuredPosition)

Thus flooring the value should fix that the tooltip auto resets the measurement all the time and only triggers the reset in case the third coordinate actually changes by a full unit.

Store.dispatch(setIsMeasuringAction(false));
}
const isAltPressed = evt.altKey;
if (isAltPressed || plane !== this.initialPlane || !this.isMeasuring) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the isAltPressed enabled moving with alt while measuring

const currentTime = Date.now();
if (
currentTime - lastLeftClickTime <= DOUBLE_CLICK_TIME_THRESHOLD &&
Math.abs(pos.x - lastClickPosition.x) + Math.abs(pos.y - lastClickPosition.y) < 6
Copy link
Member

Choose a reason for hiding this comment

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

extract the 6 into a constant?

@@ -784,12 +784,32 @@ export class QuickSelectTool {
static onToolDeselected() {}
}

function getDoubleClickGuard() {
Copy link
Member

Choose a reason for hiding this comment

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

cool abstraction 👍

Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

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

awesome! works very well :)

there are several PR comments where the code is marked as outdated. I assume these comments can be resolved? other than that, only see my one comment about a magic number.

@MichaelBuessemeyer
Copy link
Contributor Author

I assume these comments can be resolved?

I skimmed through them and nothing of these comments seemed to be important 👍

@philippotto philippotto merged commit 8e809a2 into master Oct 12, 2023
2 checks passed
@philippotto philippotto deleted the add-measurement-tool branch October 12, 2023 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Measurement Tools
3 participants