rework menu system
This commit is contained in:
@@ -12,18 +12,19 @@ from typing import Any
|
|||||||
|
|
||||||
|
|
||||||
MENU_LAYOUT: dict[str, list[str]] = {
|
MENU_LAYOUT: dict[str, list[str]] = {
|
||||||
"Add": [
|
"Input": [
|
||||||
"Image",
|
"Image",
|
||||||
"ImageDemo",
|
"ImageDemo",
|
||||||
"Folder",
|
"Folder",
|
||||||
"ColorMap",
|
|
||||||
"Number",
|
"Number",
|
||||||
"RangeSlider",
|
"RangeSlider",
|
||||||
"Coordinate",
|
"Coordinate",
|
||||||
"CoordinatePair",
|
"CoordinatePair",
|
||||||
"Font",
|
|
||||||
],
|
],
|
||||||
"Output": [
|
"Display": [
|
||||||
|
"ColorMap",
|
||||||
|
"Font",
|
||||||
|
"ColormapAdjust",
|
||||||
"PreviewImage",
|
"PreviewImage",
|
||||||
"ValueDisplay",
|
"ValueDisplay",
|
||||||
"View3D",
|
"View3D",
|
||||||
@@ -36,11 +37,10 @@ MENU_LAYOUT: dict[str, list[str]] = {
|
|||||||
"Annotations",
|
"Annotations",
|
||||||
"AngleMeasure",
|
"AngleMeasure",
|
||||||
],
|
],
|
||||||
"Modify": [
|
"Geometry": [
|
||||||
"CropResizeField",
|
"CropResizeField",
|
||||||
"RotateField",
|
"RotateField",
|
||||||
"FlipField",
|
"FlipField",
|
||||||
"ColormapAdjust",
|
|
||||||
],
|
],
|
||||||
"Filter": [
|
"Filter": [
|
||||||
"GaussianFilter",
|
"GaussianFilter",
|
||||||
@@ -50,25 +50,31 @@ MENU_LAYOUT: dict[str, list[str]] = {
|
|||||||
"FFTFilter2D",
|
"FFTFilter2D",
|
||||||
"ScarRemoval",
|
"ScarRemoval",
|
||||||
],
|
],
|
||||||
"Frequency": [
|
"Spectral": [
|
||||||
"PSDF",
|
|
||||||
"FFT2D",
|
"FFT2D",
|
||||||
"InverseFFT2D",
|
"InverseFFT2D",
|
||||||
|
"FFTFilter1D",
|
||||||
|
"FFTFilter2D",
|
||||||
|
"ACF",
|
||||||
|
"PSDF",
|
||||||
],
|
],
|
||||||
"Flatten": [
|
"Level & Correct": [
|
||||||
"FixZero",
|
"FixZero",
|
||||||
"LineCorrection",
|
|
||||||
"PlaneLevelField",
|
"PlaneLevelField",
|
||||||
"PolyLevelField",
|
"PolyLevelField",
|
||||||
"FacetLevelField",
|
"FacetLevelField",
|
||||||
|
"LineCorrection",
|
||||||
|
"ScarRemoval",
|
||||||
],
|
],
|
||||||
"Measure": [
|
"Measure": [
|
||||||
|
"AngleMeasure",
|
||||||
"CrossSection",
|
"CrossSection",
|
||||||
"Histogram",
|
"Histogram",
|
||||||
"Cursors",
|
"Cursors",
|
||||||
"Curvature",
|
"Curvature",
|
||||||
"FractalDimension",
|
"FractalDimension",
|
||||||
"ACF",
|
"ACF",
|
||||||
|
"PSDF",
|
||||||
"Statistics",
|
"Statistics",
|
||||||
"Stats",
|
"Stats",
|
||||||
],
|
],
|
||||||
@@ -78,12 +84,14 @@ MENU_LAYOUT: dict[str, list[str]] = {
|
|||||||
"MaskMorphology",
|
"MaskMorphology",
|
||||||
"MaskInvert",
|
"MaskInvert",
|
||||||
"MaskOperations",
|
"MaskOperations",
|
||||||
],
|
|
||||||
"Grains": [
|
|
||||||
"GrainAnalysis",
|
|
||||||
"GrainDistanceTransform",
|
"GrainDistanceTransform",
|
||||||
"WatershedSegmentation",
|
"WatershedSegmentation",
|
||||||
],
|
],
|
||||||
|
"Grains": [
|
||||||
|
"GrainDistanceTransform",
|
||||||
|
"WatershedSegmentation",
|
||||||
|
"GrainAnalysis",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -91,11 +99,17 @@ _CATEGORY_ORDER = {category: index for index, category in enumerate(MENU_LAYOUT)
|
|||||||
_NODE_METADATA: dict[str, dict[str, Any]] = {}
|
_NODE_METADATA: dict[str, dict[str, Any]] = {}
|
||||||
for category, class_names in MENU_LAYOUT.items():
|
for category, class_names in MENU_LAYOUT.items():
|
||||||
for node_order, class_name in enumerate(class_names):
|
for node_order, class_name in enumerate(class_names):
|
||||||
_NODE_METADATA[class_name] = {
|
metadata = _NODE_METADATA.setdefault(class_name, {
|
||||||
"category": category,
|
"category": category,
|
||||||
"category_order": _CATEGORY_ORDER[category],
|
"category_order": _CATEGORY_ORDER[category],
|
||||||
"menu_order": node_order,
|
"menu_order": node_order,
|
||||||
}
|
"menu_categories": [],
|
||||||
|
})
|
||||||
|
metadata["menu_categories"].append({
|
||||||
|
"category": category,
|
||||||
|
"category_order": _CATEGORY_ORDER[category],
|
||||||
|
"menu_order": node_order,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_menu_metadata(class_name: str) -> dict[str, Any]:
|
def get_menu_metadata(class_name: str) -> dict[str, Any]:
|
||||||
@@ -107,4 +121,9 @@ def get_menu_metadata(class_name: str) -> dict[str, Any]:
|
|||||||
"category": "Unsorted",
|
"category": "Unsorted",
|
||||||
"category_order": len(_CATEGORY_ORDER),
|
"category_order": len(_CATEGORY_ORDER),
|
||||||
"menu_order": 10_000,
|
"menu_order": 10_000,
|
||||||
|
"menu_categories": [{
|
||||||
|
"category": "Unsorted",
|
||||||
|
"category_order": len(_CATEGORY_ORDER),
|
||||||
|
"menu_order": 10_000,
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ def get_node_info(class_name: str) -> dict[str, Any]:
|
|||||||
"category": menu_metadata["category"],
|
"category": menu_metadata["category"],
|
||||||
"category_order": menu_metadata["category_order"],
|
"category_order": menu_metadata["category_order"],
|
||||||
"menu_order": menu_metadata["menu_order"],
|
"menu_order": menu_metadata["menu_order"],
|
||||||
|
"menu_categories": list(menu_metadata.get("menu_categories", [])),
|
||||||
"input": input_types,
|
"input": input_types,
|
||||||
"input_order": {k: list(v.keys()) for k, v in input_types.items()},
|
"input_order": {k: list(v.keys()) for k, v in input_types.items()},
|
||||||
"output": list(cls.RETURN_TYPES),
|
"output": list(cls.RETURN_TYPES),
|
||||||
|
|||||||
@@ -404,8 +404,16 @@ function canStartCanvasRightDragZoom(target) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compareMenuNodes(a, b) {
|
function compareMenuNodes(a, b) {
|
||||||
const orderA = Number.isFinite(a?.def?.menu_order) ? a.def.menu_order : Number.MAX_SAFE_INTEGER;
|
const orderA = Number.isFinite(a?.menu_order)
|
||||||
const orderB = Number.isFinite(b?.def?.menu_order) ? b.def.menu_order : Number.MAX_SAFE_INTEGER;
|
? a.menu_order
|
||||||
|
: Number.isFinite(a?.def?.menu_order)
|
||||||
|
? a.def.menu_order
|
||||||
|
: Number.MAX_SAFE_INTEGER;
|
||||||
|
const orderB = Number.isFinite(b?.menu_order)
|
||||||
|
? b.menu_order
|
||||||
|
: Number.isFinite(b?.def?.menu_order)
|
||||||
|
? b.def.menu_order
|
||||||
|
: Number.MAX_SAFE_INTEGER;
|
||||||
if (orderA !== orderB) return orderA - orderB;
|
if (orderA !== orderB) return orderA - orderB;
|
||||||
|
|
||||||
const nameA = (a?.def?.display_name || a?.className || '').toLowerCase();
|
const nameA = (a?.def?.display_name || a?.className || '').toLowerCase();
|
||||||
@@ -615,19 +623,37 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
|
|||||||
if (!hasMatch) continue;
|
if (!hasMatch) continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cat = def.category || 'uncategorized';
|
const menuCategories = Array.isArray(def.menu_categories) && def.menu_categories.length > 0
|
||||||
if (!cats[cat]) {
|
? def.menu_categories
|
||||||
cats[cat] = {
|
: [{
|
||||||
name: cat,
|
category: def.category || 'uncategorized',
|
||||||
order: Number.isFinite(def.category_order) ? def.category_order : Number.MAX_SAFE_INTEGER,
|
category_order: def.category_order,
|
||||||
items: [],
|
menu_order: def.menu_order,
|
||||||
};
|
}];
|
||||||
|
|
||||||
|
for (const menuCategory of menuCategories) {
|
||||||
|
const cat = menuCategory?.category || def.category || 'uncategorized';
|
||||||
|
if (!cats[cat]) {
|
||||||
|
cats[cat] = {
|
||||||
|
name: cat,
|
||||||
|
order: Number.isFinite(menuCategory?.category_order)
|
||||||
|
? menuCategory.category_order
|
||||||
|
: Number.MAX_SAFE_INTEGER,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cats[cat].order = Math.min(
|
||||||
|
cats[cat].order,
|
||||||
|
Number.isFinite(menuCategory?.category_order)
|
||||||
|
? menuCategory.category_order
|
||||||
|
: Number.MAX_SAFE_INTEGER,
|
||||||
|
);
|
||||||
|
cats[cat].items.push({
|
||||||
|
className,
|
||||||
|
def,
|
||||||
|
menu_order: Number.isFinite(menuCategory?.menu_order) ? menuCategory.menu_order : def.menu_order,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
cats[cat].order = Math.min(
|
|
||||||
cats[cat].order,
|
|
||||||
Number.isFinite(def.category_order) ? def.category_order : Number.MAX_SAFE_INTEGER,
|
|
||||||
);
|
|
||||||
cats[cat].items.push({ className, def });
|
|
||||||
}
|
}
|
||||||
return Object.values(cats)
|
return Object.values(cats)
|
||||||
.map((category) => ({
|
.map((category) => ({
|
||||||
@@ -642,10 +668,15 @@ function ContextMenu({ x, y, nodeDefs, onAdd, onClose, filterType, filterDirecti
|
|||||||
if (!search.trim()) return null;
|
if (!search.trim()) return null;
|
||||||
const q = search.toLowerCase();
|
const q = search.toLowerCase();
|
||||||
const results = [];
|
const results = [];
|
||||||
|
const seen = new Set();
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
for (const { className, def } of category.items) {
|
for (const { className, def } of category.items) {
|
||||||
|
if (seen.has(className)) continue;
|
||||||
const name = (def.display_name || className).toLowerCase();
|
const name = (def.display_name || className).toLowerCase();
|
||||||
if (name.includes(q)) results.push({ className, def });
|
if (name.includes(q)) {
|
||||||
|
results.push({ className, def });
|
||||||
|
seen.add(className);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -33,13 +33,16 @@ export const TYPE_COLORS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CAT_COLORS = {
|
export const CAT_COLORS = {
|
||||||
io: '#37474f',
|
Input: '#37474f',
|
||||||
filters: '#1a237e',
|
Display: '#212121',
|
||||||
modify: '#0f766e',
|
Overlay: '#0f766e',
|
||||||
level: '#1b5e20',
|
Geometry: '#0d9488',
|
||||||
analysis: '#4a148c',
|
Filter: '#1a237e',
|
||||||
|
Spectral: '#4c1d95',
|
||||||
|
'Level & Correct': '#1b5e20',
|
||||||
|
Measure: '#4a148c',
|
||||||
|
Mask: '#7c2d12',
|
||||||
Grains: '#bf360c',
|
Grains: '#bf360c',
|
||||||
display: '#212121',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SOCKET_COMPATIBILITY = {
|
export const SOCKET_COMPATIBILITY = {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ def test_flip_field():
|
|||||||
overlays=[markup_overlay, annotation_overlay],
|
overlays=[markup_overlay, annotation_overlay],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert get_node_info("FlipField")["category"] == "Modify"
|
assert get_node_info("FlipField")["category"] == "Geometry"
|
||||||
|
|
||||||
flipped_x, = node.process(field, axis="x")
|
flipped_x, = node.process(field, axis="x")
|
||||||
assert np.array_equal(flipped_x.data, np.flipud(data))
|
assert np.array_equal(flipped_x.data, np.flipud(data))
|
||||||
@@ -569,7 +569,7 @@ def test_facet_level():
|
|||||||
|
|
||||||
node = FacetLevelField()
|
node = FacetLevelField()
|
||||||
plane_node = PlaneLevelField()
|
plane_node = PlaneLevelField()
|
||||||
assert get_node_info("FacetLevelField")["category"] == "Flatten"
|
assert get_node_info("FacetLevelField")["category"] == "Level & Correct"
|
||||||
|
|
||||||
N = 96
|
N = 96
|
||||||
yy, xx = np.mgrid[0:N, 0:N]
|
yy, xx = np.mgrid[0:N, 0:N]
|
||||||
@@ -749,7 +749,7 @@ def test_line_correction():
|
|||||||
from backend.nodes.line_correction import LineCorrection
|
from backend.nodes.line_correction import LineCorrection
|
||||||
|
|
||||||
node = LineCorrection()
|
node = LineCorrection()
|
||||||
assert get_node_info("LineCorrection")["category"] == "Flatten"
|
assert get_node_info("LineCorrection")["category"] == "Level & Correct"
|
||||||
|
|
||||||
rows = 96
|
rows = 96
|
||||||
cols = 128
|
cols = 128
|
||||||
@@ -815,7 +815,9 @@ def test_scar_removal():
|
|||||||
from backend.nodes.scar_removal import ScarRemoval
|
from backend.nodes.scar_removal import ScarRemoval
|
||||||
|
|
||||||
node = ScarRemoval()
|
node = ScarRemoval()
|
||||||
assert get_node_info("ScarRemoval")["category"] == "Filter"
|
info = get_node_info("ScarRemoval")
|
||||||
|
assert info["category"] == "Filter"
|
||||||
|
assert {entry["category"] for entry in info["menu_categories"]} == {"Filter", "Level & Correct"}
|
||||||
|
|
||||||
rows = 96
|
rows = 96
|
||||||
cols = 128
|
cols = 128
|
||||||
@@ -874,7 +876,9 @@ def test_angle_measure():
|
|||||||
from backend.data_types import ImageData
|
from backend.data_types import ImageData
|
||||||
|
|
||||||
node = AngleMeasure()
|
node = AngleMeasure()
|
||||||
assert get_node_info("AngleMeasure")["category"] == "Overlay"
|
info = get_node_info("AngleMeasure")
|
||||||
|
assert info["category"] == "Overlay"
|
||||||
|
assert {entry["category"] for entry in info["menu_categories"]} == {"Overlay", "Measure"}
|
||||||
required_inputs = AngleMeasure.INPUT_TYPES()["required"]
|
required_inputs = AngleMeasure.INPUT_TYPES()["required"]
|
||||||
optional_inputs = AngleMeasure.INPUT_TYPES().get("optional", {})
|
optional_inputs = AngleMeasure.INPUT_TYPES().get("optional", {})
|
||||||
assert required_inputs["color"][1]["default"] == "#ff9800"
|
assert required_inputs["color"][1]["default"] == "#ff9800"
|
||||||
|
|||||||
Reference in New Issue
Block a user