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

Lasso with point field #5534

Open
wants to merge 8 commits into
base: release/v1.4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions app/packages/embeddings/src/EmbeddingsPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ export function EmbeddingsPlot({
result[p.fullData.name].push(p.id);
pointIds.push(p.id);
}
handleSelected(pointIds);
handleSelected(pointIds, selected.lassoPoints);
}}
onDeselect={() => {
handleSelected(null);
handleSelected(null, null);
}}
config={{
scrollZoom: true,
Expand Down
1 change: 1 addition & 0 deletions app/packages/embeddings/src/useBrainResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useColorByField } from "./useLabelSelector";
// a react hook that fetches a list of brain results
// and has a loading state and an error state
export const useBrainResult = () => usePanelStatePartial("brainResult", null);
export const usePointsField = () => usePanelStatePartial("pointsField", null);
export function useBrainResultsSelector() {
const [selected, setSelected] = useBrainResult();
const dataset = useRecoilValue(fos.dataset);
Expand Down
15 changes: 14 additions & 1 deletion app/packages/embeddings/src/useExtendedStageEffect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useRecoilCallback, useRecoilValue, useSetRecoilState } from "recoil";
import * as fos from "@fiftyone/state";
import { usePanelStatePartial } from "@fiftyone/spaces";
import { fetchExtendedStage } from "./fetch";
import { atoms as selectionAtoms } from "./usePlotSelection";
import { usePointsField } from "./useBrainResult";

export default function useExtendedStageEffect() {
const datasetName = useRecoilValue(fos.datasetName);
Expand All @@ -16,6 +18,8 @@ export default function useExtendedStageEffect() {
return snapshot.getPromise(fos.datasetName);
});
const slices = useRecoilValue(fos.currentSlices(false));
const lassoPoints = useRecoilValue(selectionAtoms.lassoPoints);
const [pointsField] = usePointsField();

useEffect(() => {
if (loadedPlot && Array.isArray(selection)) {
Expand All @@ -25,6 +29,8 @@ export default function useExtendedStageEffect() {
patchesField: loadedPlot.patches_field,
selection,
slices,
lassoPoints,
pointsField,
}).then(async (res) => {
const currentDataset = await getCurrentDataset();
if (currentDataset !== datasetName) return;
Expand All @@ -33,5 +39,12 @@ export default function useExtendedStageEffect() {
});
});
}
}, [datasetName, loadedPlot?.patches_field, view, selection]);
}, [
datasetName,
loadedPlot?.patches_field,
view,
selection,
pointsField,
lassoPoints,
]);
}
24 changes: 22 additions & 2 deletions app/packages/embeddings/src/usePlotSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import {
atom,
useRecoilState,
useRecoilValue,
useSetRecoilState,
} from "recoil";
import * as fos from "@fiftyone/state";
import { usePanelStatePartial } from "@fiftyone/spaces";
import { useBrainResultInfo } from "./useBrainResultInfo";
import { SELECTION_SCOPE } from "./constants";
import { useResetExtendedSelection } from "@fiftyone/state";

export const atoms = {
lassoPoints: atom({
key: "lassoPoints",
default: { x: [], y: [] },
}),
};

export function usePlotSelection() {
const brainResultInfo = useBrainResultInfo();
const patchesField = brainResultInfo?.config?.patchesField;
Expand All @@ -20,14 +32,22 @@ export function usePlotSelection() {
[],
true
);
const [lassoPoints, setLassoPoints] = useRecoilState(atoms.lassoPoints);
const selectedPatchIds = useRecoilValue(fos.selectedPatchIds(patchesField));
const selectedPatchSampleIds = useRecoilValue(fos.selectedPatchSamples);
function handleSelected(selectedResults) {
function handleSelected(
selectedResults,
lassoPoints: { x: number[]; y: number[] }
) {
setSelectedSamples(new Set());
setExtendedSelection({
selection: selectedResults,
scope: SELECTION_SCOPE,
});
if (lassoPoints) {
// TODO: this handleSelected is called without lassoPoints in some unkown cases
setLassoPoints(lassoPoints);
}
if (selectedResults === null) {
clearSelection();
}
Expand Down
4 changes: 3 additions & 1 deletion app/packages/embeddings/src/useViewChangeEffect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import * as fos from "@fiftyone/state";
import { useEffect } from "react";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { fetchPlot } from "./fetch";
import { useBrainResult } from "./useBrainResult";
import { useBrainResult, usePointsField } from "./useBrainResult";
import { useColorByField } from "./useLabelSelector";
import { useWarnings } from "./useWarnings";

export function useViewChangeEffect() {
const colorSeed = useRecoilValue(fos.colorSeed);
const datasetName = useRecoilValue(fos.datasetName);
const [brainKey, setBrainKey] = useBrainResult();
const [pointsField, setPointsField] = usePointsField();
const [labelField] = useColorByField();
const view = useRecoilValue(fos.view);
const slices = useRecoilValue(fos.currentSlices(false));
Expand Down Expand Up @@ -72,6 +73,7 @@ export function useViewChangeEffect() {

setLoadingPlotError(null);
setLoadedPlot(res);
setPointsField(res.points_field);
})
.finally(() => setLoadingPlot(false));
}, [datasetName, brainKey, labelField, view, colorSeed, slices, filters]);
Expand Down
3 changes: 2 additions & 1 deletion fiftyone/core/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3295,7 +3295,8 @@ class GeoWithin(_GeoStage):
strict (True): whether a sample's location data must strictly fall
within boundary (True) in order to match, or whether any
intersection suffices (False)
create_index (True): whether to create the required spherical index,
if necessary
"""

def __init__(
Expand Down
36 changes: 35 additions & 1 deletion fiftyone/server/routes/embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ def _post_sync(self, data):
patches_field = data["patchesField"] # patches field of plot, or None
selected_ids = data["selection"] # selected IDs in plot
slices = data["slices"]
lasso_points = data.get("lassoPoints", None)
points_field = data.get("pointsField", None)

view = fosv.get_view(
dataset_name,
Expand All @@ -250,7 +252,39 @@ def _post_sync(self, data):
is_patches_view = view._is_patches
is_patches_plot = patches_field is not None

if not is_patches_view and not is_patches_plot:
if (
lasso_points is not None
and points_field is not None
and (patches_field is None or is_patches_view)
):
# Lasso points via spatial index
# Unfortunately we can't use $geoWithin to filter nested arrays.
# The user must have switched to a patches view for this
if patches_field is not None:
_, points_field = view._get_label_field_path(
patches_field, points_field
)

stage = fos.Mongo(
[
{
"$match": {
points_field: {
"$geoWithin": {
"$polygon": [
[x, y]
for x, y in zip(
lasso_points["x"],
lasso_points["y"],
)
]
}
}
}
}
]
)
elif not is_patches_view and not is_patches_plot:
# Samples plot and samples view
stage = fos.Select(selected_ids)
elif is_patches_view and is_patches_plot:
Expand Down
Loading