diff --git a/renderer/components/dbComponents/InputToolsComponent.jsx b/renderer/components/dbComponents/InputToolsComponent.jsx index 65f70b3c..f45f79b4 100644 --- a/renderer/components/dbComponents/InputToolsComponent.jsx +++ b/renderer/components/dbComponents/InputToolsComponent.jsx @@ -1,38 +1,28 @@ -import { useContext, useEffect, useState } from "react" +import { Button } from "primereact/button" import { Card } from "primereact/card" import { Dropdown } from "primereact/dropdown" -import { Button } from "primereact/button" +import { useContext, useEffect, useState } from "react" import { Stack } from "react-bootstrap" +import MEDprofilesPrepareData from "../input/MEDprofiles/MEDprofilesPrepareData" +import { getCollectionSize } from "../mongoDB/mongoDBUtils" import { DataContext } from "../workspace/dataContext" - -// import tools +import ModuleLandingShell, { ModuleGuideText } from "../mainPages/moduleBasics/ModuleLandingShell" import BasicToolsDB from "./inputToolsDB/basicToolsDB" +import ConvertCategoricalColumnIntoNumericDB from "./inputToolsDB/convertCategoricalColumnIntoNumericDB" +import DropColumnsAndTagsToolsDB from "./inputToolsDB/dropColumnsToolsDB" import DropDuplicatesToolsDB from "./inputToolsDB/dropDuplicatesToolsDB" import FeatureReductionToolsDB from "./inputToolsDB/featureReductionToolsDB/featureReductionToolsDB" -import ConvertCategoricalColumnIntoNumericDB from "./inputToolsDB/convertCategoricalColumnIntoNumericDB" import GroupingTaggingToolsDB from "./inputToolsDB/groupingTaggingToolsDB" import HoldoutSetCreationToolsDB from "./inputToolsDB/holdoutSetCreationToolsDB" import MergeToolsDB from "./inputToolsDB/mergeToolsDB" -import DropColumnsAndTagsToolsDB from "./inputToolsDB/dropColumnsToolsDB" import NormalizationToolsDB from "./inputToolsDB/normalizationToolsDB" import SimpleCleaningToolsDB from "./inputToolsDB/simpleCleaningToolsDB" import SubsetCreationToolsDB from "./inputToolsDB/subsetCreationToolsDB" import TransformColumnToolsDB from "./inputToolsDB/transformColumnToolsDB" -import MEDprofilesPrepareData from "../input/MEDprofiles/MEDprofilesPrepareData" -import { getCollectionSize } from "../mongoDB/mongoDBUtils" const SectionContainer = ({ title, children }) => (
-
- {title} -
+
{title}
{children} @@ -135,7 +125,7 @@ const InputToolsComponent = ({ exportOptions }) => { if (!activeTool) return null const ToolComponent = activeTool.component return ( -
+
-
-
-
+ ) } diff --git a/renderer/components/mainPages/exploratory.jsx b/renderer/components/mainPages/exploratory.jsx index 7b7a0d01..b23dffd7 100644 --- a/renderer/components/mainPages/exploratory.jsx +++ b/renderer/components/mainPages/exploratory.jsx @@ -1,12 +1,10 @@ -import { shell } from 'electron' -import Image from "next/image" import { useContext } from "react" -import myimage from "../../../resources/medomics_transparent_bg.png" import DTale from "../exploratory/dtale" import SweetViz from "../exploratory/sweetViz" import YDataProfiling from "../exploratory/yDataProfiling" import { ErrorRequestContext } from "../generalPurpose/errorRequestContext" import { WorkspaceContext } from "../workspace/workspaceContext" +import ModuleLandingShell, { ModuleGuideText } from "./moduleBasics/ModuleLandingShell" import ModulePage from "./moduleBasics/modulePage" @@ -19,43 +17,30 @@ const ExploratoryPage = () => { const { setError } = useContext(ErrorRequestContext) return ( - <> -
-

- Exploratory Module -

-
- -
-

๐Ÿ” Exploratory Data Analysis (EDA)

-

- Before training a machine learning model, it is essential to explore and understand the dataset. - This page provides three powerful tools, each with a unique role in data exploration: -

- -

โžก SweetViz : Generates automated reports with dataset characteristics, - feature distributions, correlations, and comparisons. Helps quickly detect outliers, missing values, and potential biases.

- -

โžก Y-Data Profiling : Performs a deep statistical audit to analyze feature types, - missing values, and relationships, helping detect preprocessing issues before model training.

- -

โžก D-Tale : Provides a spreadsheet-like interactive interface for filtering, - sorting, visualizing, and modifying data in real time.

- -

- ๐Ÿ“– Learn how to use these tools in our - shell.openExternal("https://medomicslab.gitbook.io/medomics-docs/tutorials/design/exploratory-module")} - style={{ color: "#4991dfff", textDecoration: "none", cursor: "pointer" }} - > documentation. ๐Ÿ”— - -

- - - - -
- + +

+ The Exploratory Module helps you understand your data, detect potential issues, + and make informed decisions for preprocessing and model selection. +

+

SweetViz: Automated reports with distributions, correlations, and comparisons.

+

Y-Data Profiling: Deep statistical audit of feature types, missing values, and relationships.

+

D-Tale: Spreadsheet-like interface for filtering, sorting, and visualizing data in real time.

+ + } + > + + + +
) } diff --git a/renderer/components/mainPages/extractionLandingPage.jsx b/renderer/components/mainPages/extractionLandingPage.jsx index 0619b629..0236c939 100644 --- a/renderer/components/mainPages/extractionLandingPage.jsx +++ b/renderer/components/mainPages/extractionLandingPage.jsx @@ -6,8 +6,8 @@ import { FaAlignJustify, FaChartLine } from "react-icons/fa" import { FaImage } from "react-icons/fa6" import { LuBrain } from "react-icons/lu" import medimlLogo from "../../../resources/MEDimlLogo.png" -import myimage from "../../../resources/medomics_transparent_bg.png" import { LayoutModelContext } from "../layout/layoutContext" +import ModuleLandingShell, { ModuleGuideText } from "./moduleBasics/ModuleLandingShell" export default function ExtractionLandingPage() { @@ -20,114 +20,108 @@ export default function ExtractionLandingPage() { } return ( -
-

- Extraction Module -

-
- -
+ +

+ This module offers ready-made tools to pull insights from images, notes, and health tracking data, + while relying on the specialized MEDiml tool to extract deeper features from medical scans. +

+ + } + documentation={{ + url: "https://medomicslab.gitbook.io/medomics-docs/tutorials/design/extraction-modules", + label: "Extraction Module documentation", + }} + > + + - {/* Description of the Extraction Module */} -
-
- This input module offers ready-made tools to pull insights from images, notes, and health tracking data, - while relying on the specialized MEDiml tool to extract deeper features from medical scans. -
-
+ + + + +
MEDiml
+
+ + MEDiml + + Extract radiomic features from medical images and analyze your radiomics data using machine learning models. + + + +
-
- - - {/* Disabled warning message */} - - - {/* Main Title and Subtitle */} -
- - {/* MEDiml Card */} - - - -
MEDiml
-
- - MEDiml - - Extract radiomic features from medical images and analyze your radiomics data using machine learning models. - - - -
- - {/* Text Card */} - - - -
Text
-
- - - - Extract Embeddings from Text Notes using a variety of models. - - - -
-
-
+ + + +
Text
+
+ + + + Extract Embeddings from Text Notes using a variety of models. + + + +
-
-
- -
- - {/* Time Series Card */} - - - -
Time Series
-
- - - - Extract time series characteristics from physiological signals using TSFRESH and other models. - - - -
+ + + + +
Time Series
+
+ + + + Extract time series characteristics from physiological signals using TSFRESH and other models. + + + +
- {/* Image Card */} - - - -
Image
-
- - - - Extract Embeddings from JPG images using a variety of models from TorchXRayVision python library. - - - -
-
-
+ + + +
Image
+
+ + + + Extract Embeddings from JPG images using a variety of models from TorchXRayVision python library. + + + +
-
-
+ + ) } diff --git a/renderer/components/mainPages/learningLandingPage.jsx b/renderer/components/mainPages/learningLandingPage.jsx new file mode 100644 index 00000000..0e452073 --- /dev/null +++ b/renderer/components/mainPages/learningLandingPage.jsx @@ -0,0 +1,499 @@ +import { randomUUID } from "crypto"; +import { Button } from "primereact/button"; +import { InputText } from "primereact/inputtext"; +import { useContext, useEffect, useState } from "react"; +import { Card, Stack } from "react-bootstrap"; +import { AiOutlineExperiment } from "react-icons/ai"; +import { LuBrainCircuit } from "react-icons/lu"; +import { toast } from "react-toastify"; +import { sceneDescription as learningSceneDescription } from "../../public/setupVariables/learningNodesParams"; +import { LayoutModelContext } from "../layout/layoutContext"; +import { getCollectionData } from "../dbComponents/utils"; +import { insertMEDDataObjectIfNotExists } from "../mongoDB/mongoDBUtils"; +import { DataContext } from "../workspace/dataContext"; +import { MEDDataObject } from "../workspace/NewMedDataObject"; +import { FaPlay } from "react-icons/fa"; +import ModuleLandingShell, { ModuleGuideSteps, ModuleGuideText } from "./moduleBasics/ModuleLandingShell"; + + +const buildOpenItem = (id, medObject) => ({ + index: id, + canMove: true, + isFolder: false, + children: medObject.childrenIDs || [], + data: medObject.name, + canRename: true, + type: medObject.type || "medml", + inWorkspace: medObject.inWorkspace ?? false, + path: medObject.path ?? null, + isLocked: medObject.isLocked ?? null, + usedIn: medObject.usedIn ?? null, +}); + +const BRAND_NAVY = "rgb(88, 131, 196)"; +const BRAND_BLUE = "rgb(171, 223, 147)"; + +const JOURNEY_STEPS = [ + { id: 1, label: "Readiness check", detail: "Ensure your data is ready for training using other modules" }, + { id: 2, label: "Name your experiment", detail: "Pick a clear, unique scene name" }, + { id: 3, label: "Build visually", detail: "Drag, drop, and connect nodes, no coding required" }, +]; + +export default function LearningLandingPage() { + const [nameExt, setNameExp] = useState(""); + const [nameML, setNameML] = useState(""); + const [nameExpError, setNameExpError] = useState(""); + const [nameMlError, setNameMLError] = useState(""); + const [isExtDisabled, setIsExtDisabled] = useState(true); + const [isMLDisabled, setIsMLDisabled] = useState(true); + const [experimentList, setExperimentList] = useState([]); + const [loadingExt, setLoadingExp] = useState(false); + const [loadingML, setLoadingML] = useState(false); + const [pendingOpenId, setPendingOpenId] = useState(null); + const [scenesExpanded, setScenesExpanded] = useState(false); + const { dispatchLayout, setLayoutRequestQueue } = useContext(LayoutModelContext); + const { globalData } = useContext(DataContext); + + useEffect(() => { + let cancelled = false; + + const loadExperiments = async () => { + const scenes = []; + + for (const id of Object.keys(globalData)) { + const medObject = globalData[id]; + if (medObject.type !== "medml") continue; + + let isExperiment = false; + const metadataId = MEDDataObject.getChildIDWithName(globalData, id, "metadata.json"); + if (metadataId) { + try { + const jsonContent = await getCollectionData(metadataId); + if (jsonContent?.[0]?.isExperiment !== undefined) { + isExperiment = !!jsonContent[0].isExperiment; + } + } catch { + // Default to main scene when metadata cannot be read. + } + } + + scenes.push({ + id, + name: medObject.name.replace(/\.medml$/i, ""), + displayName: medObject.name, + isExperiment, + openItem: buildOpenItem(id, medObject), + }); + } + + scenes.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); + + if (!cancelled) { + setExperimentList(scenes); + } + }; + + loadExperiments(); + return () => { + cancelled = true; + }; + }, [globalData]); + + useEffect(() => { + if (!pendingOpenId || !globalData[pendingOpenId]) return; + + setLoadingExp(false); + setLoadingML(false); + + const medObject = globalData[pendingOpenId]; + const openItem = buildOpenItem(pendingOpenId, medObject); + const type = openItem.type === "medml" ? "openInLearningModule" : null; + if (type === null) { + toast.error("We couldn't open this file type. Please contact support if this persists."); + return; + } + dispatchLayout({ type: type, payload: openItem }); + if (setLayoutRequestQueue) { + setLayoutRequestQueue((prev) => [...prev, { type: "DELETE_TAB", payload: { id: "learningLandingPage" } }]); + } else { + dispatchLayout({ type: "remove", payload: { name: "Learning Module" } }); + } + + setPendingOpenId(null); + }, [dispatchLayout, globalData, pendingOpenId, setLayoutRequestQueue]); + + const createSceneContent = async (sceneName, isExperiment) => { + const setLoading = isExperiment ? setLoadingExp : setLoadingML; + setLoading(true); + const trimmedSceneName = sceneName.trim(); + + const sceneId = randomUUID(); + + let sceneFolder = new MEDDataObject({ + id: randomUUID(), + name: trimmedSceneName, + type: "directory", + parentID: "EXPERIMENTS", + childrenIDs: [], + inWorkspace: false + }) + let sceneFolderId = await insertMEDDataObjectIfNotExists(sceneFolder) + + if (!isExperiment) { + for (const folder of learningSceneDescription.externalFolders) { + let medObject = new MEDDataObject({ + id: randomUUID(), + name: folder, + type: "directory", + parentID: sceneFolderId, + childrenIDs: [], + inWorkspace: false + }) + await insertMEDDataObjectIfNotExists(medObject) + } + } + + let sceneObject = new MEDDataObject({ + id: randomUUID(), + name: trimmedSceneName + ".medml", + type: "medml", + parentID: sceneFolderId, + childrenIDs: [], + inWorkspace: false + }) + let sceneObjectId = await insertMEDDataObjectIfNotExists(sceneObject) + + const emptyScene = [ + { + nodes: [], + edges: [], + viewport: { + x: 235.01823373389306, + y: 186.91830088750686, + zoom: 1.0, + }, + MLType: "classification", + intersections: [], + isExperiment: isExperiment, + }, + ]; + let metadataObject = new MEDDataObject({ + id: randomUUID(), + name: "metadata.json", + type: "json", + parentID: sceneObjectId, + childrenIDs: [], + inWorkspace: false + }) + await insertMEDDataObjectIfNotExists(metadataObject, null, emptyScene) + + let backendMetadataObject = new MEDDataObject({ + id: randomUUID(), + name: "backend_metadata.json", + type: "json", + parentID: sceneObjectId, + childrenIDs: [], + inWorkspace: false + }) + await insertMEDDataObjectIfNotExists(backendMetadataObject, null, emptyScene) + + for (const folder of learningSceneDescription.internalFolders) { + let medObject = new MEDDataObject({ + id: randomUUID(), + name: folder, + type: "directory", + parentID: sceneObjectId, + childrenIDs: [], + inWorkspace: false + }) + await insertMEDDataObjectIfNotExists(medObject) + } + + MEDDataObject.updateWorkspaceDataObject(); + setPendingOpenId(sceneObjectId || sceneId); + }; + + const validateName = (value, existingList, extension) => { + const trimmedName = value.trim(); + const isValidName = /^[A-Za-z0-9_-]+$/.test(trimmedName); + const existingNames = new Set(existingList); + const hasConflict = + trimmedName !== "" && (existingNames.has(trimmedName) || existingNames.has(`${trimmedName}.${extension}`)); + + if (trimmedName === "") return { error: "", disabled: true }; + if (!isValidName) { + return { error: "Use only letters, numbers, hyphens, or underscores.", disabled: true }; + } + if (hasConflict) { + return { error: "An experiment with this name already exists. Try a different name.", disabled: true }; + } + return { error: "", disabled: false }; + }; + + const experimentNames = experimentList.map((scene) => scene.displayName); + + const handleOpenScene = (item) => { + dispatchLayout({ type: "openInLearningModule", payload: item }); + if (setLayoutRequestQueue) { + setLayoutRequestQueue((prev) => [...prev, { type: "DELETE_TAB", payload: { id: "learningLandingPage" } }]); + } else { + dispatchLayout({ type: "remove", payload: { name: "Learning Module" } }); + } + }; + + const onNameExpChange = (e) => { + setNameExp(e); + const { error, disabled } = validateName(e, experimentNames, "medml"); + setNameExpError(error); + setIsExtDisabled(loadingExt || disabled); + }; + + const onNameMLChange = (e) => { + setNameML(e); + const { error, disabled } = validateName(e, experimentNames, "medml"); + setNameMLError(error); + setIsMLDisabled(loadingML || disabled); + }; + + const totalExperiments = experimentList.length; + const isCreating = loadingExt || loadingML; + + const workspaceFooter = totalExperiments === 0 ? ( +
+

No experiments yet

+

+ Name your first scene above and click Create & open to launch the visual builder. +

+
+ ) : ( +
+