From e6d2d997615b35ca6aa4af547312b5542f7efced Mon Sep 17 00:00:00 2001
From: Karo <44981312+KaroMourad@users.noreply.github.com>
Date: Mon, 31 Jul 2023 12:32:22 +0400
Subject: [PATCH] [feat] Add Images Explorer (#2947)
---
.../ui/src/components/FigureBox/FigureBox.tsx | 2 +-
.../ui/src/components/ImageBox/ImageBox.tsx | 7 +-
.../BaseExplorer/components/Image/Image.tsx | 26 ++++++
.../BaseExplorer/components/Image/index.ts | 2 +
.../src/pages/Board/components/AudiosList.tsx | 7 --
.../pages/Board/components/FiguresList.tsx | 9 --
.../src/pages/Board/components/ImagesList.tsx | 6 --
.../ProjectStatisticsStore.ts | 2 +-
.../useProjectStatistics.tsx | 14 +--
.../pages/Explorers/ImagesExplorer/config.ts | 52 +++++++++++
.../ImagesExplorer/getStaticContent.tsx | 52 +++++++++++
.../pages/Explorers/ImagesExplorer/index.tsx | 33 +++++++
src/aimcore/web/ui/src/routes/routes.tsx | 91 +++++++------------
13 files changed, 208 insertions(+), 95 deletions(-)
create mode 100644 src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx
create mode 100644 src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts
create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts
create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx
create mode 100644 src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx
diff --git a/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx b/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx
index d7290aa4fb..be864f5dca 100644
--- a/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx
+++ b/src/aimcore/web/ui/src/components/FigureBox/FigureBox.tsx
@@ -70,7 +70,7 @@ function FigureBox(props: FigureBoxProps) {
onUpdate={onAutoSize}
/>
) : (
-
+
)}
diff --git a/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx b/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx
index 797768d249..208b3cc705 100644
--- a/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx
+++ b/src/aimcore/web/ui/src/components/ImageBox/ImageBox.tsx
@@ -1,9 +1,8 @@
import React from 'react';
import { useImageBlobURI } from 'hooks';
-import { Skeleton } from '@material-ui/lab';
-
import ErrorBoundary from 'components/ErrorBoundary';
+import { Spinner } from 'components/kit';
import { ImageBoxProps } from './';
@@ -20,9 +19,7 @@ function ImageBox(props: ImageBoxProps) {
alt={caption}
/>
) : (
-
-
-
+
)}
diff --git a/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx
new file mode 100644
index 0000000000..ea84aa4659
--- /dev/null
+++ b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/Image.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import ErrorBoundary from 'components/ErrorBoundary';
+import ImageBox from 'components/ImageBox';
+
+import { IBoxContentProps } from 'modules/BaseExplorer/types';
+
+function Image(props: IBoxContentProps) {
+ return (
+
+
+
+ );
+}
+
+export default React.memo(Image);
diff --git a/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts
new file mode 100644
index 0000000000..5e9fd8898b
--- /dev/null
+++ b/src/aimcore/web/ui/src/modules/BaseExplorer/components/Image/index.ts
@@ -0,0 +1,2 @@
+import Image from './Image';
+export default Image;
diff --git a/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx b/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx
index 89af5c93f8..4365eb8af2 100644
--- a/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx
+++ b/src/aimcore/web/ui/src/pages/Board/components/AudiosList.tsx
@@ -12,12 +12,6 @@ function AudiosList(props: any) {
...audio.record,
}));
- const boxStyle = {
- margin: '5px',
- height: 50,
- width: 'calc(100% - 10px)',
- flex: 1,
- };
const boxKey = (item: any) =>
`${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${
item.step
@@ -27,7 +21,6 @@ function AudiosList(props: any) {
{data.map((item: any) => (
`${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${
item.step
@@ -29,7 +21,6 @@ function FiguresList(props: any) {
{data.map((item: any) => (
`${item?.container?.hash}_${item.name}_${JSON.stringify(item.context)}_${
item.step
@@ -26,7 +21,6 @@ function ImagesList(props: any) {
{data.map((item: any) => (
= {
iconBgColor: '#18AB6D',
navLink: routes.FIGURES_EXPLORER.path,
},
- // [SequenceType.Image]: {
- // label: 'Images',
- // icon: 'images',
- // count: 0,
- // iconBgColor: '#F17922',
- // navLink: routes.IMAGES_EXPLORER.path,
- // },
+ [SequenceType.Image]: {
+ label: 'Images',
+ icon: 'images',
+ count: 0,
+ iconBgColor: '#F17922',
+ navLink: routes.IMAGES_EXPLORER.path,
+ },
[SequenceType.Audio]: {
label: 'Audios',
icon: 'audios',
diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts
new file mode 100644
index 0000000000..48ebed0db6
--- /dev/null
+++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/config.ts
@@ -0,0 +1,52 @@
+import produce from 'immer';
+
+import { getDefaultHydration } from 'modules/BaseExplorer';
+import { GroupType, Order } from 'modules/core/pipeline';
+import { defaultHydration } from 'modules/BaseExplorer/getDefaultHydration';
+import { CaptionProperties } from 'modules/BaseExplorer/components/Controls';
+
+import { GetSequenceName, SequenceType } from 'types/core/enums';
+
+import getImagesExplorerStaticContent from './getStaticContent';
+
+export const getImagesDefaultConfig = (): typeof defaultHydration => {
+ const defaultConfig = getDefaultHydration();
+ const sequenceName = GetSequenceName(SequenceType.Image);
+
+ const groupings = produce(defaultConfig.groupings, (draft: any) => {
+ draft[GroupType.COLUMN].defaultApplications.orders = [Order.ASC, Order.ASC];
+ draft[GroupType.COLUMN].defaultApplications.fields = [
+ 'run.hash',
+ `${sequenceName}.name`,
+ ];
+ draft[GroupType.ROW].defaultApplications.orders = [Order.DESC];
+ draft[GroupType.ROW].defaultApplications.fields = ['record.step'];
+
+ draft[GroupType.GRID].defaultApplications.orders = [Order.ASC];
+ draft[GroupType.GRID].defaultApplications.fields = [`${sequenceName}.name`];
+ });
+
+ const controls = produce(defaultConfig.controls, (draft: any) => {
+ draft.captionProperties = {
+ component: CaptionProperties,
+ state: {
+ initialState: {
+ displayBoxCaption: true,
+ selectedFields: [
+ 'run.name',
+ `${sequenceName}.name`,
+ `${sequenceName}.context`,
+ ],
+ },
+ persist: 'url',
+ },
+ };
+ });
+
+ return {
+ ...defaultConfig,
+ groupings,
+ controls,
+ getStaticContent: getImagesExplorerStaticContent,
+ };
+};
diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx
new file mode 100644
index 0000000000..8d2faf5151
--- /dev/null
+++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/getStaticContent.tsx
@@ -0,0 +1,52 @@
+import * as React from 'react';
+
+import { DOCUMENTATIONS } from 'config/references';
+
+import getBaseExplorerStaticContent, {
+ STATIC_CONTENT_TYPES,
+} from 'modules/BaseExplorer/utils/getBaseExplorerStaticContent';
+import { StaticContentType } from 'modules/BaseExplorer/types';
+
+function getImagesExplorerStaticContent(
+ type: StaticContentType,
+): React.ReactNode {
+ const illustrationContent = getImagesExplorerIllustrationContent(type);
+ return getBaseExplorerStaticContent(type, illustrationContent);
+}
+
+function getImagesExplorerIllustrationContent(
+ type: StaticContentType,
+): React.ReactNode {
+ const Never_Executed = (
+ <>
+ It’s super easy to search Aim experiments. Just start typing your query in
+ the search bar above.
+
+ Look up
+
+ search docs
+
+ to learn more.
+ >
+ );
+ const Failed = 'Incorrect Query';
+ const Insufficient_Resources = "You don't have any tracked images";
+ const Empty = 'No Results';
+ const Empty_Bookmarks = "You don't have any saved bookmark";
+
+ const CONTENT = {
+ [STATIC_CONTENT_TYPES.Never_Executed]: Never_Executed,
+ [STATIC_CONTENT_TYPES.Failed]: Failed,
+ [STATIC_CONTENT_TYPES.Insufficient_Resources]: Insufficient_Resources,
+ [STATIC_CONTENT_TYPES.Empty]: Empty,
+ [STATIC_CONTENT_TYPES.Empty_Bookmarks]: Empty_Bookmarks,
+ };
+ return CONTENT[type] || null;
+}
+
+export default getImagesExplorerStaticContent;
diff --git a/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx
new file mode 100644
index 0000000000..65ec258808
--- /dev/null
+++ b/src/aimcore/web/ui/src/pages/Explorers/ImagesExplorer/index.tsx
@@ -0,0 +1,33 @@
+import { isDEVModeOn } from 'config/config';
+
+import renderer from 'modules/BaseExplorer';
+import Image from 'modules/BaseExplorer/components/Image';
+
+import { SequenceType } from 'types/core/enums';
+
+import { getImagesDefaultConfig } from './config';
+
+const defaultConfig = getImagesDefaultConfig();
+
+export const imagesExplorerConfig = {
+ name: 'Images Explorer',
+ sequenceType: SequenceType.Image,
+ basePath: 'images',
+ persist: true,
+ groupings: defaultConfig.groupings,
+ visualizations: {
+ vis1: {
+ component: defaultConfig.Visualizer,
+ controls: defaultConfig.controls,
+ box: {
+ ...defaultConfig.box,
+ component: Image,
+ },
+ },
+ },
+ getStaticContent: defaultConfig.getStaticContent,
+};
+
+const ImagesExplorer = renderer(imagesExplorerConfig, isDEVModeOn);
+
+export default ImagesExplorer;
diff --git a/src/aimcore/web/ui/src/routes/routes.tsx b/src/aimcore/web/ui/src/routes/routes.tsx
index 6aeb30fe32..96cd244f3a 100644
--- a/src/aimcore/web/ui/src/routes/routes.tsx
+++ b/src/aimcore/web/ui/src/routes/routes.tsx
@@ -1,13 +1,6 @@
import React from 'react';
-import {
- IconChartDots,
- IconFileAnalytics,
- IconFlag3,
- IconLayout2,
- IconTable,
- IconBox,
-} from '@tabler/icons-react';
+import { IconChartDots, IconFileAnalytics, IconBox } from '@tabler/icons-react';
import { PathEnum } from 'config/enums/routesEnum';
import { ExplorersCatsEnum } from 'config/enums/explorersCatsEnum';
@@ -76,6 +69,12 @@ const AudiosExplorer = React.lazy(
/* webpackChunkName: "AudiosExplorer" */ 'pages/Explorers/AudiosExplorer'
),
);
+const ImagesExplorer = React.lazy(
+ () =>
+ import(
+ /* webpackChunkName: "ImagesExplorer" */ 'pages/Explorers/ImagesExplorer'
+ ),
+);
const MetricsExplorer = React.lazy(
() =>
@@ -138,32 +137,18 @@ export enum RouteStatusEnum {
}
export const explorersRoutes: { [key: string]: IRoute } = {
- // METRICS: {
- // path: PathEnum.Metrics,
- // component: Metrics,
- // showInSidebar: false,
- // displayName: 'Metrics',
- // description:
- // 'Metrics Explorer allows to filter, group, aggregate tracked metrics.',
- // icon: 'metrics',
- // isExact: true,
- // title: pageTitlesEnum.METRICS_EXPLORER,
- // color: '#7A4CE0',
- // category: ExplorersCatsEnum.Trainings,
- // },
- // PARAMS: {
- // path: PathEnum.Params,
- // component: Params,
- // showInSidebar: false,
- // displayName: 'Params',
- // description:
- // 'Params Explorer helps to visualize tracked h-params and metrics results via parallel coordinates plot.',
- // icon: 'params',
- // isExact: true,
- // title: pageTitlesEnum.PARAMS_EXPLORER,
- // color: '#AF4EAB',
- // category: ExplorersCatsEnum.Trainings,
- // },
+ METRICS_EXPLORER: {
+ path: PathEnum.Metrics_Explorer,
+ component: MetricsExplorer,
+ showInSidebar: false,
+ icon: 'metrics',
+ displayName: 'Metrics',
+ description: 'Explore thousands of tracked metrics with Metrics Explorer.',
+ isExact: true,
+ title: pageTitlesEnum.METRICS_EXPLORER,
+ color: '#7A4CE0',
+ category: ExplorersCatsEnum.Trainings,
+ },
TEXT_EXPLORER: {
path: PathEnum.Text_Explorer,
component: TextExplorer,
@@ -190,19 +175,19 @@ export const explorersRoutes: { [key: string]: IRoute } = {
color: '#1473E6',
category: ExplorersCatsEnum.Prompts,
},
- // IMAGES_EXPLORER: {
- // path: PathEnum.Images_Explorer,
- // component: ImagesExplore,
- // showInSidebar: false,
- // displayName: 'Images',
- // description:
- // 'Images Explorer allows comparison of tracked images during training and evaluation.',
- // icon: 'images',
- // isExact: true,
- // title: pageTitlesEnum.IMAGES_EXPLORER,
- // color: '#F17922',
- // category: ExplorersCatsEnum.Trainings,
- // },
+ IMAGES_EXPLORER: {
+ path: PathEnum.Images_Explorer,
+ component: ImagesExplorer,
+ showInSidebar: false,
+ displayName: 'Images',
+ description:
+ 'Images Explorer allows comparison of tracked images during training and evaluation.',
+ icon: 'images',
+ isExact: true,
+ title: pageTitlesEnum.IMAGES_EXPLORER,
+ color: '#F17922',
+ category: ExplorersCatsEnum.Trainings,
+ },
FIGURES_EXPLORER: {
path: PathEnum.Figures_Explorer,
component: FiguresExplorer,
@@ -242,18 +227,6 @@ export const explorersRoutes: { [key: string]: IRoute } = {
// color: '#606986',
// category: ExplorersCatsEnum.Trainings,
// },
- METRICS_EXPLORER: {
- path: PathEnum.Metrics_Explorer,
- component: MetricsExplorer,
- showInSidebar: false,
- icon: 'metrics',
- displayName: 'Metrics',
- description: 'Explore thousands of tracked metrics with Metrics Explorer.',
- isExact: true,
- title: pageTitlesEnum.METRICS_EXPLORER,
- color: '#7A4CE0',
- category: ExplorersCatsEnum.Trainings,
- },
};
const routes: { [key: string]: any } = {