{"id":18963940,"url":"https://github.com/janeliascicomp/videoannotation","last_synced_at":"2025-06-18T02:37:19.214Z","repository":{"id":253285833,"uuid":"618159449","full_name":"JaneliaSciComp/videoAnnotation","owner":"JaneliaSciComp","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-09T20:32:07.000Z","size":25126,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-06-09T21:29:51.281Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JaneliaSciComp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-03-23T21:52:46.000Z","updated_at":"2025-04-30T18:58:26.000Z","dependencies_parsed_at":"2025-03-01T00:19:57.303Z","dependency_job_id":"99dc3f39-5b6d-4d41-b8b6-63a6cfe90e34","html_url":"https://github.com/JaneliaSciComp/videoAnnotation","commit_stats":null,"previous_names":["janeliascicomp/videoannotation"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JaneliaSciComp/videoAnnotation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaneliaSciComp%2FvideoAnnotation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaneliaSciComp%2FvideoAnnotation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaneliaSciComp%2FvideoAnnotation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaneliaSciComp%2FvideoAnnotation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JaneliaSciComp","download_url":"https://codeload.github.com/JaneliaSciComp/videoAnnotation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JaneliaSciComp%2FvideoAnnotation/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260475935,"owners_count":23014942,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T14:22:35.092Z","updated_at":"2025-06-18T02:37:14.191Z","avatar_url":"https://github.com/JaneliaSciComp.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n## Quick Start\n\nWe will use ***React, Next.js*** and our library to create a simple video annotation web like this:\n\n![demo_page2_081524.png](/public/demo_page2_081524.png)\n\n\nIf you’re new to React or Next.js, check out [React](https://react.dev/learn) and [Next.js](https://nextjs.org/learn/foundations/about-nextjs).\n\n\n\n### Step 1: Initialize a new project\n\nFirst of all, you need to install [Node.js](https://nodejs.org/en/) (\u003e=19.0.0).\n\nThen clone this repo.\n\nThen outside of the repo dir, run\n```bash\nnpx create-next-app@13.2.4  # The version used to create the page\n```\n\nAnswer the popup questions. For my test, I used these settings. You can also customize your own.\n\n![nextjs_settings.png](/public/nextjs_settings.png)\n\n\n### Step 2: Copy our library to your project and install dependencies\n\n\u003e [!NOTE]\n\u003e This step will be replaced by `npm install ourLib` after we release this library\n\nCopy ***package.json, components, utils*** and ***styles*** folders from the repo dir to the root dir of the project you just created.\n\nIn the root dir of your project, install the dependencies.\n```bash\nnpm install\n```\n\n\n### Step 3: Run the development server\n\n```bash\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\n\n\n### Step 4: Modify `pages/index.js` \nYou can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.\n\nFor the web in the image above, the index.js file should look like this\n\n```javascript\nimport React, {useState} from 'react';\nimport Head from 'next/head';\nimport Workspace from '../components/Workspace.js';\nimport Canvas from '../components/Canvas.js';\nimport VideoUploader from '../components/VideoUploader.js';\nimport BtnContainer from '../components/BtnContainer.js';\nimport AnnotationTable from '../components/AnnotationTable.js';\nimport DownloadBtn from '../components/DownloadBtn.js';\nimport ProjectManager from '../components/ProjectManager.js';\nimport ModalJsonUploader from '../components/ModalJsonUploader.js';\nimport VideoManager from '../components/VideoManager.js';\nimport CanvasAdditionalDataController from '../components/CanvasAdditionalDataController.js';\nimport DropdownMenu from '../components/DropdownMenu.js';\nimport ProjectList from '../components/ProjectList.js';\nimport SaveAnnotationBtn from '../components/SaveAnnotationBtn.js';\nimport InfoBar from '../components/InfoBar.js';\nimport {Row, Col} from 'react-bootstrap'; // Third party components. Refer to the tutorial on https://react-bootstrap.netlify.app/docs/layout/grid\nimport { Menu, Modal } from 'antd'; // Third party components. Refer to the tutorial on https://ant.design/components/menu, and https://ant.design/components/modal\nimport { drawCircle, drawLine } from '../utils/canvasUtils.js'; // canvasUtils.js is a wrapper of fabric.js. It provides functions to operate on canvas easily. Currently, only these two functions are provided.\n\n\n// Client side components. They cannot be rendered on the server side, thus need to be explicitly marked as client side comp.\nimport dynamic from 'next/dynamic';\n\nconst AdditionalDataChart = dynamic(() =\u003e import('../components/AdditionalDataChart.js'), { ssr: false });\nconst AnnotationChart = dynamic(() =\u003e import('../components/AnnotationChart.js'), { ssr: false });\n\nexport default function Home() {\n  // The ..open/set..Open states are to allow the child modal components to control the visibility of themselves inside.\n  const [newProjectManagerOpen, setNewProjectManagerOpen] = useState(false);\n  const [editProjectManagerOpen, setEditProjectManagerOpen] = useState(false);\n  const [configUploaderOpen, setConfigUploaderOpen] = useState(false);\n  const [projectListOpen, setProjectListOpen] = useState(false);\n  const [videoManagerOpen, setVideoManagerOpen] = useState(false);\n  const [annotationUploaderOpen, setAnnotationUploaderOpen] = useState(false);\n  const [canvasAdditionalDataControllerOpen, setCanvasAdditionalDataControllerOpen] = useState(false);\n  const [info, setInfo] = useState(''); // To display feedback info\n\n  const projectDropdownItems = [\n    {\n      label: 'Exisiting Projects',\n      compName: 'ProjectList', // When pass a component, it is required to have a compName prop whose value should be equivalent to the name of the component, e.g. \u003cProjectList\u003e's compName is 'ProjectList'.\n      component: \u003cProjectList \n                  key='0' // When pass a component to a child, it is required to have a unique key prop.\n                  open={projectListOpen}\n                  setOpen={setProjectListOpen}\n                /\u003e,\n      // preventDefault: true, // when use some built-in components as the children of DropdownMenu, there may be some pre-defined behaviors, such as opening a modal window. To prevent the default behavior, set this to true.\n    },\n    {\n      label: 'New Project',\n      compName: 'ProjectManager',\n      component: \u003cProjectManager \n                  key='1'\n                  status='new' // ProjectManager has two status: 'new' and 'edit'. 'new' mode is for creating a new project, and 'edit' mode is for editing an existing project.\n                  open={newProjectManagerOpen} \n                  setOpen={setNewProjectManagerOpen}\n                  // defaultGroupType='category'\n                  // defaultBtnType='category'\n                  // disableGroupTypeSelect\n                  // disableBtnTypeSelect\n                  // hidePlusBtn\n                /\u003e,\n    },\n    {\n      label: 'Upload Project',\n      compName: 'ModalJsonUploader',\n      component: \u003cModalJsonUploader \n                  key='2'\n                  type='configuration' // ModalJsonUploader has two types: 'configuration' and 'annotation'. 'configuration' is for uploading a configuration file, and 'annotation' is for uploading an annotation file.\n                  open={configUploaderOpen} \n                  setOpen={setConfigUploaderOpen}\n                /\u003e, \n    },\n    {\n      label: 'Edit Project',\n      compName: 'ProjectManager',\n      component: \u003cProjectManager\n                  key='3' \n                  status='edit' \n                  open={editProjectManagerOpen} \n                  setOpen={setEditProjectManagerOpen}\n                  // defaultGroupType='category'\n                  // defaultBtnType='category'\n                  // disableGroupTypeSelect\n                  // disableBtnTypeSelect\n                  // hidePlusBtn\n                /\u003e, \n    },\n    {\n      label: 'Download Configuration',\n      compName: 'DownloadBtn',\n      component: \u003cDownloadBtn \n                  key='4'\n                  type='configuration'  // DownloadBtn has two types: 'configuration' and 'annotation'. 'configuration' is for saving the configuration data, and 'annotation' is for saving the annotation data.\n                  mode='inMenu' // DownloadBtn has two modes: 'inMenu' and 'solely'. 'inMenu' is used when the DownloadBtn is used in a dropdown menu, and 'solely' is used when the DownloadBtn is used as a standalone button. This prop affects the UI of the DownloadBtn.\n                /\u003e,\n    },\n];\n  \n  function projectDropdownClickHandler(e) {\n    /**\n     * Click event handler for DropdownMenu. Will be called after the default behavior of each child.\n     * e is the event object which has a key property corresponding to the index(integer) of each child in the 'menu' prop. This may be different with the 'key' prop of the component passed to each child.\n     * */ \n    // console.log(e);\n    // TODO: customize click handler\n    \n    // const label = projectDropdownItems[e.key].label;\n    // switch (label) {\n    //   case 'Exisiting Projects':\n    //       setInfo('Exisiting Projects');\n    //       break;\n    //   case 'New Project':\n    //       setInfo('New Project');\n    //       break;\n    //   case 'Upload Project':\n    //       setInfo('Upload Project');\n    //       break;\n    //   case 'Save Annotation':\n    //       setInfo('Save Annotation');\n    //       break;\n    //   case 'Edit Project':\n    //       setInfo('Edit Project');\n    //       break;\n    //   case 'Download Configuration':\n    //       setInfo('Download Configuration');\n    //       break;\n    // }\n  }\n\n  const projectDropdown = \u003cDropdownMenu name='Project' menu={projectDropdownItems} onClick={projectDropdownClickHandler}/\u003e;\n\n  const videoDropdownItems = [\n    {\n      label: 'Video Manager',\n      compName: 'VideoManager',\n      component: \u003cModal\n                  key='0'  \n                  title='Video Manager'\n                  open={videoManagerOpen}\n                  setOpen={setVideoManagerOpen}\n                  onCancel={() =\u003e setVideoManagerOpen(false)}\n                  style={{overflowX: 'auto'}}\n                  footer={null}\n                \u003e\n                  \u003cVideoManager \n                    setModalOpen={setVideoManagerOpen} // To allow the child to contorl the visibility of the modal window, e.g. when the user clicks the 'load' or 'add and load' button.\n                    /**\n                     * name: str, // required and unique, used as var name, no white space allowed.\n                     * label: str, // required, label shown to the user, allow white space\n                     * required: boolean, // false by default\n                     * loadIn: 'canvas'/'chart'/null, // whether to draw the data on canvas/chart with each frame. If yes, will fetch the data from backend and ask canvas/chart to draw it\n                     * onLoad: event handler. Can be used to draw shapes on canvas and so on. required when loadin='canvas' \n                     */\n                    // additionalFields={[\n                    //   {name: 'canvas1', label: 'canvas1', required: true, loadIn: 'canvas', onLoad: drawDataAsCircle}, \n                    //   {name: 'canvas2', label: 'canvas2', required: true, loadIn: 'canvas', onLoad: drawDataAsLine},\n                    //   {name: 'chart1', label: 'chart1', required: true, loadIn: 'chart'}, \n                    //   {name: 'chart2', label: 'chart2', required: true, loadIn: 'chart'}\n                    // ]}\n                  /\u003e\n                \u003c/Modal\u003e,\n    },\n    {\n      label: 'Additional Data For Canvas',\n      compName: 'CanvasAdditionalDataController',\n      component: \u003cModal\n                  key='1'\n                  title='Canvas Additional Data Controller'\n                  open={canvasAdditionalDataControllerOpen}\n                  setOpen={setCanvasAdditionalDataControllerOpen}\n                  onCancel={() =\u003e setCanvasAdditionalDataControllerOpen(false)}\n                  style={{overflowX: 'auto'}}\n                  footer={null}\n                \u003e\n                    \u003cCanvasAdditionalDataController \n                      // hideRange  //Hide the range input.\n                      // halfRange={2} //Allow developer to set half range value when hideRange is true. Required and only useful when hideRange is true.\n                      defaultHalfRange={1} // Default value for half range input. Should only be used when hideRange is false.\n                    /\u003e\n                \u003c/Modal\u003e,\n    },\n  ];\n\n  function drawDataAsCircle(params) {\n    /**\n     * OnLoad event handler for the additional data for canvas.\n     * \n     * params: {\n     *      target: fabric obj needed for the drawing. Just pass it to the imported func from canvasUtils.js\n     *      data: [additional data in needed range]\n     * }\n     */\n    for (let c of params.data) {\n      c.push(3); // add radius\n      drawCircle(params.target, c, 'red');\n    }\n  }\n\n  function drawDataAsLine(params) {\n    for (let l of params.data) {\n      drawLine(params.target, l, 'white');\n    }\n    \n  }\n\n  function videoDropdownClickHandler(e) {\n    // TODO: customize click handler\n  }\n\n  const videoDropdown = \u003cDropdownMenu name='Video' menu={videoDropdownItems} onClick={videoDropdownClickHandler}/\u003e\n\n  const annotationDropdownItems = [\n    {\n      label: 'Save Annotation',\n      compName: 'SaveAnnotationBtn',\n      component: \u003cSaveAnnotationBtn \n                  key='0'\n                  mode='inMenu'\n                /\u003e,\n    },\n    {\n      label: 'Download Annotation',\n      compName: 'DownloadBtn',\n      component: \u003cDownloadBtn \n                  key='1'\n                  type='annotation' \n                  mode='inMenu'\n                /\u003e,\n    },\n    {\n      label: 'Upload Annotation',\n      compName: 'ModalJsonUploader',\n      component: \u003cModalJsonUploader \n                  key='2'\n                  type='annotation' \n                  open={annotationUploaderOpen} \n                  setOpen={setAnnotationUploaderOpen}\n                /\u003e, \n    },\n  ];\n\n  function annotationDropdownClickHandler(e) {\n    // TODO: customize click handler\n  }\n\n  const annotationDropdown = \u003cDropdownMenu name='Annotation' menu={annotationDropdownItems} onClick={annotationDropdownClickHandler}/\u003e\n\n\n  const menubarItems = [\n    {\n      label: projectDropdown,\n      key: '0',\n    },\n    {\n      label: videoDropdown,\n      key: '1',\n    },\n    {\n      label: annotationDropdown,\n      key: '2',\n    },\n  ]\n  \n\n  return (\n    \u003cdiv\u003e\n      \u003cHead\u003e\n        \u003ctitle\u003eAnnotator\u003c/title\u003e\n        \u003clink rel=\"icon\" href=\"/favicon.ico\" /\u003e\n      \u003c/Head\u003e\n\n      \u003cWorkspace \u003e \n        \u003cMenu items={menubarItems} mode=\"horizontal\"/\u003e\n        \u003cdiv className='py-2'\u003e\n          {/* If state info is null, InfoBar will only display predefined information for events. Otherwise, will display both predefined and contents of the info state */}\n          \u003cInfoBar info={info} /\u003e\n        \u003c/div\u003e\n        \n        \u003cRow \u003e\n          \u003cCol lg='auto'\u003e\n            \u003ccanvas width={350} height={250} style={{border: 'solid'}}/\u003e\n            \u003cAnnotationTable width={350} height={250} scrollY={230} ellipsis /\u003e\n            \u003cBtnContainer /\u003e\n          \u003c/Col\u003e\n          \u003cCol\u003e\n              \u003cCanvas width={550} height={350}/\u003e\n              \u003cVideoUploader \n                hideSubmit \n                /\u003e\n              \u003cdiv className='py-2' style={{height: '100px', width: '670px', border: 'solid 1px black'}} \u003e\n                \u003cAnnotationChart \n                  labels = {['chase', 'no-chase']}\n                  // omitXLables\n                /\u003e\n              \u003c/div\u003e\n              \u003cdiv className='py-2' style={{height: '150px', width: '670px', border: 'solid 1px black'}} \u003e\n                \u003cAdditionalDataChart\n                  // hideRange  //Hide the range input.\n                  // halfRange={5}  //Allow developer to set half range value when hideRange is true. Required and only useful when hideRange is true.\n                  defaultHalfRange={50}  // Default value for half range input. Should only be used when hideRange is false.\n                  /\u003e\n              \u003c/div\u003e\n          \u003c/Col\u003e\n        \u003c/Row\u003e\n      \u003c/Workspace\u003e\n    \u003c/div\u003e\n  )\n}\n\n```\n\n\u003e [!NOTE]\n\u003e In order to process the video, you need to set up a backend server. We will cover this in another [repo](https://github.com/JaneliaSciComp/videoAnnotation_backend/tree/main).\n\nHere we used react-bootstrap for the layout. Check out the [tutorial](https://react-bootstrap.netlify.app/docs/layout/grid), and also [Ant Design](https://ant.design/components)'s components.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaneliascicomp%2Fvideoannotation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaneliascicomp%2Fvideoannotation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaneliascicomp%2Fvideoannotation/lists"}