diff --git a/PanTS-Demo/src/helpers/search.test.ts b/PanTS-Demo/src/helpers/search.test.ts index 29828cb..b0cc6cd 100644 --- a/PanTS-Demo/src/helpers/search.test.ts +++ b/PanTS-Demo/src/helpers/search.test.ts @@ -64,12 +64,22 @@ describe("buildSearchParams", () => { expect(params.get("sort_by")).toBe("quality"); expect(params.get("per_page")).toBe("12"); }); + + it("maps the dataset selection to ?dataset= (empty/both = all)", () => { + expect(buildSearchParams(base).get("dataset")).toBe("all"); // none selected + expect(buildSearchParams({ ...base, dataset: ["PanTS"] }).get("dataset")).toBe("pants"); + expect(buildSearchParams({ ...base, dataset: ["CancerVerse"] }).get("dataset")).toBe("cancerverse"); + expect( + buildSearchParams({ ...base, dataset: ["PanTS", "CancerVerse"] }).get("dataset") + ).toBe("all"); + }); }); describe("parseFiltersFromParams", () => { it("round-trips filters through the URL query string", () => { const filters: SearchFilters = { tumor: "tumor", + dataset: ["CancerVerse"], sex: ["F"], age: ["50-59"], manufacturer: ["GE"], diff --git a/PanTS-Demo/src/helpers/search.ts b/PanTS-Demo/src/helpers/search.ts index ffb12ee..a1b13bc 100644 --- a/PanTS-Demo/src/helpers/search.ts +++ b/PanTS-Demo/src/helpers/search.ts @@ -5,6 +5,7 @@ export type TumorFilter = "any" | "tumor" | "no_tumor"; export type SearchFilters = { tumor: TumorFilter; + dataset: string[]; // "PanTS" / "CancerVerse"; empty = both (Any) sex: string[]; // M / F / UNKNOWN age: string[]; // "0-9" … "90-99" / "UNKNOWN" manufacturer: string[]; // scanner manufacturer (from facets) @@ -15,6 +16,7 @@ export type SearchFilters = { export const EMPTY_FILTERS: SearchFilters = { tumor: "any", + dataset: [], sex: [], age: [], manufacturer: [], @@ -24,7 +26,7 @@ export const EMPTY_FILTERS: SearchFilters = { }; // The multi-select array keys (everything except `tumor`). -export type MultiFilterKey = "sex" | "age" | "manufacturer" | "ctPhase" | "siteNat" | "year"; +export type MultiFilterKey = "dataset" | "sex" | "age" | "manufacturer" | "ctPhase" | "siteNat" | "year"; // Minimal shape of an item returned by /api/search and /api/random. export type SearchItem = { @@ -53,6 +55,14 @@ export const buildSearchParams = ( opts: { sortBy?: string; perPage?: number } = {} ): URLSearchParams => { const params = new URLSearchParams(); + // Dataset dispatch → backend ?dataset=. Empty or both = all (show PanTS + CancerVerse); + // exactly one selected restricts to that dataset. + const ds = filters.dataset ?? []; + const hasPanTS = ds.includes("PanTS"); + const hasCV = ds.includes("CancerVerse"); + if (hasCV && !hasPanTS) params.set("dataset", "cancerverse"); + else if (hasPanTS && !hasCV) params.set("dataset", "pants"); + else params.set("dataset", "all"); // both or neither → everything filters.sex.forEach((v) => params.append("sex[]", v)); if (filters.tumor === "tumor") params.set("tumor", "1"); else if (filters.tumor === "no_tumor") params.set("tumor", "0"); @@ -71,8 +81,14 @@ export const buildSearchParams = ( export const parseFiltersFromParams = (params: URLSearchParams): SearchFilters => { const tumorRaw = params.get("tumor"); const tumor: TumorFilter = tumorRaw === "1" ? "tumor" : tumorRaw === "0" ? "no_tumor" : "any"; + const datasetRaw = (params.get("dataset") || "").toLowerCase(); + const dataset = + datasetRaw === "pants" ? ["PanTS"] : + datasetRaw === "cancerverse" || datasetRaw === "cv" ? ["CancerVerse"] : + []; // "all"/absent → both (Any) return { tumor, + dataset, sex: params.getAll("sex[]"), age: params.getAll("age_bin[]"), manufacturer: params.getAll("manufacturer[]"), @@ -84,6 +100,9 @@ export const parseFiltersFromParams = (params: URLSearchParams): SearchFilters = export const countActiveFilters = (f: SearchFilters): number => (f.tumor !== "any" ? 1 : 0) + + // dataset only counts as an active filter when it restricts to a single dataset + // (empty or both = "Any", i.e. no restriction). + ((f.dataset?.length ?? 0) === 1 ? 1 : 0) + f.sex.length + f.age.length + f.manufacturer.length + diff --git a/PanTS-Demo/src/routes/Homepage.tsx b/PanTS-Demo/src/routes/Homepage.tsx index 0520777..251367c 100644 --- a/PanTS-Demo/src/routes/Homepage.tsx +++ b/PanTS-Demo/src/routes/Homepage.tsx @@ -59,6 +59,13 @@ const TUMOR_OPTIONS: { value: TumorFilter; label: string }[] = [ { value: "no_tumor", label: "No tumor" }, ]; +// Which dataset(s) to search. Empty = both (backend ?dataset=all). CancerVerse is +// CT-only (no masks yet), so those cases sort after the mask-complete PanTS cases. +const DATASET_OPTIONS = [ + { value: "PanTS", label: "PanTS" }, + { value: "CancerVerse", label: "CancerVerse" }, +]; + // Values match the backend /api/search params: sex -> M/F/UNKNOWN, age -> age_bin[]. const SEX_OPTIONS = [ { value: "M", label: "Male" }, @@ -698,6 +705,31 @@ export default function Homepage() { gap: "20px", }} > + {/* Dataset */} +