-
Notifications
You must be signed in to change notification settings - Fork 299
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
feat(annotation): AnnotationLayerView now shows currently displayed annotations in MultiscaleAnnotationSource #504
base: master
Are you sure you want to change the base?
Conversation
…nnotations in MultiscaleAnnotationSource
This is a really cool feature --- thanks. There seems to be a performance problem when there are many annotations visible, though: Regarding As far as sorting, there are a few issues to consider:
|
For the performance issues, I don't see anything grossly inefficient. I think we need debouncing (or more of it) and probably moving the computation to the backend thread. Do you agree with that and is there anything tricky with moving it to the backend? |
Moving to the backend thread is tricky because the annotation data is not accessible there once it is moved to the frontend. In terms of efficiency, the current implementation has a few issues:
I imagine the use cases you built this for are expected to have only a small number of annotations, where this isn't an issue. |
…hat the virtual list renders started to work on sorting the list for spatial index but the chunk calculation is incorrect
Update I'm reconsidering the use of lazy deserialization since as I said below, it doesn't work in the case we want to sort and even with unsorted segmentFilteredSources, the performance issue there should be possible to handle by observing if the segment list actually changed and the chunks were already loaded. @jbms I wanted to try out lazy evaluation by holding off on deserializing annotations until the virtual list calls render. Performance was greatly improved but it definitely adds some code complexity and there is some cleanup/optimization left. The sorted spatial index needs to be deserialized immediately for coordinates. I am attempting to use the chunk structure to only collect and sort the annotations closest to our global position. I'll point that out in the code but the chunk I calculate using the global position divided the chunk size is different than the chunks loaded by the 3d view. I am doing a very basic calculation compared to the 3d projection. I added debounce to listening to the visibleChunksChanged. When we have active segmentFilteredSources, I should probably be able to ignore calls to Most of the expense of Add/update/remove annotation element is disabled when the source is a MultiscaleAnnotationSource. Perhaps this is a good opportunity to add that as unimplemented methods on MultiscaleAnnotationSource? At some point we want to support editing our annotations within neuroglancer. Overall, do you think this lazy approach for handling large annotation lists for segment filtered sources is worth it? |
const sortChunk = new Uint32Array(rank); | ||
const {chunkDataSize} = source.spec; | ||
for (let i = 0; i < rank; i++) { | ||
sortChunk[i] = Math.floor(sortByPosition.value[i] / chunkDataSize[i]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the basic chunk calculation that doesn't match up with the loaded chunks. The idea is to start with the highest level chunks and go out till we have a chunk that is loaded. Eventually it would be nice to support neighboring chunks particularly if the global is near a chunk boundary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sortByPosition
you are just using the globalPosition
, which is not necessarily in the same coordinate space. Instead you need to transform the relevant globalPosition and localPosition to the coordinate space of the annotation layer, as done here:
function getMousePositionInAnnotationCoordinates( |
Then each scale level also has a coordinate transform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I converted getMousePositionInAnnotationCoordinates
to getPositionInAnnotationCoordinates
so I could pass in any Float32Array, call that with
const point = getPositionInAnnotationCoordinates(
globalPosition.value,
state,
);
I then copied the logic in forEachPossibleChunk
:
private forEachPossibleChunk( |
To make
positionToMultiscaleChunks(position: Float32Array)
https://gist.github.com/chrisj/328828d13480627961166beb16cfcd18
This uses source.multiscaleToChunkTransform
which I assume is the coordinate transform that you mentioned per scale. The issue is that it only returns 0,0,0 for the largest scale. I'm not sure if I'm misunderstanding the code but what happens in the case of a single point is that totalChunks = 1;
(unless on boundary), remainder = 0
chunkIndex = 0;
, size = 1
, remainder % size = 0
, remainder = 0 / 1
so you end up with tempChunk = [0,0,0] and that chunk usually only exists for the largest scale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain a bit more what the issue is? It is indeed the case that at the largest scale, there is normally just a single chunk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that MultiscaleAnnotationSource.forEachPossibleChunk is supposed run the callback across all the scales that contain the annotation.
When I follow the logic, it seems to be stuck at [0,0,0] across all scales so chunks.get(tempChunk.join());
only returns a result at the largest scale.
I am looking for the smallest scales that contain my target point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this branch that shows the problem I'm experiencing
https://github.com/seung-lab/neuroglancer/tree/cj_multiscale_annotation_list_3
In the console you will see output such as
addChunk 0,0,0
addChunk 28,31,35
addChunk 14,15,17
addChunk 0,1,1
addChunk 0,0,0
addChunk 1,0,1
addChunk 29,30,36
addChunk 28,32,36
addChunk 0,0,1
... (removed 20 lines)
addChunk 2,3,4
addChunk 7,7,9
addChunk 1,1,2
addChunk 1,1,1
5 no chunk for key 0,0,0
chunk exists 0,0,0 size 17209,15302,19814
Potentially you could sort without actually fully deserializing, by just creating an array of indices, In general it seems to me that a top priority is ensuring that regardless of what is selected, the list doesn't degrade performance, and instead we should degrade the quality of the list (e.g. skip sorting, etc. if there are too many items) as needed to retain reasonable performance. If necessary there could be an additional user-configurable setting that trades off performance vs quality, though ideally we could avoid that extra complexity.
Might make sense to wait to add the methods until there will be at least one implementation.
For unsorted lists filtered by segments, lazy deserialization will be a pretty big win, because in some cases we may have several hundred segments, and it will be expensive to deserialize all of those, compared to just deserializing the ~30 that are visible, which could be done quite efficiently. This could be a good fallback in the case where there are more than a certain number of annotations. I suppose the lazy deserialization could still happen on a per-chunk basis, though, rather than deserializing individual elements within a chunk. |
Breaking apart #493
I left out the sorting by distance from because we don't want it for the cave datasource, would a boolean
sortByDistance
property on MultiscaleAnnotationSource be reasonable?I left some comments in the code about the arguments to
this.virtualListSource.changed!.dispatch
.I could do a diff between the old and new list to see which elements are identical to get a proper retainCount but I'm not sure what effect it would have.