Browse Source

init

tags/v0.3.0
之江实验室 4 years ago
parent
commit
f973f968df
11 changed files with 97 additions and 166 deletions
  1. +0
    -9
      LICENSE
  2. +0
    -8
      README.md
  3. +5
    -31
      dubhe-server/README.md
  4. +2
    -70
      webapp/README.md
  5. +3
    -2
      webapp/src/components/UploadForm/index.vue
  6. +3
    -2
      webapp/src/components/UploadForm/inline.vue
  7. +34
    -4
      webapp/src/components/UploadForm/util.js
  8. +4
    -3
      webapp/src/views/dataset/annotate/thumbContainer/index.vue
  9. +8
    -4
      webapp/src/views/dataset/classify.vue
  10. +12
    -33
      webapp/src/views/dataset/list/index.vue
  11. +26
    -0
      webapp/src/views/dataset/util.js

+ 0
- 9
LICENSE View File

@@ -200,12 +200,3 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.

Other dependencies and licenses:
----------------------------------------------------------------------------------------

Open Source Software Licensed Under the Apache License, Version 2.0:
The below software in this distribution may have been modified.
----------------------------------------------------------------------------------------
1. EL-ADMIN
Copyright 2019-2020 Zheng Jie

+ 0
- 8
README.md View File

@@ -35,14 +35,6 @@
## 技术架构 ## 技术架构
![技术架构](http://cdn.qjycloud.com/tech-arc.jpg "技术架构") ![技术架构](http://cdn.qjycloud.com/tech-arc.jpg "技术架构")


## 技术栈
- 后端: [Spring Boot](https://spring.io/projects/spring-boot)
- 前端: [Vue.js](https://vuejs.org/), [Element](https://element.eleme.cn/)
- 数据处理 [Yolo](https://pjreddie.com/darknet/yolo/) ...
- 可视化: [Django](https://www.djangoproject.com/) ...
- 中间件: [MySQL](https://www.mysql.com/), [MyBatis-Plus](https://mp.baomidou.com/), [Redis](https://redis.io/)
- 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)

## 反馈问题 ## 反馈问题


- [在线社区](http://www.aiiaos.cn/index.php?s=/forum/index/forum/id/45.html) - [在线社区](http://www.aiiaos.cn/index.php?s=/forum/index/forum/id/45.html)


+ 5
- 31
dubhe-server/README.md View File

@@ -1,8 +1,6 @@
# 之江天枢-服务端
# 一站式开发平台-服务端


**之江天枢一站式人工智能开源平台**(简称:**之江天枢**),包括海量数据处理、交互式模型构建(包含Notebook和模型可视化)、AI模型高效训练。多维度产品形态满足从开发者到大型企业的不同需求,将提升人工智能技术的研发效率、扩大算法模型的应用范围,进一步构建人工智能生态“朋友圈”。

## 源码部署
## 本地开发


### 准备环境 ### 准备环境
安装如下软件环境。 安装如下软件环境。
@@ -11,13 +9,6 @@
- Maven: 3.0+ - Maven: 3.0+
- MYSQL: 5.5.0+ - MYSQL: 5.5.0+


### 下载源码
``` bash
git clone https://codeup.teambition.com/zhejianglab/dubhe-server.git
# 进入项目根目录
cd dubhe-server
```

### 创建DB ### 创建DB
在MySQL中依次执行如下sql文件 在MySQL中依次执行如下sql文件
``` ```
@@ -29,31 +20,14 @@ sql/v1/02-Dubhe-DML.sql
### 配置 ### 配置
根据实际情况修改如下配置文件。 根据实际情况修改如下配置文件。
``` ```
dubhe-admin/src/main/resources/config/application-prod.yml
dubhe-admin/src/main/resources/config/application-dev.yml
``` ```


### 构建
``` bash
# 构建,生成的 jar 包位于 ./dubhe-admin/target/dubhe-admin-1.0.jar
mvn clean compile package
### 启动:
``` ```

### 启动
``` bash
# 指定启动环境为 prod
java -jar ./dubhe-admin/target/dubhe-admin-1.0.jar --spring.profiles.active=prod
mvn spring-boot:run
``` ```


## 本地开发

### 必要条件:
导入maven项目,下载所需的依赖包
mysql下创建数据库dubhe,初始化数据脚本
安装redis

### 启动:
mvn spring-boot:run

## 代码结构: ## 代码结构:
``` ```
├── common 公共模块 ├── common 公共模块


+ 2
- 70
webapp/README.md View File

@@ -1,78 +1,10 @@
# 一站式开发平台-前端 # 一站式开发平台-前端


**天枢人工智能开源开放平台**(简称:**天枢平台**)是天枢平台由之江实验室牵头,联合北京一流科技、中国信通院和浙江大学共同自研的人工智能开源平台。整个平台由一站式AI模型开发平台、高性能深度学习框架和模型炼知框架三大子系统组成。

其中, **一站式AI模型开发平台面**(简称:**一站式开发平台**)面向AI模型生产的生命周期,提供了包括数据处理、模型开发、模型训练和模型管理等功能,方便用户一站式构建AI算法。

## 特性
* 一站式开发
* 集成先进算法
* 灵活易用
* 性能优越

## 预览
![概览](/public/dubhe_dashboard.png "概览")

## 源码部署

### 1. 下载源码

``` bash
git clone https://codeup.teambition.com/zhejianglab/dubhe-web.git

# 进入根目录
cd dubhe-web

```
### 2. 配置

根据需要修改如下配置文件
```
config/index.js
settings.js
.env.production
```

### 3. 构建

``` bash
# 安装项目依赖
npm install

# 构建生产环境
npm run build:prod
```

### 4. 部署

- 构建完成后会在根目录生成 dist 文件夹,并将该文件夹上传至服务器;
- 在服务器 nginx.conf 文件中添加如下配置;

``` nginx
server {
listen 80; # 端口
server_name localhost; # 域名/外网IP

location / {
root /home/wwwroot/dubhe-web/dist; # dist 文件夹根目录
index index.html;
try_files $uri $uri/ /index.html;
}
}

```

- 保存 `nginx.conf` 并重启 Nginx 使之生效。


## 本地开发 ## 本地开发


``` bash ``` bash
# 下载源码
git clone https://codeup.teambition.com/zhejianglab/dubhe-web.git

# 进入项目根目录
cd dubhe-web
# 进入前端项目根目录
cd webapp


# 安装依赖 # 安装依赖
npm install npm install


+ 3
- 2
webapp/src/components/UploadForm/index.vue View File

@@ -55,6 +55,7 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
transformFile: Function,
hash: { hash: {
type: Boolean, type: Boolean,
default: true, default: true,
@@ -67,7 +68,7 @@ export default {
}, },
}, },
setup(props, ctx) { setup(props, ctx) {
const { toggleVisible, request } = props;
const { toggleVisible, request, transformFile } = props;
const formRef = ref(null); const formRef = ref(null);
const state = reactive({ const state = reactive({
visible: props.visible, visible: props.visible,
@@ -98,7 +99,7 @@ export default {


state.uploading = true; state.uploading = true;
// 开始调用上传接口 // 开始调用上传接口
uploadReqeust && uploadReqeust({ ...props.params, fileList: renameFileList }, handleUploadProgress)
uploadReqeust && uploadReqeust({ ...props.params, fileList: renameFileList, transformFile }, handleUploadProgress)
.then(res => { .then(res => {
const outputPath = getFileOutputPath(renameFileList, props.params); const outputPath = getFileOutputPath(renameFileList, props.params);
state.uploading = false; state.uploading = false;


+ 3
- 2
webapp/src/components/UploadForm/inline.vue View File

@@ -38,6 +38,7 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
transformFile: Function,
hash: { hash: {
type: Boolean, type: Boolean,
default: true, default: true,
@@ -48,7 +49,7 @@ export default {
}, },
}, },
setup(props, ctx) { setup(props, ctx) {
const { request } = props;
const { request, transformFile } = props;
const formRef = ref(null); const formRef = ref(null);
const state = reactive({ const state = reactive({
uploading: false, uploading: false,
@@ -71,7 +72,7 @@ export default {
ctx.emit('uploadStart'); ctx.emit('uploadStart');
const uploadReqeust = request || minIOUpload; const uploadReqeust = request || minIOUpload;
// 开始调用上传接口 // 开始调用上传接口
return uploadReqeust({ ...props.params, fileList: renameFileList }, callback)
return uploadReqeust({ ...props.params, fileList: renameFileList, transformFile }, callback)
.then(res => { .then(res => {
const outputPath = getFileOutputPath(renameFileList, props.params); const outputPath = getFileOutputPath(renameFileList, props.params);
state.uploading = false; state.uploading = false;


+ 34
- 4
webapp/src/components/UploadForm/util.js View File

@@ -87,7 +87,7 @@ export const putObject = (uploadUrl, file, options = {}) => {
}; };


// 默认通过 minIO 上传 // 默认通过 minIO 上传
export const minIOUpload = async({ objectPath, fileList }, callback) => {
export const minIOUpload = async({ objectPath, fileList, transformFile }, callback) => {
// add 进度条 // add 进度条
let resolved = 0; let resolved = 0;


@@ -97,7 +97,7 @@ export const minIOUpload = async({ objectPath, fileList }, callback) => {


const uploadPrefix = `${minIOPrefix}/${bucketName}`; const uploadPrefix = `${minIOPrefix}/${bucketName}`;
const objectName = `${objectPath}/${d.name}`; const objectName = `${objectPath}/${d.name}`;
const result = await putObject(`${uploadPrefix}/${objectName}`, d.raw, {
const fileRes = await putObject(`${uploadPrefix}/${objectName}`, d.raw, {
objectName, objectName,
callback, callback,
}); });
@@ -110,11 +110,18 @@ export const minIOUpload = async({ objectPath, fileList }, callback) => {
if (typeof callback === 'function' && fileList.length > 1) { if (typeof callback === 'function' && fileList.length > 1) {
callback(resolved, fileList.length); callback(resolved, fileList.length);
} }
return result;

// 视频不做转换
if (isValidVideoFile(d)) return fileRes;

if (typeof transformFile === 'function') {
const transformed = await transformFile(fileRes, d);
return transformed;
}
return fileRes;
}; };


const result = await pMap(fileList, mapper, {concurrency: 10}); const result = await pMap(fileList, mapper, {concurrency: 10});

return result; return result;
}; };


@@ -125,3 +132,26 @@ export const hashify = (name, hash) => {
export const getFileOutputPath = (rawFiles, { objectPath }) => { export const getFileOutputPath = (rawFiles, { objectPath }) => {
return rawFiles.map(d => `${bucketHost}/${bucketName}/${objectPath}/${d.name}`); return rawFiles.map(d => `${bucketHost}/${bucketName}/${objectPath}/${d.name}`);
}; };

// 对文件进行自定义转换
export const transformFile = (result, file) => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
const img = new Image();
img.onload = () => resolve({
...result,
data: {
...result.data,
meta: {
width: img.width,
height: img.height,
},
},
});
img.src = reader.result;
}, false);

reader.readAsDataURL(file.raw);
});
};

+ 4
- 3
webapp/src/views/dataset/annotate/thumbContainer/index.vue View File

@@ -78,6 +78,7 @@
ref="uploaderRef" ref="uploaderRef"
action="fakeApi" action="fakeApi"
:visible="thumbState.showDialog" :visible="thumbState.showDialog"
:transformFile="withDimensionFile"
:toggleVisible="handleClose" :toggleVisible="handleClose"
:params="uploadParams" :params="uploadParams"
@uploadSuccess="uploadSuccess" @uploadSuccess="uploadSuccess"
@@ -92,7 +93,7 @@ import { Message } from 'element-ui';
import { pick } from 'lodash'; import { pick } from 'lodash';


import UploadForm from '@/components/UploadForm'; import UploadForm from '@/components/UploadForm';
import { fileTypeEnum, getImgFromMinIO, withDimensionFiles } from '@/views/dataset/util';
import { fileTypeEnum, getImgFromMinIO, withDimensionFile } from '@/views/dataset/util';
import { submit } from '@/api/preparation/datafile'; import { submit } from '@/api/preparation/datafile';
import { detectFileList, queryFileOffset } from '@/api/preparation/dataset'; import { detectFileList, queryFileOffset } from '@/api/preparation/dataset';
import List from './list'; import List from './list';
@@ -160,9 +161,8 @@ export default {


const uploadSuccess = async(res) => { const uploadSuccess = async(res) => {
const files = getImgFromMinIO(res); const files = getImgFromMinIO(res);
const _files = await withDimensionFiles(files);
// 提交业务上传 // 提交业务上传
submit(datasetId.value, _files).then(() => {
submit(datasetId.value, files).then(() => {
Message.success('上传成功'); Message.success('上传成功');
updateList({ type: thumbState.type }); updateList({ type: thumbState.type });
}); });
@@ -224,6 +224,7 @@ export default {


return { return {
thumbState, thumbState,
withDimensionFile,
uploadParams, uploadParams,
dropdownList, dropdownList,
handleUpload, handleUpload,


+ 8
- 4
webapp/src/views/dataset/classify.vue View File

@@ -19,6 +19,7 @@
<UploadForm <UploadForm
action="fakeApi" action="fakeApi"
:visible="uploadDialogVisible" :visible="uploadDialogVisible"
:transformFile="withDimensionFile"
:toggleVisible="handleClose" :toggleVisible="handleClose"
:params="uploadParams" :params="uploadParams"
@uploadSuccess="uploadSuccess" @uploadSuccess="uploadSuccess"
@@ -164,7 +165,7 @@ import { without, isNil } from 'lodash';
import { Message } from 'element-ui'; import { Message } from 'element-ui';
import { queryDataEnhanceList } from '@/api/preparation/dataset'; import { queryDataEnhanceList } from '@/api/preparation/dataset';


import { transformFile, transformFiles , getImgFromMinIO, dataEnhanceMap, withDimensionFiles } from '@/views/dataset/util';
import { transformFile, transformFiles, getImgFromMinIO, dataEnhanceMap, withDimensionFile } from '@/views/dataset/util';
import crudDataFile, { list, del , submit } from '@/api/preparation/datafile'; import crudDataFile, { list, del , submit } from '@/api/preparation/datafile';
import { getAutoLabels, getLabels, createLabel } from '@/api/preparation/datalabel'; import { getAutoLabels, getLabels, createLabel } from '@/api/preparation/datalabel';
import { batchFinishAnnotation } from '@/api/preparation/annotation'; import { batchFinishAnnotation } from '@/api/preparation/annotation';
@@ -232,6 +233,10 @@ export default {
}; };
}, },
computed: { computed: {
// 文件上传前携带尺寸信息
withDimensionFile() {
return withDimensionFile;
},
uploadParams() { uploadParams() {
return { return {
datasetId: this.datasetId, datasetId: this.datasetId,
@@ -412,10 +417,9 @@ export default {
}, },
async uploadSuccess(res) { async uploadSuccess(res) {
const files = getImgFromMinIO(res); const files = getImgFromMinIO(res);
const _files = await withDimensionFiles(files);
// 提交业务上传 // 提交业务上传
if (_files.length > 0) {
submit(this.datasetId, _files).then(() => {
if (files.length > 0) {
submit(this.datasetId, files).then(() => {
this.$message({ this.$message({
message: '上传文件成功', message: '上传文件成功',
type: 'success', type: 'success',


+ 12
- 33
webapp/src/views/dataset/list/index.vue View File

@@ -125,6 +125,7 @@
ref="initFileUploadForm" ref="initFileUploadForm"
action="fakeApi" action="fakeApi"
:params="uploadParams" :params="uploadParams"
:transformFile="withDimensionFile"
v-bind="optionCreateProps" v-bind="optionCreateProps"
@uploadSuccess="uploadSuccess" @uploadSuccess="uploadSuccess"
@uploadError="uploadError" @uploadError="uploadError"
@@ -203,6 +204,7 @@
:visible="uploadDialogVisible" :visible="uploadDialogVisible"
:toggleVisible="toggleUploadFormClose" :toggleVisible="toggleUploadFormClose"
:params="uploadParams" :params="uploadParams"
:transformFile="withDimensionFile"
v-bind="optionImportProps" v-bind="optionImportProps"
@uploadSuccess="uploadSuccess" @uploadSuccess="uploadSuccess"
@uploadError="uploadError" @uploadError="uploadError"
@@ -446,7 +448,7 @@ import InfoSelect from '@/components/InfoSelect';
import { getAutoLabels } from '@/api/preparation/datalabel'; import { getAutoLabels } from '@/api/preparation/datalabel';


import { submit, submitVideo } from '@/api/preparation/datafile'; import { submit, submitVideo } from '@/api/preparation/datafile';
import { getImgFromMinIO, getFullFileUrl, annotationMap, dataTypeMap, annotationProgressMap, decompressProgressMap, datasetStatusMap, withDimensionFiles } from '@/views/dataset/util';
import { getImgFromMinIO, annotationMap, dataTypeMap, annotationProgressMap, decompressProgressMap, datasetStatusMap, withDimensionFile } from '@/views/dataset/util';
import Edit from '@/components/InlineTableEdit'; import Edit from '@/components/InlineTableEdit';
import BaseModal from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal';
import { toFixed, isEqualByProp, formatDateTime, downloadZipFromObjectPath } from '@/utils'; import { toFixed, isEqualByProp, formatDateTime, downloadZipFromObjectPath } from '@/utils';
@@ -586,6 +588,10 @@ export default {
return String(state.dataset.activePanel); return String(state.dataset.activePanel);
}, },
}), }),
// 文件上传前携带尺寸信息
withDimensionFile() {
return withDimensionFile;
},
// 自定义上传数据集 // 自定义上传数据集
isImport() { isImport() {
return isImport; return isImport;
@@ -1132,41 +1138,14 @@ export default {
} }
}, },


// 获取文件信息
async checkImg (file){
const fileUrl = getFullFileUrl(file);
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve({
width: img.width,
height: img.height,
...file,
});
img.onerror = (err) => reject(err);

img.src = fileUrl;
});
},

// 上传文件之前加一层转换
async getTransformFiles(files) {
return Promise.all(files.map(file => this.checkImg(file)));
},

// 将文件上传和视频上传统一 // 将文件上传和视频上传统一
async uploader(datasetId, files, options = {}) {
async uploader(datasetId, files) {
const datasetInfo = await this.queryDatasetDetail(datasetId); const datasetInfo = await this.queryDatasetDetail(datasetId);
const { transformFile } = options;
// 点击导入操作 // 点击导入操作
const { dataType } = datasetInfo || {}; const { dataType } = datasetInfo || {};
// 文件上传 // 文件上传
if (dataType === 0) { if (dataType === 0) {
let _files = files.slice();
// 对文件进行转换,生成宽、高信息
if (typeof transformFile === 'function') {
_files = await transformFile(files);
}
return submit(datasetId, _files);
return submit(datasetId, files);
} if (dataType === 1) { } if (dataType === 1) {
// 根据是否通过点击导入按钮来区分 frameInterval 来源 // 根据是否通过点击导入按钮来区分 frameInterval 来源
const frameInterval = this.importRow const frameInterval = this.importRow
@@ -1194,9 +1173,7 @@ export default {
const successMessage = [0, 1].includes(this.chosenDatasetStatus) const successMessage = [0, 1].includes(this.chosenDatasetStatus)
? '上传文件成功' : '上传文件成功,若数据集状态未及时更新,请手动刷新页面'; ? '上传文件成功' : '上传文件成功,若数据集状态未及时更新,请手动刷新页面';
if (files.length > 0) { if (files.length > 0) {
this.uploader(this.chosenDatasetId, files, {
transformFile: withDimensionFiles,
}).then(() => {
this.uploader(this.chosenDatasetId, files).then(() => {
this.$message({ this.$message({
message: successMessage, message: successMessage,
duration: 5000, duration: 5000,
@@ -1244,9 +1221,11 @@ export default {
return toFixed(allFinished / (allFinished + progress.unfinished), 2, 0); return toFixed(allFinished / (allFinished + progress.unfinished), 2, 0);
}, },
parseDataType(row, column, cellValue = 0) { parseDataType(row, column, cellValue = 0) {
if(row.import) return "自定义";
return dataTypeMap[cellValue]; return dataTypeMap[cellValue];
}, },
parseAnnotateType(row, column, cellValue) { parseAnnotateType(row, column, cellValue) {
if(row.import) return "自定义";
return (annotationMap[cellValue] || {}).name || ''; return (annotationMap[cellValue] || {}).name || '';
}, },
parseStatus(row, column, cellValue = 0) { parseStatus(row, column, cellValue = 0) {


+ 26
- 0
webapp/src/views/dataset/util.js View File

@@ -74,9 +74,33 @@ export const stringifyAnnotations = (annotations) => {
const buildImgUrl = (list = []) => { const buildImgUrl = (list = []) => {
return list.map(d => ({ return list.map(d => ({
url: `${bucketName}/${d.data.objectName}`, url: `${bucketName}/${d.data.objectName}`,
...(d.data.meta || {}), // 附加的信息,目前只包括 width, height
})); }));
}; };


// 对文件进行自定义转换
export const withDimensionFile = (result, file) => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
const img = new Image();
img.onload = () => resolve({
...result,
data: {
...result.data,
meta: {
width: img.width,
height: img.height,
},
},
});
img.src = reader.result;
}, false);

reader.readAsDataURL(file.raw);
});
};

export const getImgFromMinIO = (res) => { export const getImgFromMinIO = (res) => {
return buildImgUrl(res); return buildImgUrl(res);
}; };
@@ -110,6 +134,7 @@ export const transformFile = (rawFile, callback) => {
return res; return res;
}; };


// deprecated
// 获取文件信息 // 获取文件信息
async function checkImg (file){ async function checkImg (file){
const fileUrl = getFullFileUrl(file); const fileUrl = getFullFileUrl(file);
@@ -126,6 +151,7 @@ async function checkImg (file){
}); });
} }


// deprecated
// 上传文件之前加一层转换 // 上传文件之前加一层转换
export const withDimensionFiles = async(files) => { export const withDimensionFiles = async(files) => {
return Promise.all(files.map(file => checkImg(file))); return Promise.all(files.map(file => checkImg(file)));


Loading…
Cancel
Save