update comments

This commit is contained in:
Calvin 2023-03-22 11:01:16 +08:00
parent cc6257a807
commit 9f8814b3ce
192 changed files with 661 additions and 8068 deletions

View File

@ -145,8 +145,9 @@ public class AudioServiceImpl implements AudioService {
byte[] bytes = FileUtil.getByte(file);
String uuid = UUIDUtil.getUUID();
audioInfo.setUuid(uuid);
// 待存储的文件名
// file name to be stored
String fileName = uuid + "." + ext; // 待存储的文件名
String fileName = uuid + "." + ext;
String relativePath = FileUtil.generatePath(rootPath);
// filePath 完整路径含uuid文件名
// filePath complete path (including uuid filename)

View File

@ -402,7 +402,7 @@ public class SearchServiceImpl implements SearchService {
}
// 删除向量
// Search Vector
// Delete Vector
// String deleteExpr = ID_FIELD + " in " + deleteIds.toString();
public R<MutationResult> delete(String expr) {
System.out.println("========== delete() ==========");

View File

@ -44,7 +44,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/imagesearch/index'),
name: 'storage',
meta: { title: '人脸搜索', icon: 'el-icon-search' }
meta: { title: 'Face Search', icon: 'el-icon-search' }
}
]
},
@ -56,7 +56,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/storage/index'),
name: 'storage',
meta: { title: '数据管理', icon: 'el-icon-upload' }
meta: { title: 'Data Management', icon: 'el-icon-upload' }
}
]
},

View File

@ -13,7 +13,7 @@
</div>
</el-image>
<div class="score">距离 {{ data.score }}</div>
<div class="score">Dis {{ data.score }}</div>
<div class="footer">
<div class="title">{{ data.id }}</div>
<!-- <div class="date-time">{{ data.createTime }}</div>-->

View File

@ -2,7 +2,7 @@
<div class="person-image-list">
<div class="head">
<div class="bts">
TopK<el-input v-model="topK" type="primary" style="margin:0 5px;width: 100px;" placeholder="输入TopK" />
TopK<el-input v-model="topK" type="primary" style="margin:0 5px;width: 100px;" placeholder="TopK" />
<el-upload
ref="imageUploader"
name="image"
@ -21,7 +21,7 @@
>
<el-input
style="margin-right: 5px;"
placeholder="请上传图片或拖入图片"
placeholder="Please upload an image or drag in an image."
>
<i slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
@ -45,8 +45,8 @@
type="primary"
:disabled="!imgUrl"
@click="doFaceQuery"
>查询</el-button>
<el-button size="medium" type="primary" @click="restQuery">重置</el-button>
>Query</el-button>
<el-button size="medium" type="primary" @click="restQuery">Reset</el-button>
</div>
</div>
@ -70,9 +70,9 @@
</template>
<empty-data
v-else-if="!imgFile || !imgFile.size"
title="您还未上传图片,请拖入图片或点击上传"
title="You have not uploaded an image yet. Please drag an image or click to upload."
/>
<empty-data v-else title="未查询到信息" />
<empty-data v-else title="No information found" />
</div>
</template>
@ -159,7 +159,7 @@ export default {
},
handleFileChange(file, fileList) {
if (fileList[0] && fileList[0].size > 2097152) {
this.$message.warning('请上传小于2M大小的图片')
this.$message.warning('Please upload an image smaller than 2MB in size.')
this.$refs.imageUploader.clearFiles()
return false
}
@ -168,7 +168,7 @@ export default {
},
handleUploadBefore(file) {
if (file.size > 2097152) {
this.$message.warning('请上传小于2M大小的图片')
this.$message.warning('Please upload an image smaller than 2MB in size.')
return false
}
},

View File

@ -15,16 +15,16 @@
:show-file-list="false"
:auto-upload="false"
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button slot="trigger" size="small" type="primary">Select</el-button>
<el-button
v-loading.fullscreen.lock="fullscreenLoading"
style="margin-left: 10px;"
type="success"
size="small"
element-loading-text="拼命加载中"
element-loading-text="loading"
@click="submitUpload"
>上传</el-button>
<div slot="tip" class="el-upload__tip">文件类型: zip</div>
>Upload</el-button>
<div slot="tip" class="el-upload__tip">File Type: zip</div>
</el-upload>
</el-row>
@ -41,25 +41,25 @@
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="文件名" align="center">
<el-table-column label="Name" align="center">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="文件类型" align="center">
<el-table-column label="Type" align="center">
<template slot-scope="scope">
{{ scope.row.suffix }}
</template>
</el-table-column>
<el-table-column label="大小" align="center">
<el-table-column label="Size" align="center">
<template slot-scope="scope">
{{ scope.row.size }}
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<el-table-column label="Action" align="center">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="extract(scope.row)">特征提取</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除文件</el-button>
<el-button size="mini" type="primary" @click="extract(scope.row)">Feature</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">Delete</el-button>
</template>
</el-table-column>
</el-table>
@ -104,52 +104,43 @@ export default {
handleError(file) {
this.fullscreenLoading = false
},
// beforeUpload(file) {
// if (file.type !== 'application/zip') {
// this.fullscreenLoading = false
// this.$message.error('zip!')
// return false
// } else {
// return true
// }
// },
fetchData() {
this.listLoading = true
getStorageList().then(response => {
this.list = response.data
this.listLoading = false
}).catch(function(response) {
console.log(response)//
console.log(response)
})
},
handleDelete(row) {
this.$confirm('此操作将删除设置项,是否继续?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm('This operation will delete the setting, continue?', 'Deletion Confirmation', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
const id = row.id
del(id).then(response => {
this.$message({
type: 'success',
message: '删除成功!'
message: 'Deleted successfully!'
})
this.fetchData()
})
}).catch(() => {
console.log('取消成功')
console.log('Canceled successfully!')
})
},
extract(row) {
const id = row.id
this.$message({
type: 'success',
message: '已开始提取图片特征,请耐心等待。'
message: 'In process,please waiting...'
})
extract(id).then(response => {
this.$message({
type: 'success',
message: '特征提取成功'
message: 'successfully!'
})
})
}

View File

@ -6,9 +6,12 @@ package me.aias.common.constant;
**/
public class Constant {
// 上传压缩文件类型
// Upload compressed file type
public static String ZIP_FILE_TYPE = "zip";
// 判断window系统
// Determine Windows system
public static String PC_WINDOW_TYPE = "WINDOWS";
// 判断苹果系统
// Determine Apple system
public static String PC_APPLE_TYPE = "MACOSX";
}

View File

@ -2,17 +2,19 @@ package me.aias.common.constant;
/**
* 常用静态常量
* Common Constants
*
* @author Calvin
* @date 2021-12-12
**/
public class Constants {
/**
* win 系统
* win
*/
public static final String WIN = "win";
/**
* mac 系统
* mac
*/
public static final String MAC = "mac";
}

View File

@ -4,16 +4,17 @@ import lombok.Getter;
/**
* 状态枚举
* Status enumeration
*
* @author Calvin
* @date 2021-12-12
* @date 2021-06-20
**/
@Getter
public enum ResEnum {
SUCCESS("0000", "success"),
ZIP_FILE_FAIL("0001", "压缩包类型错误"),
DECOMPRESSION_FAIL("0002", "压缩包解压异常"),
SYSTEM_ERROR("1001", "内部系统错误");
ZIP_FILE_FAIL("0001", "Incorrect compression package type"),
DECOMPRESSION_FAIL("0002", "Exception occurred during decompression of the compression package"),
SYSTEM_ERROR("1001", "Internal system error");
public String KEY;
public String VALUE;

View File

@ -8,13 +8,18 @@ import java.util.List;
public class FaceUtil {
/** 返回外扩100%人脸 factor = 1, 100%, factor = 0.2, 20% */
/**
* 返回外扩100%人脸 factor = 1, 100%, factor = 0.2, 20%
* Returns 100% expansion of face, factor = 1, 100%, factor = 0.2, 20%
* */
public static Rectangle getSubImageRect(
BufferedImage image, Rectangle rectangle, int width, int height, float factor) {
// 左上角坐标
//Top left coordinate
int x1 = (int) (rectangle.getX() * width);
int y1 = (int) (rectangle.getY() * height);
// 宽度高度
//Width, height
int w = (int) (rectangle.getWidth() * width);
int h = (int) (rectangle.getHeight() * height);
// 左上角坐标
@ -24,6 +29,7 @@ public class FaceUtil {
// drawImageRect(image, x1, y1, w, h);
// 外扩大100%防止对齐后人脸出现黑边
//Expand by 100% to prevent black edges after alignment
int new_x1 = Math.max((int) (x1 + x1 * factor / 2 - x2 * factor / 2), 0);
int new_x2 = Math.min((int) (x2 + x2 * factor / 2 - x1 * factor / 2), width - 1);
int new_y1 = Math.max((int) (y1 + y1 * factor / 2 - y2 * factor / 2), 0);
@ -47,12 +53,15 @@ public class FaceUtil {
int x = (int) (subImageRect.getX());
int y = (int) (subImageRect.getY());
// 图中关键点坐标
// Expand by 60% of face
// 1. left_eye_x , left_eye_y
// 2. right_eye_x , right_eye_y
// 3. nose_x , nose_y
// 4. left_mouth_x , left_mouth_y
// 5. right_mouth_x , right_mouth_y
double[][] pointsArray = new double[5][2]; // 保存人脸关键点
double[][] pointsArray = new double[5][2];
// 保存人脸关键点
//Save facial key points
for (int i = 0; i < 5; i++) {
pointsArray[i][0] = points.get(i).getX() - x;
pointsArray[i][1] = points.get(i).getY() - y;

View File

@ -8,14 +8,14 @@ import java.util.Enumeration;
import java.util.Vector;
public class ConnectionPool {
private String host = ""; // Milvus 主机
private int port; // Milvus 端口号
private String host = ""; // Milvus 主机 - Milvus host
private int port; // Milvus 端口号 - Milvus port
private static volatile ConnectionPool uniqueInstance;
private int initialConnections = 10; // 连接池的初始大小
private int incrementalConnections = 5; // 连接池自动增加的大小
private int maxConnections = 50; // 连接池最大的大小
private int initialConnections = 10; // 连接池的初始大小 - initial size of the connection pool
private int incrementalConnections = 5; // 连接池自动增加的大小 - automatic increase in connection pool size
private int maxConnections = 50; // 连接池最大的大小 - maximum size of the connection pool
private Vector connections = null; // 存放连接池中连接的向量, 存放的对象为 PooledConnection
// vector that stores the connections in the connection pool, objects stored are of type PooledConnection
private ConnectionPool(String host, int port) {
this.host = host;
this.port = port;
@ -40,31 +40,39 @@ public class ConnectionPool {
private void createPool() { // synchronized
if (connections != null) {
return; // 假如己经创建则返回
// return if the connection pool already exists
}
// 创建保存连接的向量 , 初始时有 0 个元素
// create a vector to store the connections, initially with 0 elements
connections = new Vector();
// 根据 initialConnections 中设置的值创建连接
// create connections based on the value set in initialConnections
createConnections(this.initialConnections);
System.out.println(" Milvus连接池创建成功");
System.out.println("Milvus connection pool created successfully!");
}
private void createConnections(int numConnections) {
// 循环创建指定数目的数据库连接
// loop to create the specified number of database connections
for (int x = 0; x < numConnections; x++) {
// 是否连接池中的Milvus连接数量己经达到最大最大值由类成员 maxConnections
// if the number of Milvus connections in the connection pool has reached the maximum, break the loop. The maximum value is set by the class member maxConnections.
if (this.maxConnections > 0 && this.connections.size() >= this.maxConnections) {
break;
}
// 增加一个连接到连接池中Vector connections
// add a connection to the connection pool (Vector connections)
connections.addElement(new PooledConnection(newConnection()));
System.out.println(" Milvus连接己创建 ......");
System.out.println("Milvus connection created......");
}
}
private MilvusClient newConnection() {
// 创建一个 Milvus 客户端
// create a Milvus client
ConnectParam connectParam = ConnectParam.newBuilder()
.withHost(host)
.withPort(port)
@ -72,37 +80,49 @@ public class ConnectionPool {
MilvusServiceClient milvusClient = new MilvusServiceClient(connectParam);
// 返回创建的新的Milvus连接
// return the newly created Milvus connection
return milvusClient;
}
public synchronized MilvusClient getConnection() {
// 确保连接池己被创建
// ensure that the connection pool has been created
if (connections == null) {
return null; // 连接池还没创建则返回 null
// if the connection pool has not been created, return null
}
MilvusClient client = getFreeConnection(); // 获得一个可用的数据库连接
// obtain a available database connection
// 假如目前没有可以使用的连接即所有的连接都在使用中
// if there are no available connections, i.e., all connections are in use
while (client == null) {
// 等一会再试 250 ms
// wait for a while and try again in 250 ms
wait(250);
client = getFreeConnection(); // 重新再试直到获得可用的连接假如
// getFreeConnection() 返回的为 null
// 则表明创建一批连接后也不可获得可用连接
// try again until an available connection is obtained, if getFreeConnection() returns null, it means that even after creating a batch of connections, no available connections can be obtained.
}
return client; // 返回获得的可用的连接
// return the available connection obtained
}
private MilvusClient getFreeConnection() {
// 从连接池中获得一个可用的Milvus连接
// obtain an available Milvus connection from the connection pool
MilvusClient client = findFreeConnection();
if (client == null) {
// 假如目前连接池中没有可用的连接
// 创建一些连接
// if there are no available connections in the connection pool, create some connections
createConnections(incrementalConnections);
// 重新从池中查找是否有可用连接
// search again in the pool for available connections
client = findFreeConnection();
if (client == null) {
// 假如创建连接后仍获得不到可用的连接则返回 null
// if no available connections are obtained after creating new connections, return null
return null;
}
}
@ -113,18 +133,23 @@ public class ConnectionPool {
MilvusClient client = null;
PooledConnection pConn = null;
// 获得连接池中所有的对象
// obtain all objects in the connection pool
Enumeration enumerate = connections.elements();
// 遍历所有的对象看是否有可用的连接
// traverse all the objects to see if there are any available connections
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
if (!pConn.isBusy()) {
// 假如此对象不忙则获得它的数据库连接并把它设为忙
// if this object is not busy, obtain its database connection and set it as busy
client = pConn.getConnection();
pConn.setBusy(true);
break; // 己经找到一个可用的连接退出
// an available connection has been found, exit
}
}
return client; // 返回找到到的可用连接
// return the found available connection
}
public void returnConnection(MilvusClient client) {
@ -136,11 +161,14 @@ public class ConnectionPool {
PooledConnection pConn = null;
Enumeration enumerate = connections.elements();
// 遍历连接池中的所有连接找到这个要返回的连接对象
// ensure that the connection pool exists, if the connection has not been created (does not exist), return directly
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
// 先找到连接池中的要返回的连接对象
// traverse all connections in the connection pool to find the connection object to be returned
if (client == pConn.getConnection()) {
// 找到了 , 设置此连接为空闲状态
// if found, set the connection to idle status
pConn.setBusy(false);
break;
}
@ -149,20 +177,25 @@ public class ConnectionPool {
public synchronized void refreshConnections() {
// 确保连接池己创新存在
// ensure that the connection pool has been created
if (connections == null) {
System.out.println(" 连接池不存在,无法刷新 !");
System.out.println("The connection pool does not exist, cannot refresh!");
return;
}
PooledConnection pConn = null;
Enumeration enumerate = connections.elements();
while (enumerate.hasMoreElements()) {
// 获得一个连接对象
// obtain a connection object
pConn = (PooledConnection) enumerate.nextElement();
// 假如对象忙则等 5 ,5 秒后直接刷新
// if the object is busy, wait for 5 seconds and then refresh it directly
if (pConn.isBusy()) {
wait(5000); // 5
}
// 关闭此连接用一个新的连接代替它
// close this connection and replace it with a new one.
closeConnection(pConn.getConnection());
pConn.setConnection(newConnection());
pConn.setBusy(false);
@ -171,8 +204,10 @@ public class ConnectionPool {
public synchronized void closeConnectionPool() {
// 确保连接池存在假如不存在返回
// ensure that the connection pool exists, if it does not exist, return
if (connections == null) {
System.out.println(" 连接池不存在,无法关闭 !");
System.out.println("The connection pool does not exist, cannot close!");
return;
}
PooledConnection pConn = null;
@ -180,15 +215,19 @@ public class ConnectionPool {
while (enumerate.hasMoreElements()) {
pConn = (PooledConnection) enumerate.nextElement();
// 假如忙 5
// if busy, wait for 5 seconds
if (pConn.isBusy()) {
wait(5000); // 5
}
// 5 秒后直接关闭它
// close it directly after 5 seconds
closeConnection(pConn.getConnection());
// 从连接池向量中删除它
// remove it from the connection pool vector
connections.removeElement(pConn);
}
// 置连接池为空
// set the connection pool to null
connections = null;
}
@ -205,29 +244,35 @@ public class ConnectionPool {
class PooledConnection {
MilvusClient client = null; // Milvus连接
// Milvus connection
boolean busy = false; // 此连接是否正在使用的标志默认没有正在使用
// flag indicating whether this connection is currently in use, default is not in use
// 构造函数根据一个 Connection 构告一个 PooledConnection 对象
// constructor that constructs a PooledConnection object based on a Connection
public PooledConnection(MilvusClient client) {
this.client = client;
}
// 返回此对象中的连接
// return the connection in this object
public MilvusClient getConnection() {
return client;
}
// 设置此对象的连接
// set the connection in this object
public void setConnection(MilvusClient client) {
this.client = client;
}
// 获得对象连接是否忙
// obtain the status of if the connection in the object is busy
public boolean isBusy() {
return busy;
}
// 设置对象的连接正在忙
// set the connection in the object as busy
public void setBusy(boolean busy) {
this.busy = busy;
}

View File

@ -13,6 +13,8 @@ import java.nio.file.Paths;
/**
* DJL图片类
* DJL Utility Class
*
* @author Calvin
* @date 2021-12-12
**/
@ -23,10 +25,9 @@ public class DJLImageUtil {
}
public static void saveImage(BufferedImage img, String name, String path) {
Image djlImg = ImageFactory.getInstance().fromImage(img); // 支持多种图片格式自动适配
Image djlImg = ImageFactory.getInstance().fromImage(img);
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
djlImg.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {
@ -37,7 +38,6 @@ public class DJLImageUtil {
public static void saveDJLImage(Image img, String name, String path) {
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
img.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {
@ -52,16 +52,13 @@ public class DJLImageUtil {
Path outputDir = Paths.get(path);
Files.createDirectories(outputDir);
Path imagePath = outputDir.resolve(name);
// OpenJDK can't save jpg with alpha channel
img.save(Files.newOutputStream(imagePath), "png");
}
public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(new Color(246, 96, 0));
// 声明画笔属性 单位像素末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);
@ -73,11 +70,9 @@ public class DJLImageUtil {
public static void drawImageRect(
BufferedImage image, int x, int y, int width, int height, Color c) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(c);
// 声明画笔属性 单位像素末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);

View File

@ -4,6 +4,8 @@ import java.text.SimpleDateFormat;
/**
* 日期类
* Date Utility Class
*
* @author Calvin
* @date 2021-12-12
**/

View File

@ -29,6 +29,7 @@ import java.util.Date;
/**
* File工具类扩展 hutool 工具包
* File tool class, extending hutool tool package
*
* @author Zheng Jie
* @date 2018-12-27
@ -39,10 +40,14 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 系统临时目录
* System temporary directory
* <br>
* windows 包含路径分割符但Linux 不包含,
* 在windows \\==\ 前提下
* 为安全起见 同意拼装 路径分割符
* - Windows contains path separators, but Linux does not,
* - under the premise of windows \\==\,
* - for safety, agree to assemble path separators,
* <pre>
* java.io.tmpdir
* windows : C:\Users/xxx\AppData\Local\Temp\
@ -52,30 +57,35 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
/**
* 定义GB的计算常量
* Define GB calculation constants
*/
private static final int GB = 1024 * 1024 * 1024;
/**
* 定义MB的计算常量
* Define MB calculation constants
*/
private static final int MB = 1024 * 1024;
/**
* 定义KB的计算常量
* Define KB calculation constants
*/
private static final int KB = 1024;
/**
* 格式化小数
* Format decimal
*/
private static final DecimalFormat DF = new DecimalFormat("0.00");
public static final String IMAGE = "图片";
public static final String TXT = "文档";
public static final String MUSIC = "音乐";
public static final String VIDEO = "视频";
public static final String OTHER = "其他";
public static final String IMAGE = "IMAGE";
public static final String TXT = "TXT";
public static final String MUSIC = "MUSIC";
public static final String VIDEO = "VIDEO";
public static final String OTHER = "OTHER";
/**
* 根据日期生成本地图片相对保存路径
* Generate local image relative storage path based on date
*/
public static String generatePath(String fileRoot) {
Date date = new Date();
@ -83,6 +93,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
String relativePath = DateUtil.YYYY_MM_dd.get().format(date);
String filePath = fileRoot + relativePath;
// 如果不存在,创建文件夹
// If it does not exist, create a folder
File f = new File(filePath);
if (!f.exists()) {
f.mkdirs();
@ -95,12 +106,15 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
*/
public static File toFile(MultipartFile multipartFile) {
// 获取文件名
// Get file name
String fileName = multipartFile.getOriginalFilename();
// 获取文件后缀
// Get file extension
String prefix = "." + getExtensionName(fileName);
File file = null;
try {
// 用uuid作为文件名防止生成的临时文件重复
// Use uuid as file name to prevent duplicate temporary files from being generated
file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);
// MultipartFile to File
multipartFile.transferTo(file);
@ -112,6 +126,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 获取文件扩展名不带 .
* Get file extension name without .
*/
public static String getExtensionName(String filename) {
if ((filename != null) && (filename.length() > 0)) {
@ -125,6 +140,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* Java文件操作 获取不带扩展名的文件名
* Java file operation, get the file name without extension
*/
public static String getFileNameNoEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
@ -138,17 +154,21 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 文件大小转换
* File size conversion
*/
public static String getSize(long size) {
String resultSize;
if (size / GB >= 1) {
//如果当前Byte的值大于等于1GB
// If the current Byte value is greater than or equal to 1GB
resultSize = DF.format(size / (float) GB) + "GB ";
} else if (size / MB >= 1) {
//如果当前Byte的值大于等于1MB
// If the current Byte value is greater than or equal to 1MB
resultSize = DF.format(size / (float) MB) + "MB ";
} else if (size / KB >= 1) {
//如果当前Byte的值大于等于1KB
// If the current Byte value is greater than or equal to 1KB
resultSize = DF.format(size / (float) KB) + "KB ";
} else {
resultSize = size + "B ";
@ -158,6 +178,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* inputStream File
* InputStream to File
*/
static File inputStreamToFile(InputStream ins, String name) throws Exception {
File file = new File(SYS_TEM_DIR + name);
@ -178,6 +199,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 将文件名解析成文件的上传路径
* Parse the file name into the upload path of the file
*/
public static File upload(MultipartFile file, String filePath) {
Date date = new Date();
@ -189,14 +211,17 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
String fileName = name + nowStr + "." + suffix;
String path = filePath + fileName;
// getCanonicalFile 可解析正确各种路径
// getCanonicalFile can correctly parse various paths
File dest = new File(path).getCanonicalFile();
// 检测是否存在目录
// Check if the directory exists
if (!dest.getParentFile().exists()) {
if (!dest.getParentFile().mkdirs()) {
System.out.println("was not successful.");
}
}
// 文件写入
// File write
file.transferTo(dest);
return dest;
} catch (Exception e) {
@ -227,12 +252,13 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
// 1M
int len = 1024 * 1024;
if (size > (maxSize * len)) {
throw new BadRequestException("文件超出规定大小");
throw new BadRequestException("max size limitted");
}
}
/**
* 判断两个文件是否相同
* Determine whether two files are the same
*/
public static boolean check(File file1, File file2) {
String img1Md5 = getMd5(file1);
@ -242,6 +268,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 判断两个文件是否相同
* Determine whether two files are the same
*/
public static boolean check(String file1Md5, String file2Md5) {
return file1Md5.equals(file2Md5);
@ -249,6 +276,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
public static byte[] getByte(File file) {
// 得到文件长度
// Get file length
byte[] b = new byte[(int) file.length()];
try {
InputStream in = new FileInputStream(file);
@ -266,15 +294,17 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 保存字节数组图片到指定path
* Save byte array picture to specified path
*/
public static void bytesToFile(byte[] bs, String filePath) throws IOException {
FileOutputStream os = new FileOutputStream(filePath);
os.write(bs);
os.close();
}
private static String getMd5(byte[] bytes) {
// 16进制字符
// 16 hexadecimal characters
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
@ -284,6 +314,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
char[] str = new char[j * 2];
int k = 0;
// 移位 输出字符串
// Move output string
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
@ -304,13 +335,18 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
boolean flag = false;
File file = new File(path);
// 判断目录或文件是否存在
// Check if the directory or file exists
if (!file.exists()) { // 不存在返回 false
//Does not exist returns false
return flag;
} else {
// 判断是否为文件
// If it is a file, call the delete file method
if (file.isFile()) { // 为文件时调用删除文件方法
// If it is a file, call the delete file method
return deleteFile(path);
} else { // 为目录时调用删除目录方法
// If it is a directory, call the delete directory method
return deleteDirectory(path);
}
}
@ -318,14 +354,16 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 删除单个文件
*Delete single file
*
* @param path 被删除文件的文件名
* @return 单个文件删除成功返回true否则返回false
* @param path 被删除文件的文件名 - path file name to be deleted
* @return 单个文件删除成功返回true否则返回false - Return true if the single file is deleted successfully, otherwise return false
*/
public static boolean deleteFile(String path) {
boolean flag = false;
File file = new File(path);
// 路径为文件且不为空则进行删除
// If the path is a file and is not empty, delete it
if (file.isFile() && file.exists()) {
file.delete();
flag = true;
@ -335,29 +373,36 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 删除目录文件夹以及目录下的文件
* Delete directory (file) and files under the directory
*
* @param path 被删除目录的文件路径
* @param path 被删除目录的文件路径 - path file path of the directory to be deleted
* @return 目录删除成功返回true否则返回false
* Return true if the directory is deleted successfully, otherwise return false
*/
public static boolean deleteDirectory(String path) {
// 如果sPath不以文件分隔符结尾自动添加文件分隔符
// If sPath does not end with a file separator, automatically add a file separator
if (!path.endsWith(File.separator)) {
path = path + File.separator;
}
File dirFile = new File(path);
// 如果dir对应的文件不存在或者不是一个目录则退出
// If dir does not exist or is not a directory, exit
if (!dirFile.exists() || !dirFile.isDirectory()) {
return false;
}
boolean flag = true;
// 删除文件夹下的所有文件(包括子目录)
// Delete all files in the folder (including subdirectories)
File[] files = dirFile.listFiles();
for (int i = 0; i < files.length; i++) {
// 删除子文件
// Delete sub files
if (files[i].isFile()) {
flag = deleteFile(files[i].getAbsolutePath());
if (!flag) break;
} // 删除子目录
// Delete subdirectories
else {
flag = deleteDirectory(files[i].getAbsolutePath());
if (!flag) break;
@ -365,6 +410,7 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
}
if (!flag) return false;
// 删除当前目录
// Delete the current directory
if (dirFile.delete()) {
return true;
} else {
@ -372,4 +418,4 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
}
}
}
}

View File

@ -16,6 +16,8 @@ import java.util.Iterator;
/**
* 图片操作类
* Image manipulation class
*
* @author Calvin
* @date 2021-12-12
**/
@ -25,6 +27,7 @@ public class ImageUtil {
/**
* multipartFile 编码转换为 BufferedImage
* Convert multipartFile encoding to BufferedImage
*/
public static BufferedImage multipartFileToBufImage(MultipartFile imageFile) {
try {
@ -40,6 +43,7 @@ public class ImageUtil {
/**
* MultipartFile对象转字节数组
* Convert MultipartFile object to byte array
*/
public static byte[] multipartFileToBytes(MultipartFile file) {
InputStream ins = null;
@ -64,6 +68,7 @@ public class ImageUtil {
/**
* file byte数组
* Convert file to byte array
*/
public static byte[] file2Byte(File file) throws IOException {
byte[] buffer = null;
@ -82,6 +87,7 @@ public class ImageUtil {
/**
* 根据地址获得数据的字节流
* Get byte stream of data according to address
*/
public static byte[] getImageByUrl(String strUrl) {
try {
@ -100,6 +106,7 @@ public class ImageUtil {
/**
* 从输入流中获取数据
* Get data from input stream
*/
public static byte[] readInputStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
@ -114,6 +121,7 @@ public class ImageUtil {
/**
* bytes 编码转换为 BufferedImage
* Convert bytes encoding to BufferedImage
*/
public static BufferedImage bytesToBufferedImage(byte[] bytes) {
try {
@ -127,6 +135,7 @@ public class ImageUtil {
/**
* 保存字节数组图片到指定path
* Save byte array image to specified path
*/
public static void bytesToImageFile(byte[] bs, String filePath) throws IOException {
FileOutputStream os = new FileOutputStream(filePath);
@ -136,6 +145,7 @@ public class ImageUtil {
/**
* 根据日期生成本地图片相对保存路径
* Generate local image relative save path based on date
*/
public static String generatePath(String fileRoot) {
Date date = new Date();
@ -152,6 +162,7 @@ public class ImageUtil {
/**
* 获得图片的后缀例如JPEGGIF等
* Get the suffix of the image, such as: JPEG, GIF, etc.
*/
public static String getImageFormat(Object obj) {
ImageInputStream iis = null;

View File

@ -6,9 +6,15 @@ import org.bytedeco.javacpp.indexer.DoubleRawIndexer;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Point2f;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class NDArrayUtil {
// NDArray opencv_core.Mat
// NDArray to opencv_core.Mat
public static Mat toOpenCVMat(NDArray points, int rows, int cols) {
double[] doubleArray = points.toDoubleArray();
// CV_32F = FloatRawIndexer
@ -27,6 +33,7 @@ public class NDArrayUtil {
}
// NDArray opencv_core.Mat
// NDArray to opencv_core.Mat
public static Mat toOpenCVMat(NDManager manager, NDArray srcPoints, NDArray dstPoints) {
NDArray svdMat = SVDUtil.transformationFromPoints(manager, srcPoints, dstPoints);
@ -46,6 +53,7 @@ public class NDArrayUtil {
}
// NDArray opencv_core.Point2f
// NDArray to opencv_core.Point2f
public static Point2f toOpenCVPoint2f(NDArray points, int rows) {
double[] doubleArray = points.toDoubleArray();
Point2f points2f = new Point2f(rows);
@ -58,6 +66,7 @@ public class NDArrayUtil {
}
// DoubleArray opencv_core.Point2f
// DoubleArray to opencv_core.Point2f
public static Point2f toOpenCVPoint2f(double[] doubleArray, int rows) {
Point2f points2f = new Point2f(rows);

View File

@ -5,12 +5,16 @@ import Jama.SingularValueDecomposition;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
/** 仿射变换处理工具 */
public class SVDUtil {
/**
* 仿射变换处理工具
* Affine transformation processing tool
*/
public class SVDUtils {
// 计算全局标准差
// Calculate global standard deviation
public static NDArray point112X96(NDManager manager) {
double[][] coord5point = {
{30.2946f, 51.6963f}, // 112x96的目标点
{30.2946f, 51.6963f}, // 112x96的目标点 - Target point of 112x96
{65.5318f, 51.6963f},
{48.0252f, 71.7366f},
{33.5493f, 92.3655f},
@ -22,7 +26,7 @@ public class SVDUtil {
public static NDArray point112x112(NDManager manager) {
double[][] coord5point = {
{30.2946f + 8.0000f, 51.6963f}, // 112x112的目标点
{30.2946f + 8.0000f, 51.6963f}, // 112x112的目标点 - Target point of 112x112
{65.5318f + 8.0000f, 51.6963f},
{48.0252f + 8.0000f, 71.7366f},
{33.5493f + 8.0000f, 92.3655f},
@ -33,20 +37,25 @@ public class SVDUtil {
}
// 计算仿射变换矩阵
// Calculate affine transformation matrix
public static NDArray transformationFromPoints(
NDManager manager, NDArray points1, NDArray points2) {
// 按列计算均值
NDArray c1 = points1.mean(new int[] {0}); // axis=0 列操作
NDArray c2 = points2.mean(new int[] {0}); // axis=0 列操作
// Calculate column-wise mean
NDArray c1 = points1.mean(new int[] {0}); // axis=0 列操作 - axis=0 column operation
NDArray c2 = points2.mean(new int[] {0}); // axis=0 列操作 - axis=0 column operation
// 按列减去均值
// Subtract column-wise mean
points1 = points1.sub(c1);
points2 = points2.sub(c2);
// 计算全局标准差
// Calculate global standard deviation
double s1 = std(points1);
double s2 = std(points2);
// 矩阵除以全局标准差
// Matrix divided by global standard deviation
NDArray djl_s1 = manager.create(s1);
NDArray djl_s2 = manager.create(s2);
points1 = points1.div(djl_s1);
@ -56,6 +65,7 @@ public class SVDUtil {
double[] points2D = points2.toDoubleArray();
// DJL 格式转换成Jamma格式
// Convert DJL format to Jama format
double[][] m1 = new double[5][2];
double[][] m2 = new double[5][2];
for (int i = 0; i < 5; i++) {
@ -72,6 +82,7 @@ public class SVDUtil {
Matrix p2 = new Matrix(m2);
// 进行奇异值分解
// Perform singular value decomposition
Matrix p3 = p1.transpose().times(p2);
SingularValueDecomposition s = p3.svd();
@ -79,6 +90,7 @@ public class SVDUtil {
Matrix S = s.getS();
Matrix V = s.getV();
// TODO 为什么第2列的符号是反的
// Why is the sign of the second column opposite?
m1 = U.getArray();
m1[0][1] = -m1[0][1];
m1[1][1] = -m1[1][1];
@ -92,6 +104,8 @@ public class SVDUtil {
NDArray newR = manager.create(rArray);
// np.vstack([np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), np.matrix([0.,0., 1.])])
// TODO NDArray 单行向量直接转置无效用reshape代替
// // TODO NDArray single-row vector transpose is invalid, use reshape instead
// (s2 / s1) * R
NDArray leftPart = djl_s2.div(djl_s1).mul(newR);
// c2.T - (s2 / s1) * R * c1.T)
@ -109,6 +123,7 @@ public class SVDUtil {
}
// 计算全局标准差
// Calculate global standard deviation
public static double std(NDArray points) {
points = points.square();
double[] doubleResult = points.toDoubleArray();

View File

@ -4,6 +4,7 @@ import java.util.UUID;
/**
* 获取UUID
* Get UUID
*
* @author Calvin
* @date 2021-12-12

View File

@ -9,6 +9,8 @@ import java.net.UnknownHostException;
/**
* 获取系统信息
* Get system info
*
* @author Calvin
* @date 2021-12-12
**/
@ -26,6 +28,8 @@ public class UserAgentUtil {
/**
* 获取操作系统
* Get os info
*
*/
public String getOS() {
if (null == userAgent) {

View File

@ -17,6 +17,7 @@ public class ZipUtil {
/**
* 解压Zip文件
* unzip file
*/
public static void unZip(String receivedZipFile, String osName, String filePath) {
int count = -1;

View File

@ -12,7 +12,12 @@ import java.nio.FloatBuffer;
import static org.bytedeco.opencv.global.opencv_calib3d.findHomography;
import static org.bytedeco.opencv.global.opencv_core.cvCreateMat;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class FaceAlignment {
public static Mat affineTransform(
@ -81,11 +86,13 @@ public class FaceAlignment {
}
// 根据目标5点进行旋转仿射变换
// Rotate and transform based on 5 target points
public static Mat get5WarpAffineImg(Mat src, Mat rot_mat) {
Mat oral = new Mat();
src.copyTo(oral);
Mat rot = new Mat();
// 进行仿射变换变换后大小为src的大小
// Perform affine transformation, and the size after transformation is the same as src
opencv_imgproc.warpAffine(src, rot, rot_mat, src.size());
return rot;
}

View File

@ -10,6 +10,7 @@ import java.awt.image.DataBufferByte;
/**
* 图片类型转换
* Image type conversion
*
* @author Calvin
*/
@ -17,6 +18,7 @@ public class OpenCVImageUtil {
/**
* BufferedImage Mat
* Convert BufferedImage to Mat
*
* @param original
*/
@ -27,6 +29,7 @@ public class OpenCVImageUtil {
/**
* 将mat转BufferedImage
* Convert Mat to BufferedImage
*
* @param matrix
*/
@ -59,12 +62,16 @@ public class OpenCVImageUtil {
// BufferedImage对象中最重要的两个组件为Raster和ColorModel分别用于存储图像的像素数据与颜色数据
// 表示像素矩形数组的类Raster 封装存储样本值的 DataBuffer以及描述如何在 DataBuffer 中定位给定样本值的 SampleModel
// 由于Raster对象是BufferedImage对象中的像素数据存储对象因此BufferedImage支持从Raster对象中获取任意位置xy点的像素值pxy
// The two most important components of the BufferedImage object are Raster and ColorModel, which are used to store pixel data and color data of the image, respectively.
// The Raster class represents an array of pixel rectangles. Raster encapsulates the DataBuffer that stores the sample values, and the SampleModel that describes how to locate a given sample value in the DataBuffer.
// Since the Raster object is the pixel data storage object in the BufferedImage object, BufferedImage supports getting the pixel value p(x,y) at any position (x,y) from the Raster object.
image.getRaster().setDataElements(0, 0, cols, rows, data);
return image;
}
/**
* 将bufferImage转Mat
* Convert bufferImage to Mat
*
* @param original
* @param matType

View File

@ -26,7 +26,10 @@ import java.util.List;
@EnableWebMvc
public class ConfigurerAdapter implements WebMvcConfigurer {
/** 文件配置 */
/**
* 文件配置
* File configuration.
* */
private final FileProperties properties;
public ConfigurerAdapter(FileProperties properties) {
@ -59,6 +62,7 @@ public class ConfigurerAdapter implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 使用 fastjson 序列化会导致 @JsonIgnore 失效可以使用 @JSONField(serialize = false) 替换
// Use fastjson serialization, which will cause @JsonIgnore to be invalid, can be replaced with @JSONField (serialize = false)
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportMediaTypeList = new ArrayList<>();
supportMediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);

View File

@ -7,6 +7,8 @@ import org.springframework.context.annotation.Configuration;
/**
* 文件操作常量类
* File operation constants class
*
* @author Calvin
* @date 2021-12-12
**/
@ -15,7 +17,8 @@ import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "file")
public class FileProperties {
/** 文件大小限制 */
// 文件大小限制
// File size limit
private Long maxSize;
private ElPath mac;

View File

@ -27,7 +27,7 @@ import static springfox.documentation.schema.AlternateTypeRules.newRule;
/**
* @author Calvin
* @date 2021-12-12
* @date 2021-06-20
**/
@Configuration
@EnableSwagger2
@ -52,7 +52,7 @@ public class SwaggerConfig {
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.description("")
.title("接口文档")
.title("API Doc")
.version("1.0")
.build();
}
@ -61,6 +61,7 @@ public class SwaggerConfig {
/**
* 将Pageable转换展示在swagger中
* Convert Pageable for display in Swagger
*/
@Configuration
class SwaggerDataConfig {
@ -83,13 +84,13 @@ class SwaggerDataConfig {
@ApiModel
@Data
private static class Page {
@ApiModelProperty("页码 (0..N)")
@ApiModelProperty("Page number (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
@ApiModelProperty("Number of items per page")
private Integer size;
@ApiModelProperty("以下列格式排序标准property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
@ApiModelProperty("Sort criteria in the following format: property[,asc|desc]. Default sort order is ascending. Multiple sort conditions are supported, such as id,asc")
private List<String> sort;
}
}

View File

@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 数据管理
* Data management
*
* @author Calvin
* @date 2021-12-12
@ -44,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequiredArgsConstructor
@Api(tags = "数据管理")
@Api(tags = "数据管理 -Data management")
@RequestMapping("/api/data")
public class DataController {
private final FileProperties properties;
@ -61,7 +62,7 @@ public class DataController {
@Autowired
private LocalStorageService localStorageService;
@ApiOperation(value = "提取特征")
@ApiOperation(value = "提取特征 - Extract feature values")
@GetMapping("/extractFeatures")
public ResponseEntity<Object> extractFeatures(@RequestParam(value = "id") String id, HttpServletRequest request) throws IOException {
LocalStorage localStorage = localStorageService.findById(Integer.parseInt(id));
@ -72,6 +73,7 @@ public class DataController {
}
// 获取上传者操作系统
// Get operating system of the uploader
UserAgentUtil userAgentGetter = new UserAgentUtil(request);
String os = userAgentGetter.getOS();
@ -79,6 +81,7 @@ public class DataController {
new File(properties.getPath().getRootPath()).mkdirs();
}
//生成UUID作为解压缩的目录
// Generate UUID as the directory to be extracted to
String UUID = UUIDUtil.getUUID();
String unZipFilePath = properties.getPath().getRootPath() + UUID;
if (!new File(unZipFilePath).exists()) {
@ -87,6 +90,7 @@ public class DataController {
ZipUtil.unZip(localStorage.getPath(), os, unZipFilePath);
//生成视频文件提取的图片帧目录
// Generate directory for image frames extracted from video files
String imagesPath = properties.getPath().getRootPath();
if (!new File(imagesPath).exists()) {
new File(imagesPath).mkdirs();
@ -99,6 +103,7 @@ public class DataController {
List<List<Float>> vectors = new ArrayList<>();
for (DataInfo dataInfo : dataInfoList) {
// 保存图片信息
// save image info
ConcurrentHashMap<String, String> map = dataService.getMap();
int size = map.size();
long imageId = size + 1;
@ -124,6 +129,7 @@ public class DataController {
}
// 将向量插入Milvus向量引擎
// Insert vectors into Milvus vector engine
try {
R<Boolean> response = searchService.hasCollection();
if (!response.getData()) {

View File

@ -20,26 +20,28 @@ import java.util.List;
/**
* 存储管理
* Storage Management
*
* @author Calvin
* @date 2021-12-12
**/
@RestController
@RequiredArgsConstructor
@Api(tags = "存储管理")
@Api(tags = "存储管理 - Storage Management")
@RequestMapping("/api/localStorage")
public class LocalStorageController {
private final LocalStorageService localStorageService;
private final FileProperties properties;
@ApiOperation("查询文件列表")
@ApiOperation("查询文件列表 -Query File List")
@GetMapping("/list")
public ResponseEntity<Object> getContact() {
List<LocalStorage> result = localStorageService.getStorageList();
return new ResponseEntity<>(ResultRes.success(result, result.size()), HttpStatus.OK);
}
@ApiOperation("上传文件")
@ApiOperation("上传文件 -Upload File")
@PostMapping("/file")
public ResponseEntity<Object> create(@RequestParam("file") MultipartFile multipartFile) {
FileUtil.checkSize(properties.getMaxSize(), multipartFile.getSize());
@ -67,7 +69,7 @@ public class LocalStorageController {
return new ResponseEntity<>(ResultRes.success(), HttpStatus.OK);
}
@ApiOperation("删除文件")
@ApiOperation("删除文件 -Delete File")
@DeleteMapping
public ResponseEntity<Object> delete(@RequestBody LocalStorage localStorage) {
LocalStorage storage = localStorageService.findById(localStorage.getId());

View File

@ -34,12 +34,13 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 搜索管理
* Search management
*
* @author Calvin
* @date 2021-12-19
**/
@Slf4j
@Api(tags = "搜索管理")
@Api(tags = "搜索管理 -Search management")
@RequestMapping("/api/search")
@RequiredArgsConstructor
@RestController
@ -55,7 +56,7 @@ public class SearchController {
String baseUrl;
@PostMapping(value = "/image")
@ApiOperation(value = "搜索图片", nickname = "searchImage")
@ApiOperation(value = "搜索图片 - image search", nickname = "searchImage")
public ResponseEntity<Object> searchImage(@RequestParam("image") MultipartFile imageFile, @RequestParam(value = "topK") String topk) {
BufferedImage bufferedImage = ImageUtil.multipartFileToBufImage(imageFile);
Image img = ImageFactory.getInstance().fromImage(bufferedImage);
@ -63,8 +64,10 @@ public class SearchController {
List<Float> vectorToSearch;
try {
//人脸检测 & 特征提取
// Face detection & feature extraction
List<FaceObject> faceObjects = detectService.faceDetect(imageFile.getName(), img);
//如何有多个人脸取第一个也可以选最大的或者一起送入搜索引擎搜索
// If there are multiple faces, take the first one (or the largest, or search together with the search engine)
vectorToSearch = faceObjects.get(0).getFeature();
} catch (Exception e) {
e.printStackTrace();
@ -77,11 +80,13 @@ public class SearchController {
try {
// 根据图片向量搜索
// Search for vectors based on image files
R<SearchResults> searchResponse = searchService.search(topK, vectorsToSearch);
SearchResultsWrapper wrapper = new SearchResultsWrapper(searchResponse.getData().getResults());
List<SearchResultsWrapper.IDScore> scores = wrapper.getIDScore(0);
// 根据ID获取图片信息
// Get image information based on ID
ConcurrentHashMap<String, String> map = imageService.getMap();
List<DataInfoRes> imageInfoResList = new ArrayList<>();
for (SearchResultsWrapper.IDScore score : scores) {

View File

@ -8,9 +8,11 @@ import java.util.Date;
/**
* 数据信息对象
* Data Info Object
*
*/
@Data
@ApiModel(value = "DataInfo", description = "数据信息对象")
@ApiModel(value = "DataInfo", description = "Data Info Object")
public class DataInfo {
@ApiModelProperty(value = "id", name = "id")
private Long id;
@ -21,15 +23,15 @@ public class DataInfo {
@ApiModelProperty(value = "uuid", name = "uuid")
private String uuid;
@ApiModelProperty(value = "原名称", name = "preName")
@ApiModelProperty(value = "preName", name = "preName")
private String preName;
@ApiModelProperty(value = "全路径", name = "fullPath")
@ApiModelProperty(value = "fullPath", name = "fullPath")
private String fullPath;
@ApiModelProperty(value = "相对路径", name = "relativePath")
@ApiModelProperty(value = "relativePath", name = "relativePath")
private String relativePath;
@ApiModelProperty(value = "创建时间", name = "createTime")
@ApiModelProperty(value = "createTime", name = "createTime")
private Date createTime;
}

View File

@ -8,9 +8,11 @@ import java.util.Date;
/**
* 信息返回对象
* Information Return Object
*
*/
@Data
@ApiModel(value = "DataInfoRes", description = "信息返回对象")
@ApiModel(value = "DataInfoRes", description = "信息返回对象 - Information Return Object")
public class DataInfoRes {
@ApiModelProperty(value = "id", name = "id")
private Long id;
@ -21,12 +23,12 @@ public class DataInfoRes {
@ApiModelProperty(value = "uuid", name = "uuid")
private String uuid;
@ApiModelProperty(value = "原名称", name = "preName")
@ApiModelProperty(value = "preName", name = "preName")
private String preName;
@ApiModelProperty(value = "url", name = "url")
private String url;
@ApiModelProperty(value = "创建时间", name = "createTime")
@ApiModelProperty(value = "createTime", name = "createTime")
private Date createTime;
}

View File

@ -21,22 +21,22 @@ public class LocalStorage implements Serializable {
@ApiModelProperty(value = "ID")
private int id;
@ApiModelProperty(value = "真实文件名")
@ApiModelProperty(value = "realName")
private String realName;
@ApiModelProperty(value = "文件名")
@ApiModelProperty(value = "name")
private String name;
@ApiModelProperty(value = "后缀")
@ApiModelProperty(value = "suffix")
private String suffix;
@ApiModelProperty(value = "路径")
@ApiModelProperty(value = "path")
private String path;
@ApiModelProperty(value = "类型")
@ApiModelProperty(value = "type")
private String type;
@ApiModelProperty(value = "大小")
@ApiModelProperty(value = "size")
private String size;
public LocalStorage(String realName, String name, String suffix, String path, String type, String size) {

View File

@ -4,17 +4,19 @@ import lombok.Getter;
/**
* 状态枚举
* Status enumeration
*
*/
@Getter
public enum ResEnum {
SUCCESS("0000", "success"),
PACKAGE_FILE_FAIL("0001", "压缩包类型错误"),
PACKAGE_DECOMPRESSION_FAIL("0002", "压缩包解压异常"),
UPLOAD_FAIL("0003", "上传压缩包失败!"),
IMAGE_NOT_FOUND("0004", "上传压缩包未找到照片!"),
MODEL_ERROR("0005", "模型推理出错"),
MILVUS_CONNECTION_ERROR("0006", "向量引擎连接错误"),
SYSTEM_ERROR("1001", "内部系统错误");
PACKAGE_FILE_FAIL("0001", "Wrong compressed file type"),
PACKAGE_DECOMPRESSION_FAIL("0002", "Exception occurred while decompressing the package"),
UPLOAD_FAIL("0003", "Failed to upload compressed file!"),
IMAGE_NOT_FOUND("0004", "Failed to find photo in the uploaded compressed file!"),
MODEL_ERROR("0005", "Error occurred while inferring the model"),
MILVUS_CONNECTION_ERROR("0006", "Vector engine connection error"),
SYSTEM_ERROR("1001", "Internal system error");
public String KEY;
public String VALUE;

View File

@ -10,15 +10,15 @@ import me.aias.common.enums.ResEnum;
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(value = "公共输出对象", description = "公共输出对象")
@ApiModel(value = "Public Output Object", description = "Public Output Object")
public class ResultRes<T> {
@ApiModelProperty(value = "输出编码,如0000", name = "code", example = "0000")
@ApiModelProperty(value = "Output code0000", name = "code", example = "0000")
private String code;
@ApiModelProperty(value = "输出消息(String)", name = "msg", example = "操作成功")
@ApiModelProperty(value = "Output message(String)", name = "msg", example = "Operation succeeded")
private String msg;
@ApiModelProperty(value = "输出对象(Object)", name = "data")
@ApiModelProperty(value = "Output object(Object)", name = "data")
private int total;
@ApiModelProperty(value = "输出对象数量", name = "total")
@ApiModelProperty(value = "Number of output objects", name = "total")
private T data;
public ResultRes(String code, String msg) {
@ -28,8 +28,10 @@ public class ResultRes<T> {
/**
* 系统默认返回内置错误编码
* System default return built-in error code
*
* code : 9999
* msg : 系统繁忙
* msg : 系统繁忙 - System busy
*
* @return BaseRes
*/
@ -42,10 +44,12 @@ public class ResultRes<T> {
/**
* 系统默认返回内置错误编码
* code : 9999
* msg : 系统繁忙
* System default return built-in error code
*
* @param data 返回消息数据
* code : 9999
* msg : 系统繁忙 - System busy
*
* @param data
* @return BaseRes
*/
public static ResultRes error(String data) {
@ -57,6 +61,7 @@ public class ResultRes<T> {
/**
* 返回错误消息编码不包含消息体
* Return error message code, does not include message body
*
* @param code
* @param msg
@ -71,6 +76,7 @@ public class ResultRes<T> {
/**
* 返回错误消息编码包含消息体
* Return error message code, including message body
*
* @param code 编码
* @param msg 消息
@ -87,6 +93,7 @@ public class ResultRes<T> {
/**
* 返回正确消息体
* Return correct message body
*
* @param data
* @param <T>
@ -98,6 +105,7 @@ public class ResultRes<T> {
/**
* 返回正确消息体
*Return correct message body
*
* @param data
* @param <T>
@ -109,6 +117,7 @@ public class ResultRes<T> {
/**
* 返回正确消息
* Return correct message
*
* @param <T>
* @return
@ -119,6 +128,7 @@ public class ResultRes<T> {
/**
* 判断是否执行正确
* Determine whether to execute correctly
*
* @return
*/

View File

@ -9,6 +9,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 服务接口
* Service Interface
*
* @author Calvin
* @date 2021-12-12
@ -16,21 +17,25 @@ import java.util.concurrent.ConcurrentHashMap;
public interface DataService {
/**
* 根据ID查询图片路径
* Query by ID
*/
String findById(String id);
/**
* 添加图片
* Add file
*/
void addData(String id, String audioPath);
/**
* 获取清单
* Get file list
*/
ConcurrentHashMap<String, String> getMap();
/**
* 上传音频文件
* 上传文件
* upload file
*/
public List<DataInfo> uploadData(String rootPath, String UUID)
throws BusinessException, IOException;

View File

@ -11,6 +11,7 @@ import java.util.List;
/**
* 目标检测服务接口
* Object detection service
*
* @author Calvin
* @date 2021-12-12

View File

@ -9,6 +9,7 @@ import java.util.List;
/**
* 特征提取服务接口
* Feature Service
*
* @author Calvin
* @date 2021-12-12

View File

@ -15,21 +15,25 @@ import java.util.List;
public interface LocalStorageService {
/**
* 保存上传文件列表
* save file list
*/
void saveStorageList();
/**
* 新增文件
* add file
*/
void addStorageFile(LocalStorage localStorage);
/**
* 根据ID查询
* get file by id
*/
LocalStorage findById(int id);
/**
* 删除
* delete
*/
boolean delete(int id);

View File

@ -12,60 +12,79 @@ import java.util.List;
public interface SearchService {
// 重置向量引擎
// Reset vector engine
void clearSearchEngine();
// 初始化向量引擎
// Initialize vector engine
void initSearchEngine();
// 获取连接池
// Get connection pool
ConnectionPool getConnectionPool(boolean refresh);
// 获取Milvus Client
// Get Milvus Client
MilvusClient getClient(ConnectionPool connPool);
// 检查是否存在 collection
// Return connection
void returnConnection(ConnectionPool connPool, MilvusClient client);
// 检查是否存在 collection
// Check if collection exists
R<Boolean> hasCollection(MilvusClient milvusClient);
R<Boolean> hasCollection();
// 创建 collection
// Create collection
R<RpcStatus> createCollection(MilvusClient milvusClient, long timeoutMiliseconds);
// 加载 collection
// Load collection
R<RpcStatus> loadCollection(MilvusClient milvusClient);
// 释放 collection
// Release collection
R<RpcStatus> releaseCollection(MilvusClient milvusClient);
// 删除 collection
// Drop collection
R<RpcStatus> dropCollection(MilvusClient milvusClient);
// 创建 分区
// Create partition
R<RpcStatus> createPartition(MilvusClient milvusClient, String partitionName);
// 删除 分区
// Drop partition
R<RpcStatus> dropPartition(MilvusClient milvusClient, String partitionName);
// 是否存在分区
// Check if partition exists
R<Boolean> hasPartition(MilvusClient milvusClient, String partitionName);
// 创建 index
// Create index
R<RpcStatus> createIndex(MilvusClient client);
// 删除 index
// Drop index
R<RpcStatus> dropIndex(MilvusClient client);
// 插入向量
// Insert vectors
R<MutationResult> insert(List<Long> vectorIds, List<List<Float>> vectors);
// 查询向量
// Query vectors
R<QueryResults> query(String expr);
// 搜索向量
// Search vectors
R<SearchResults> search(Integer topK, List<List<Float>> vectorsToSearch);
// 删除向量
// Delete vectors
R<MutationResult> delete(String expr);
}

View File

@ -62,6 +62,7 @@ public class DataServiceImpl implements DataService {
/**
* 新增文件
* Add file
*/
public void addData(String id, String audioPath) {
map.put(id, audioPath);
@ -70,6 +71,7 @@ public class DataServiceImpl implements DataService {
/**
* 根据ID查询
* Query by ID
*/
public String findById(String id) {
return map.get(id);
@ -77,6 +79,7 @@ public class DataServiceImpl implements DataService {
/**
* 获取清单
* Get file list
*/
public ConcurrentHashMap<String, String> getMap() {
return map;
@ -84,6 +87,7 @@ public class DataServiceImpl implements DataService {
/**
* 保存上传文件列表
* Save uploaded file list
*/
private void saveAudioList() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
@ -103,14 +107,19 @@ public class DataServiceImpl implements DataService {
if (!new File(rootPath).exists()) {
new File(rootPath).mkdirs();
}
String unZipFilePath = rootPath + UUID; // 以压缩文件名为新目录
String unZipFilePath = rootPath + UUID;
// 以压缩文件名为新目录
// the new directory name is the same as the compressed file name
try {
// 记录文件集合
// record the file collection
List<DataInfo> resultList = new ArrayList<>();
// 解压缩后的文件
// decompressed files
File[] listFiles = new File(unZipFilePath).listFiles();
// 判断上传文件是否包含音频文件
// 判断上传文件是否包含图片文件
// determine whether the uploaded file contains image files
boolean found = false;
for (File file : listFiles) {
String suffix = FileUtil.getExtensionName(file.getName());
@ -123,10 +132,12 @@ public class DataServiceImpl implements DataService {
}
if (!found) {
// 通用异常
// general exception
throw new BadRequestException(ResEnum.IMAGE_NOT_FOUND.VALUE);
}
// 保存文件到可访问路径
// save file to accessible path
for (File file : listFiles) {
DataInfo dataInfo = new DataInfo();
dataInfo.setPreName(file.getName());
@ -138,13 +149,17 @@ public class DataServiceImpl implements DataService {
byte[] bytes = FileUtil.getByte(file);
String uuid = UUIDUtil.getUUID();
dataInfo.setUuid(uuid);
String fileName = uuid + "." + suffix; // 待存储的文件名
// 待存储的文件名
// file name to be stored
String fileName = uuid + "." + suffix;
String relativePath = FileUtil.generatePath(rootPath);
// filePath 完整路径含uuid文件名
// filePath complete path (including uuid filename)
String filePath = rootPath + relativePath + fileName;
dataInfo.setFullPath(filePath);
dataInfo.setRelativePath(relativePath + fileName);
// 转成文件保存
// convert to file and save
FileUtil.bytesToFile(bytes, filePath);
resultList.add(dataInfo);
}
@ -152,6 +167,7 @@ public class DataServiceImpl implements DataService {
return resultList;
} finally {
// unZipFilePath = rootPath + uuid; // 以压缩文件名为新目录
// unZipFilePath = rootPath + uuid; // the new directory name is the same as the compressed file name
FileUtil.delete(unZipFilePath);
}

View File

@ -39,6 +39,7 @@ import java.util.List;
/**
* 目标检测服务
* Object detection service
*
* @author Calvin
* @date 2021-12-12
@ -73,6 +74,7 @@ public class DetectServiceImpl implements DetectService {
Rectangle rectangle = box.getBounds();
// 抠人脸图
// extract face image
Rectangle subImageRect =
FaceUtil.getSubImageRect(
image, rectangle, djlImg.getWidth(), djlImg.getHeight(), 0f);
@ -83,6 +85,7 @@ public class DetectServiceImpl implements DetectService {
BufferedImage subImage = image.getSubimage(x, y, w, h);
Image img = DJLImageUtil.bufferedImage2DJLImage(subImage);
//获取特征向量
// get feature vector
List<Float> feature = featureService.faceFeature(img);
FaceObject faceObject = new FaceObject();
@ -110,6 +113,8 @@ public class DetectServiceImpl implements DetectService {
// 抠人脸图
// factor = 0.1f, 意思是扩大10%防止人脸仿射变换后人脸被部分截掉
// extract face image
// factor = 0.1f, meaning expand 10%, to prevent the face from being partially cut off after face affine transformation
Rectangle subImageRect =
FaceUtil.getSubImageRect(
image, rectangle, djlImg.getWidth(), djlImg.getHeight(), 1.0f);
@ -120,6 +125,7 @@ public class DetectServiceImpl implements DetectService {
BufferedImage subImage = image.getSubimage(x, y, w, h);
// debug使用保存抠出的人脸图
// for debug: save the extracted face image
if (save) {
String savePath = properties.getPath().getPath() + "detected/";
if (!new File(savePath).exists()) {
@ -129,10 +135,12 @@ public class DetectServiceImpl implements DetectService {
}
// 计算人脸关键点在子图中的新坐标
// calculate the new coordinates of the face keypoints in the sub-image
List<Point> points = (List<Point>) box.getPath();
double[][] pointsArray = FaceUtil.pointsArray(subImageRect, points);
// buffered image 图片格式
// convert to buffered image format
BufferedImage converted3BGRsImg =
new BufferedImage(
subImage.getWidth(), subImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
@ -145,17 +153,21 @@ public class DetectServiceImpl implements DetectService {
NDArray dstPoints = SVDUtil.point112x112(manager);
// 定制的5点仿射变换
// customized 5-point affine transformation
Mat svdMat = NDArrayUtil.toOpenCVMat(manager, srcPoints, dstPoints);
// 换仿射变换矩阵
// replace affine transformation matrix
mat = FaceAlignment.get5WarpAffineImg(mat, svdMat);
// mat转bufferedImage类型
// convert mat to buffered image type
BufferedImage mat2BufferedImage = OpenCVImageUtil.mat2BufferedImage(mat);
int width = mat2BufferedImage.getWidth() > 112 ? 112 : mat2BufferedImage.getWidth();
int height = mat2BufferedImage.getHeight() > 112 ? 112 : mat2BufferedImage.getHeight();
mat2BufferedImage = mat2BufferedImage.getSubimage(0, 0, width, height);
// debug使用保存对齐后的人脸图
// for debug: save the aligned face image
if (save) {
String savePath = properties.getPath().getPath() + "aligned/";
if (!new File(savePath).exists()) {
@ -167,6 +179,7 @@ public class DetectServiceImpl implements DetectService {
Image img = DJLImageUtil.bufferedImage2DJLImage(mat2BufferedImage);
//获取特征向量
// get feature vector
List<Float> feature = featureService.faceFeature(img);
FaceObject faceObject = new FaceObject();

View File

@ -15,6 +15,8 @@ import java.util.List;
/**
* 特征提取服务
* Feature Service
*
* @author Calvin
* @date 2021-12-12
**/

View File

@ -16,6 +16,8 @@ import java.util.List;
/**
* 文件存储服务
* File Storage Service
*
* @author Calvin
* @date 2021-12-12
**/
@ -58,6 +60,8 @@ public class LocalStorageServiceImpl implements LocalStorageService {
/**
* 保存上传文件列表
* save file list
*
*/
public void saveStorageList() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
@ -73,6 +77,7 @@ public class LocalStorageServiceImpl implements LocalStorageService {
/**
* 新增文件
* add file
*/
public void addStorageFile(LocalStorage localStorage) {
localStorage.setId(storageId);
@ -83,6 +88,7 @@ public class LocalStorageServiceImpl implements LocalStorageService {
/**
* 根据ID查询
* get file by id
*/
public LocalStorage findById(int id) {
for (LocalStorage localStorage : storageList) {
@ -95,6 +101,7 @@ public class LocalStorageServiceImpl implements LocalStorageService {
/**
* 删除
* Delete
*/
public boolean delete(int id) {
for (LocalStorage localStorage : storageList) {

View File

@ -66,6 +66,7 @@ public class SearchServiceImpl implements SearchService {
private static final String VECTOR_FIELD = "feature";
// 重置向量引擎
//Reset vector engine
public void clearSearchEngine() {
ConnectionPool connPool = this.getConnectionPool(false);
MilvusClient client = connPool.getConnection();
@ -80,6 +81,7 @@ public class SearchServiceImpl implements SearchService {
}
// 初始化向量引擎
//Initialize vector engine
public void initSearchEngine() {
ConnectionPool connPool = this.getConnectionPool(false);
MilvusClient client = connPool.getConnection();
@ -105,12 +107,15 @@ public class SearchServiceImpl implements SearchService {
public void returnConnection(ConnectionPool connPool, MilvusClient client) {
// 释放 Milvus client 回连接池
//Release Milvus client back to the connection pool
connPool.returnConnection(client);
// 关闭 Milvus 连接池
//Close the Milvus connection pool
// connPool.closeConnectionPool();
}
// 检查是否存在 collection
//Check if collection exists
public R<Boolean> hasCollection(MilvusClient client) {
R<Boolean> response = client.hasCollection(HasCollectionParam.newBuilder()
.withCollectionName(collectionName)
@ -130,6 +135,7 @@ public class SearchServiceImpl implements SearchService {
}
// 创建 collection
//Create collection
public R<RpcStatus> createCollection(MilvusClient milvusClient, long timeoutMiliseconds) {
System.out.println("========== createCollection() ==========");
FieldType fieldType1 = FieldType.newBuilder()
@ -137,7 +143,7 @@ public class SearchServiceImpl implements SearchService {
.withDescription("image id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(false) // 使用数据库生成的id
.withAutoID(false)
.build();
FieldType fieldType2 = FieldType.newBuilder()
@ -161,6 +167,7 @@ public class SearchServiceImpl implements SearchService {
}
// 加载 collection
//Load collection
public R<RpcStatus> loadCollection(MilvusClient milvusClient) {
System.out.println("========== loadCollection() ==========");
R<RpcStatus> response = milvusClient.loadCollection(LoadCollectionParam.newBuilder()
@ -170,6 +177,7 @@ public class SearchServiceImpl implements SearchService {
}
// 释放 collection
//Release collection
public R<RpcStatus> releaseCollection(MilvusClient milvusClient) {
System.out.println("========== releaseCollection() ==========");
R<RpcStatus> response = milvusClient.releaseCollection(ReleaseCollectionParam.newBuilder()
@ -179,6 +187,7 @@ public class SearchServiceImpl implements SearchService {
}
// 删除 collection
//Delete collection
public R<RpcStatus> dropCollection(MilvusClient milvusClient) {
System.out.println("========== dropCollection() ==========");
R<RpcStatus> response = milvusClient.dropCollection(DropCollectionParam.newBuilder()
@ -188,6 +197,7 @@ public class SearchServiceImpl implements SearchService {
}
// 创建 分区
//Create partition
public R<RpcStatus> createPartition(MilvusClient milvusClient, String partitionName) {
System.out.println("========== createPartition() ==========");
R<RpcStatus> response = milvusClient.createPartition(CreatePartitionParam.newBuilder()
@ -198,6 +208,7 @@ public class SearchServiceImpl implements SearchService {
}
// 删除 分区
//Delete partition
public R<RpcStatus> dropPartition(MilvusClient milvusClient, String partitionName) {
System.out.println("========== dropPartition() ==========");
R<RpcStatus> response = milvusClient.dropPartition(DropPartitionParam.newBuilder()
@ -208,6 +219,7 @@ public class SearchServiceImpl implements SearchService {
}
// 是否存在分区
//Check if partition exists
public R<Boolean> hasPartition(MilvusClient milvusClient, String partitionName) {
System.out.println("========== hasPartition() ==========");
R<Boolean> response = milvusClient.hasPartition(HasPartitionParam.newBuilder()
@ -218,6 +230,7 @@ public class SearchServiceImpl implements SearchService {
}
// 创建 index
//Create index
public R<RpcStatus> createIndex(MilvusClient milvusClient) {
System.out.println("========== createIndex() ==========");
String INDEX_PARAM = "{\"nlist\":" + nlist + "}";
@ -296,6 +309,7 @@ public class SearchServiceImpl implements SearchService {
}
// 删除 index
//Delete index
public R<RpcStatus> dropIndex(MilvusClient milvusClient) {
System.out.println("========== dropIndex() ==========");
R<RpcStatus> response = milvusClient.dropIndex(DropIndexParam.newBuilder()
@ -306,6 +320,7 @@ public class SearchServiceImpl implements SearchService {
}
// 插入向量
//Insert vectors
public R<MutationResult> insert(List<Long> vectorIds, List<List<Float>> vectors) {
System.out.println("========== insert() ==========");
ConnectionPool connPool = this.getConnectionPool(false);
@ -328,6 +343,7 @@ public class SearchServiceImpl implements SearchService {
}
// 查询向量
//Query vectors
// queryExpr = ID_FIELD + " == 60";
public R<QueryResults> query(String expr) {
System.out.println("========== query() ==========");
@ -351,6 +367,7 @@ public class SearchServiceImpl implements SearchService {
}
// 搜索向量
//Search vectors
public R<SearchResults> search(Integer topK, List<List<Float>> vectorsToSearch) {
System.out.println("========== searchImage() ==========");
ConnectionPool connPool = this.getConnectionPool(false);
@ -385,6 +402,7 @@ public class SearchServiceImpl implements SearchService {
}
// 删除向量
// Delete Vector
// String deleteExpr = ID_FIELD + " in " + deleteIds.toString();
public R<MutationResult> delete(String expr) {
System.out.println("========== delete() ==========");

View File

@ -12,6 +12,7 @@ import io.milvus.param.partition.DropPartitionParam;
/**
* 搜索引擎初始化工具
* Search engine initialization tool
*
* @author Calvin
* @date 2021-12-12
@ -33,6 +34,7 @@ public class MilvusInit {
public static void main(String[] args) {
try {
// 检查 collection 是否存在不存在会抛异常
// Check if the collection exists, an exception will be thrown if it does not exist
hasCollection();
releaseCollection();
// dropPartition("p1");
@ -43,10 +45,12 @@ public class MilvusInit {
e.printStackTrace();
}
// 关闭 Milvus 连接
// Close Milvus connection
milvusClient.close();
}
// 检查是否存在 collection
// Check if the collection exists
private static R<Boolean> hasCollection() {
System.out.println("========== hasCollection() ==========");
R<Boolean> response = milvusClient.hasCollection(HasCollectionParam.newBuilder()
@ -58,6 +62,7 @@ public class MilvusInit {
}
// 删除 collection
// Delete collection
private static R<RpcStatus> dropCollection() {
System.out.println("========== dropCollection() ==========");
R<RpcStatus> response = milvusClient.dropCollection(DropCollectionParam.newBuilder()
@ -78,6 +83,7 @@ public class MilvusInit {
}
// 删除 index
// Delete index
private static R<RpcStatus> dropIndex() {
System.out.println("========== dropIndex() ==========");
R<RpcStatus> response = milvusClient.dropIndex(DropIndexParam.newBuilder()

View File

@ -60,6 +60,7 @@ public class ConfigAdapter implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 使用 fastjson 序列化会导致 @JsonIgnore 失效可以使用 @JSONField(serialize = false) 替换
// Use fastjson serialization, which will cause @JsonIgnore to be invalid, can be replaced with @JSONField (serialize = false)
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportMediaTypeList = new ArrayList<>();
supportMediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);

View File

@ -68,7 +68,7 @@ public class SwaggerConfig {
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.description("")
.title("接口文档")
.title("API Doc")
.version("1.0")
.build();
}
@ -77,6 +77,7 @@ public class SwaggerConfig {
/**
* 将Pageable转换展示在swagger中
* Convert Pageable for display in Swagger
*/
@Configuration
class SwaggerDataConfig {
@ -99,13 +100,13 @@ class SwaggerDataConfig {
@ApiModel
@Data
private static class Page {
@ApiModelProperty("页码 (0..N)")
@ApiModelProperty("Page number (0..N)")
private Integer page;
@ApiModelProperty("每页显示的数目")
@ApiModelProperty("Number of items per page")
private Integer size;
@ApiModelProperty("以下列格式排序标准property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
@ApiModelProperty("Sort criteria in the following format: property[,asc|desc]. Default sort order is ascending. Multiple sort conditions are supported, such as id,asc")
private List<String> sort;
}
}

View File

@ -24,7 +24,7 @@ import java.util.List;
* @author Calvin
* @date Oct 19, 2021
*/
@Api(tags = "通用文字识别")
@Api(tags = "通用文字识别 -General Text Recognition")
@RestController
@RequestMapping("/inference")
public class InferController {
@ -42,7 +42,7 @@ public class InferController {
@Autowired
private FileProperties properties;
@ApiOperation(value = "通用文字识别-URL")
@ApiOperation(value = "通用文字识别-URL -General Text Recognition-URL")
@GetMapping(value = "/generalInfoForImageUrl", produces = "application/json;charset=utf-8")
public ResultBean generalInfoForImageUrl(@RequestParam(value = "url") String url) throws IOException {
try {
@ -56,7 +56,7 @@ public class InferController {
}
}
@ApiOperation(value = "通用文字识别-图片")
@ApiOperation(value = "通用文字识别-图片 -General Text Recognition-Image")
@PostMapping(value = "/generalInfoForImageFile", produces = "application/json;charset=utf-8")
public ResultBean generalInfoForImageFile(@RequestParam(value = "imageFile") MultipartFile imageFile) {
try (InputStream inputStream = imageFile.getInputStream()) {

View File

@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author Calvin
* @date Oct 19, 2021
*/
@Api(tags = "表格文字识别")
@Api(tags = "表格文字识别 - Table Text Recognition")
@RestController
@RequestMapping("/table")
public class TableController {
@ -50,13 +50,14 @@ public class TableController {
@Autowired
private FileProperties properties;
@ApiOperation(value = "单表格文字识别-URL")
@ApiOperation(value = "单表格文字识别-URL - Single Table Text Recognition - URL")
@GetMapping(value = "/tableInfoForImageUrl")
public ResultBean tableInfoForImageUrl(@RequestParam(value = "url") String url) {
try {
Image image = ImageFactory.getInstance().fromUrl(url);
String tableHtml = tableInferService.getTableHtml(image);
// 创建一个Excel文件
// Create an Excel file
tableHtml = tableHtml.replace("<html><body>", "");
tableHtml = tableHtml.replace("</body></html>", "");
HSSFWorkbook workbook = ConvertHtml2Excel.table2Excel(tableHtml);
@ -92,7 +93,7 @@ public class TableController {
}
}
@ApiOperation(value = "单表格文字识别-图片")
@ApiOperation(value = "单表格文字识别-图片 -Single Table Text Recognition - Image")
@PostMapping("/tableInfoForImageFile")
public ResultBean tableInfoForImageFile(@RequestParam(value = "imageFile") MultipartFile imageFile) {
try (InputStream inputStream = imageFile.getInputStream()) {
@ -100,6 +101,7 @@ public class TableController {
Image image = ImageFactory.getInstance().fromInputStream(inputStream);
String tableHtml = tableInferService.getTableHtml(image);
// 创建一个Excel文件
// Create an Excel file
tableHtml = tableHtml.replace("<html><body>", "");
tableHtml = tableHtml.replace("</body></html>", "");
HSSFWorkbook workbook = ConvertHtml2Excel.table2Excel(tableHtml);
@ -138,7 +140,7 @@ public class TableController {
}
}
@ApiOperation(value = "表单表格自动检测文字识别-URL")
@ApiOperation(value = "表单表格自动检测文字识别-URL -Auto Table Text Detection and Recognition - URL")
@GetMapping(value = "/autoTableInfoForImageUrl")
public ResultBean autoTableInfoForImageUrl(@RequestParam(value = "url") String url) {
try {
@ -190,7 +192,7 @@ public class TableController {
}
}
@ApiOperation(value = "表单表格自动检测文字识别-URL")
@ApiOperation(value = "表单表格自动检测文字识别-URL -Auto Table Text Detection and Recognition - Image")
@PostMapping("/autoTableInfoForImageFile")
public ResultBean autoTableInfoForImageFile(@RequestParam(value = "imageFile") MultipartFile imageFile) {
try (InputStream inputStream = imageFile.getInputStream()) {

View File

@ -27,7 +27,7 @@ import java.util.Map;
* @author Calvin
* @date Oct 19, 2021
*/
@Api(tags = "自定义模版文字识别")
@Api(tags = "自定义模版文字识别 - Custom Template Text Recognition")
@RestController
@Configuration
@RequestMapping("/template")
@ -42,11 +42,12 @@ public class TemplateController {
/**
* 文件配置
* File Configuration
*/
@Autowired
private FileProperties properties;
@ApiOperation(value = "获取模版")
@ApiOperation(value = "获取模版 Get Template")
@GetMapping(value = "/getTemplate", produces = "application/json;charset=utf-8")
public ResultBean getTemplate(@RequestParam(value = "uid") String uid) {
try {
@ -58,7 +59,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "获取模版列表")
@ApiOperation(value = "获取模版列表 Get Template List")
@GetMapping(value = "/getTemplates", produces = "application/json;charset=utf-8")
public ResultBean getTemplatesList() {
try {
@ -69,7 +70,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "更新模板信息")
@ApiOperation(value = "更新模板信息 Update Template Information")
@PostMapping(value = "/updateTemplate", consumes = "application/json;charset=utf-8")
public ResultBean updateTemplate(@RequestBody TemplateBean templateBean) {
try {
@ -81,7 +82,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "删除模板")
@ApiOperation(value = "删除模板 Remove Template")
@PostMapping(value = "/removeTemplate", produces = "application/json;charset=utf-8")
public ResultBean removeTemplate(@RequestParam(value = "uid") String uid) {
try {
@ -93,7 +94,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "识别标注字段")
@ApiOperation(value = "识别标注字段 Recognize Label Fields")
@PostMapping(value = "/getLabelData", produces = "application/json;charset=utf-8")
public ResultBean getLabelData(@RequestBody LabelDTO labelDTO) {
try {
@ -107,11 +108,12 @@ public class TemplateController {
}
}
@ApiOperation(value = "创建模板")
@ApiOperation(value = "创建模板 Create Template")
@PostMapping(value = "/addTemplate")
public ResultBean addTemplate(@RequestParam(value = "name") String name, @RequestParam(value = "imageFile") MultipartFile imageFile) {
try {
// 要上传的目标文件存放路径
// Target file storage path to be uploaded
FileProperties.ElPath path = properties.getPath();
String imagePath = path.getPath().replace("\\", "/") + "images/";
FileUtils.checkAndCreatePath(imagePath);
@ -146,7 +148,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "模版文字识别-URL")
@ApiOperation(value = "模版文字识别-URL Template Text Recognition-URL")
@GetMapping(value = "/infoForImageUrl", produces = "application/json;charset=utf-8")
public ResultBean infoForImageUrl(@RequestParam(value = "uid") String uid, @RequestParam(value = "url") String url) {
try {
@ -161,7 +163,7 @@ public class TemplateController {
}
}
@ApiOperation(value = "模版文字识别-图片")
@ApiOperation(value = "模版文字识别-图片 - Template Text Recognition-Image")
@PostMapping(value = "/infoForImageFile", produces = "application/json;charset=utf-8")
public ResultBean infoForImageFile(@RequestParam(value = "uid") String uid, @RequestParam(value = "imageFile") MultipartFile imageFile) {
try (InputStream inputStream = imageFile.getInputStream()) {

View File

@ -14,7 +14,12 @@ import ai.djl.translate.TranslatorContext;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class LayoutDetectionTranslator implements Translator<Image, DetectedObjects> {
private int width;

View File

@ -28,12 +28,14 @@ public class PerspectiveRecogition {
public static Map<String, String> recognize(NDManager manager, BufferedImage templateImg, RecognitionModel recognitionModel, Image image, List<LabelBean> anchorlabels, List<LabelBean> contentLabels, String fileRelativePath, String distance, boolean save) throws TranslateException {
// 锚点识别区 - 计算中心点坐标用于透视变换
// Anchor recognition area - calculating the center point coordinates for perspective transformation
for (int i = 0; i < anchorlabels.size(); i++) {
List<me.aias.ocr.model.Point> points = anchorlabels.get(i).getPoints();
anchorlabels.get(i).setCenterPoint(PointUtils.getCenterPoint(points));
}
// 文本检测区
// Text detection area
List<LabelBean> detectedTexts = new ArrayList<>();
DetectedObjects textDetections = recognitionModel.predict(image);
List<DetectedObjects.DetectedObject> dt_boxes = textDetections.items();
@ -85,23 +87,30 @@ public class PerspectiveRecogition {
srcPoint2f = NDArrayUtils.toOpenCVPoint2f(srcPoints, 3);
dstPoint2f = NDArrayUtils.toOpenCVPoint2f(dstPoints, 3);
//3点仿射变换
// 3-point affine transformation
warp_mat = opencv_imgproc.getAffineTransform(srcPoint2f.position(0), dstPoint2f.position(0));
} else if (dstPoints.size() >= 4) {
srcPoint2f = NDArrayUtils.toOpenCVPoint2f(srcPoints, 4);
dstPoint2f = NDArrayUtils.toOpenCVPoint2f(dstPoints, 4);
//4点透视变换
// 4-point perspective transformation
warp_mat = opencv_imgproc.getPerspectiveTransform(srcPoint2f.position(0), dstPoint2f.position(0));
}
if (dstPoints.size() >= 3 && save) {
// 模板图片透视变换跟上传图片对齐
// Template image perspective transformation, aligning with uploaded image
Mat mat = Java2DFrameUtils.toMat(templateImg);
if (dstPoints.size() == 3) { //3点仿射变换
if (dstPoints.size() == 3) {
//3点仿射变换
// 3-point affine transformation
mat = OpenCVUtils.affineTransform(mat, srcPoint2f, dstPoint2f);
templateImg = Java2DFrameUtils.toBufferedImage(mat);
DJLImageUtils.saveImage(templateImg, "affineTransform.png", fileRelativePath);
} else if (dstPoints.size() >= 4) { //4点透视变换
} else if (dstPoints.size() >= 4) {
//4点透视变换
// 4-point perspective transformation
mat = OpenCVUtils.perspectiveTransform(mat, srcPoint2f, dstPoint2f);
templateImg = Java2DFrameUtils.toBufferedImage(mat);
DJLImageUtils.saveImage(templateImg, "perspectiveTransform.png", fileRelativePath);
@ -109,16 +118,20 @@ public class PerspectiveRecogition {
}
// 内容识别区 - 计算中心点坐标用于距离计算
// Content recognition area - calculating the center point coordinates for distance calculation
for (int i = 0; i < contentLabels.size(); i++) {
List<me.aias.ocr.model.Point> points = contentLabels.get(i).getPoints();
//根据变换矩阵对所有点坐标进行坐标变换
// Transform all point coordinates using the transformation matrix
if (dstPoints.size() >= 3) {
points = PointUtils.transformPoints(manager, warp_mat, points);
}
ai.djl.modality.cv.output.Point point = PointUtils.getCenterPoint(points);
if (dstPoints.size() < 3) { //无坐标变换则将坐标归一化坐标变为占整张图片的百分比
if (dstPoints.size() < 3) {
//无坐标变换则将坐标归一化坐标变为占整张图片的百分比
// No coordinate transformation, normalize the coordinates (i.e., convert them to a percentage of the entire image)
double x = point.getX() / templateImg.getHeight();
double y = point.getY() / templateImg.getWidth();
ai.djl.modality.cv.output.Point djlPoint = new ai.djl.modality.cv.output.Point(x, y);
@ -133,11 +146,14 @@ public class PerspectiveRecogition {
}
// 文本检测区 - 中心点坐标根据透视变换矩阵进行坐标变换
// Text detection area - center point coordinates are transformed based on the perspective transformation matrix
for (int i = 0; i < detectedTexts.size(); i++) {
List<me.aias.ocr.model.Point> points = detectedTexts.get(i).getPoints();
ai.djl.modality.cv.output.Point point = PointUtils.getCenterPoint(points);
if (dstPoints.size() < 3) { //无坐标变换则将坐标归一化坐标变为占整张图片的百分比
if (dstPoints.size() < 3) {
//无坐标变换则将坐标归一化坐标变为占整张图片的百分比
// No coordinate transformation, normalize the coordinates (i.e., convert them to a percentage of the entire image)
double x = point.getX() / image.getHeight();
double y = point.getY() / image.getWidth();
ai.djl.modality.cv.output.Point djlPoint = new ai.djl.modality.cv.output.Point(x, y);
@ -161,6 +177,7 @@ public class PerspectiveRecogition {
if (save) {
// 保存图
// Save image
DJLImageUtils.saveImage((BufferedImage) image.getWrappedImage(), "points_result.png", fileRelativePath);
}

View File

@ -48,6 +48,7 @@ public final class TableDetectionModel {
List<DetectedObjects.DetectedObject> dt_boxes = textDetections.items();
// 获取 Cell 文本检测框 的对应关系(1:N)
// Get the correspondence (1:N) between cells and text detection boxes.
Map<Integer, List<Integer>> matched = new ConcurrentHashMap<>();
for (int i = 0; i < dt_boxes.size(); i++) {
@ -55,6 +56,7 @@ public final class TableDetectionModel {
Rectangle textBounds = item.getBoundingBox().getBounds();
int[] box_1 = rectXYXY(textBounds, image.getWidth(), image.getHeight());
// 获取两两cell之间的L1距离和 1- IOU
// Get the L1 distance and 1- IOU between each pair of cells.
List<Pair<Float, Float>> distances = new ArrayList<>();
for (BoundingBox cell : cells) {
Rectangle cellBounds = cell.getBounds();
@ -64,9 +66,11 @@ public final class TableDetectionModel {
distances.add(Pair.of(distance, iou));
}
// 根据距离和IOU挑选最""的cell
// Select the "closest" cell based on the distance and IOU.
Pair<Float, Float> nearest = sorted(distances);
// 获取最小距离对应的下标id也等价于cell的下标id distances列表是根据遍历cells生成的
// Get the index id of the smallest distance, which is equivalent to the index id of the cell (the distances list is generated by traversing cells).
int id = 0;
for (int idx = 0; idx < distances.size(); idx++) {
Pair<Float, Float> current = distances.get(idx);

View File

@ -28,7 +28,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class TableStructTranslator implements Translator<Image, TableResult> {
private final int maxLength;
@ -237,7 +242,6 @@ public class TableStructTranslator implements Translator<Image, TableResult> {
public void saveImage(Image img, String name, String path) {
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
img.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {

View File

@ -51,9 +51,11 @@ public class TableInferServiceImpl implements TableInferService {
List<DetectedObjects.DetectedObject> boxes = layoutDetections.items();
for (int i = 0; i < boxes.size(); i++) {
// TODO 模型需优化, 页面出现多个表不准但是当前可以用于表单中一个表格自动检测识别
// TODO Model needs optimization, multiple tables on the page are not allowed, but currently it can be used to automatically detect and recognize a table in a form
if (boxes.get(i).getClassName().equals("Table")) {
Image subImage = getSubImage(image, boxes.get(i).getBoundingBox());
// 表格单元检测
// Table cell detection
DetectedObjects textDetections = recognitionModel.predict(subImage);
String tableHtml = tableDetectionModel.getTableHtml(subImage, textDetections);
tableHtmlList.add(tableHtml);

View File

@ -95,6 +95,7 @@ public class TemplateServiceImpl implements TemplateService {
/**
* 获取模板
* Get Template
*
* @param uid
*/
@ -112,6 +113,7 @@ public class TemplateServiceImpl implements TemplateService {
/**
* 新增模板
* Add Template
*
* @param templateBean
*/
@ -123,15 +125,18 @@ public class TemplateServiceImpl implements TemplateService {
String json = gson.toJson(templateList);
FileProperties.ElPath path = properties.getPath();
// 保存模版列表数据
// Save template list data
String fileRelativePath = path.getPath().replace("\\", "/");
FileUtils.saveFile(fileRelativePath, TEMPLATE_LIST_FILE, json);
// 保存模版数据
// Save template data
json = gson.toJson(templateBean);
FileUtils.saveFile(fileRelativePath + "templates/", templateBean.getUid() + ".json", json);
}
/**
* 更新模板
* Update Template
*
* @param templateBean
*/
@ -149,15 +154,18 @@ public class TemplateServiceImpl implements TemplateService {
String json = gson.toJson(templateList);
FileProperties.ElPath path = properties.getPath();
// 保存模版列表数据
// Save template list data
String fileRelativePath = path.getPath().replace("\\", "/");
FileUtils.saveFile(fileRelativePath, TEMPLATE_LIST_FILE, json);
// 保存模版数据
// Save template data
json = gson.toJson(templateBean);
FileUtils.saveFile(fileRelativePath + "templates/", templateBean.getUid() + ".json", json);
}
/**
* 删除模板
* Delete Template
*
* @param uid
*/
@ -174,9 +182,11 @@ public class TemplateServiceImpl implements TemplateService {
String json = gson.toJson(templateList);
FileProperties.ElPath path = properties.getPath();
// 保存模版列表数据
// Save template data
String fileRelativePath = path.getPath().replace("\\", "/");
FileUtils.saveFile(fileRelativePath, TEMPLATE_LIST_FILE, json);
// 删除模版数据
// Delete template data
FileUtils.removeFile(fileRelativePath + "templates/", uid + ".json");
}

View File

@ -25,8 +25,9 @@ import java.util.List;
public class ConvertHtml2Excel {
/**
* html表格转excel
* Convert HTML table to Excel
*
* @param tableHtml
* @param tableHtml
* <table>
* ..
* </table>
@ -40,9 +41,10 @@ public class ConvertHtml2Excel {
try {
Document data = DocumentHelper.parseText(tableHtml);
// 生成表头
// generate header
Element thead = data.getRootElement().element("thead");
HSSFCellStyle titleStyle = getTitleStyle(wb);
int ls=0;//列数
int ls=0;//列数 //column number
if (thead != null) {
List<Element> trLs = thead.elements("tr");
for (Element trEle : trLs) {
@ -54,6 +56,7 @@ public class ConvertHtml2Excel {
}
}
// 生成表体
// generate body
Element tbody = data.getRootElement().element("tbody");
HSSFCellStyle contentStyle = getContentStyle(wb);
if (tbody != null) {
@ -68,12 +71,15 @@ public class ConvertHtml2Excel {
}
}
// 合并表头
// merge header
for (CrossRangeCellMeta crcm : crossRowEleMetaLs) {
sheet.addMergedRegion(new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()));
setRegionStyle(sheet, new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()),titleStyle);
}
for(int i=0;i<sheet.getRow(0).getPhysicalNumberOfCells();i++){
sheet.autoSizeColumn(i, true);//设置列宽
sheet.autoSizeColumn(i, true);
//设置列宽
//set column width
if(sheet.getColumnWidth(i)<255*256){
sheet.setColumnWidth(i, sheet.getColumnWidth(i) < 9000 ? 9000 : sheet.getColumnWidth(i));
}else{
@ -89,16 +95,17 @@ public class ConvertHtml2Excel {
/**
* 生产行内容
* Generate row content
*
* @return 最后一列的cell index
* @return 最后一列的cell index - the index of the last cell
*/
/**
* @param tdLs th或者td集合
* @param rowIndex 行号
* @param row POI行对象
* @param tdLs th或者td集合 - th or td list
* @param rowIndex 行号 - row number
* @param row POI行对象 - POI row object
* @param startCellIndex
* @param cellStyle 样式
* @param crossRowEleMetaLs 跨行元数据集合
* @param cellStyle 样式 - style
* @param crossRowEleMetaLs 跨行元数据集合 - row and column span metadata set
* @return
*/
private static int makeRowCell(List<Element> tdLs, int rowIndex, HSSFRow row, int startCellIndex, HSSFCellStyle cellStyle,
@ -107,7 +114,9 @@ public class ConvertHtml2Excel {
for (int eleIndex = 0; eleIndex < tdLs.size(); i++, eleIndex++) {
int captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs);
while (captureCellSize > 0) {
for (int j = 0; j < captureCellSize; j++) {// 当前行跨列处理补单元格
for (int j = 0; j < captureCellSize; j++) {
// 当前行跨列处理补单元格
//handle the current row span (fill in cells)
row.createCell(i);
i++;
}
@ -131,10 +140,14 @@ public class ConvertHtml2Excel {
int rowSpan = NumberUtils.toInt(thEle.attributeValue("rowspan"), 1);
int colSpan = NumberUtils.toInt(thEle.attributeValue("colspan"), 1);
c.setCellStyle(cellStyle);
if (rowSpan > 1 || colSpan > 1) { // 存在跨行或跨列
if (rowSpan > 1 || colSpan > 1) {
// 存在跨行或跨列
//exists row and column span
crossRowEleMetaLs.add(new CrossRangeCellMeta(rowIndex, i, rowSpan, colSpan));
}
if (colSpan > 1) {// 当前行跨列处理补单元格
if (colSpan > 1) {
// 当前行跨列处理补单元格
// handle the current row span (fill in cells)
for (int j = 1; j < colSpan; j++) {
i++;
row.createCell(i);
@ -146,6 +159,7 @@ public class ConvertHtml2Excel {
/**
* 设置合并单元格的边框样式
* Set the border style of the merged cells
*
* @param sheet
* @param region
@ -163,11 +177,12 @@ public class ConvertHtml2Excel {
/**
* 获得因rowSpan占据的单元格
* Get the cells occupied by rowSpan
*
* @param rowIndex 行号
* @param colIndex 列号
* @param crossRowEleMetaLs 跨行列元数据
* @return 当前行在某列需要占据单元格
* @param rowIndex 行号 - row number
* @param colIndex 列号 - column number
* @param crossRowEleMetaLs 跨行列元数据 - row and column span metadata
* @return 当前行在某列需要占据单元格 - the number of cells to be occupied in a column on the current row
*/
private static int getCaptureCellSize(int rowIndex, int colIndex, List<CrossRangeCellMeta> crossRowEleMetaLs) {
int captureCellSize = 0;
@ -183,6 +198,7 @@ public class ConvertHtml2Excel {
/**
* 获得标题样式
* Get the title style
*
* @param workbook
* @return
@ -194,12 +210,12 @@ public class ConvertHtml2Excel {
HSSFCellStyle style = workbook.createCellStyle();
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setAlignment(HorizontalAlignment.CENTER);
style.setBorderBottom(BorderStyle.THIN); //下边框
style.setBorderLeft(BorderStyle.THIN);//左边框
style.setBorderTop(BorderStyle.THIN);//上边框
style.setBorderRight(BorderStyle.THIN);//右边框
style.setBorderBottom(BorderStyle.THIN); //下边框 bottom border
style.setBorderLeft(BorderStyle.THIN);//左边框 left border
style.setBorderTop(BorderStyle.THIN);//上边框 top border
style.setBorderRight(BorderStyle.THIN);//右边框 right border
//style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//style.setFillForegroundColor(titlebackgroundcolor);// 背景色
//style.setFillForegroundColor(titlebackgroundcolor);// 背景色 background color
HSSFFont font = workbook.createFont();
font.setFontName(fontName);
@ -211,6 +227,7 @@ public class ConvertHtml2Excel {
/**
* 获得内容样式
* Get the content style
*
* @param wb
* @return
@ -219,16 +236,16 @@ public class ConvertHtml2Excel {
short fontSize = 12;
String fontName = "宋体";
HSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom(BorderStyle.THIN); //下边框
style.setBorderLeft(BorderStyle.THIN);//左边框
style.setBorderTop(BorderStyle.THIN);//上边框
style.setBorderRight(BorderStyle.THIN);//右边框
style.setBorderBottom(BorderStyle.THIN); //下边框 bottom border
style.setBorderLeft(BorderStyle.THIN);//左边框 left border
style.setBorderTop(BorderStyle.THIN);//上边框 top border
style.setBorderRight(BorderStyle.THIN);//右边框 right border
HSSFFont font = wb.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);//水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
style.setAlignment(HorizontalAlignment.CENTER);//水平居中 horizontal center
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中 vertical center
style.setWrapText(true);
return style;
}

View File

@ -10,7 +10,12 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class DJLImageUtils {
public static Image bufferedImage2DJLImage(BufferedImage img) {
@ -18,10 +23,9 @@ public class DJLImageUtils {
}
public static void saveImage(BufferedImage img, String name, String path) {
Image djlImg = ImageFactory.getInstance().fromImage(img); // 支持多种图片格式自动适配
Image djlImg = ImageFactory.getInstance().fromImage(img);
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
djlImg.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {
@ -32,7 +36,6 @@ public class DJLImageUtils {
public static void saveDJLImage(Image img, String name, String path) {
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
img.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {
@ -52,11 +55,9 @@ public class DJLImageUtils {
}
public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(new Color(246, 96, 0));
// 声明画笔属性 单位像素末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);
@ -68,11 +69,9 @@ public class DJLImageUtils {
public static void drawImageRect(
BufferedImage image, int x, int y, int width, int height, Color c) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(c);
// 声明画笔属性 单位像素末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);

View File

@ -7,6 +7,8 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Utility class for calculating distances and IoU.
*
* @author Calvin
* @date Oct 19, 2021
*/
@ -14,8 +16,8 @@ public class DistanceUtils {
/**
* Calculate L2 distance
*
* @param contentLabels 内容识别区
* @param detectedTexts 文本检测区
* @param contentLabels 内容识别区 - the list of labels for content recognition area
* @param detectedTexts 文本检测区 - the list of labels for text detection area
* @return
*/
public static Map<String, String> l2Distance(List<LabelBean> contentLabels, List<LabelBean> detectedTexts) {
@ -40,8 +42,8 @@ public class DistanceUtils {
/**
* Calculate iou
*
* @param contentLabels 内容识别区
* @param detectedTexts 文本检测区
* @param contentLabels 内容识别区 - the list of labels for content recognition area
* @param detectedTexts 文本检测区 - the list of labels for text detection area
* @return
*/
public static Map<String, String> iou(List<LabelBean> contentLabels, List<LabelBean> detectedTexts) {

View File

@ -14,27 +14,31 @@ import java.util.List;
/**
* 文件上传工具包
* File upload tool package
*/
public class FileUtils {
/**
* @param file 文件
* @param path 文件存放路径
* @param fileName 源文件名
* @param file 文件 - file
* @param path 文件存放路径 - file storage path
* @param fileName 源文件名 - source file name
* @return
*/
public static boolean upload(MultipartFile file, String path, String fileName) {
// 生成新的文件名
// Generate a new file name
//String realPath = path + "/" + FileNameUtils.getFileName(fileName);
Path filePath = Paths.get(path + fileName);
File dest = filePath.toAbsolutePath().toFile();
//判断文件父目录是否存在
// Determine if the parent directory of the file exists
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdir();
}
try {
//保存文件
// Save the file
file.transferTo(dest);
return true;
} catch (IllegalStateException e) {
@ -48,6 +52,7 @@ public class FileUtils {
/**
* 获取文件后缀
* Get file suffix
*
* @param fileName
* @return
@ -58,8 +63,8 @@ public class FileUtils {
/**
* 生成新的文件名
*
* @param fileOriginName 源文件名
* Generate a new file name
* @param fileOriginName 源文件名 - source file name
* @return
*/
public static String getFileName(String fileOriginName) {
@ -68,9 +73,10 @@ public class FileUtils {
/**
* 读取json文件
* Read json file
*
* @param path 文件路径信息
* @param fileName 文件名
* @param path 文件路径信息 - file path information
* @param fileName 文件名 - file name
* @return
*/
public static String readFile(String path, String fileName) throws IOException {
@ -89,10 +95,11 @@ public class FileUtils {
/**
* 保存json文件
* Save json file
*
* @param path 文件路径信息
* @param fileName 文件名
* @param json json信息
* @param path 文件路径信息 file path information
* @param fileName 文件名 file name
* @param json json信息 json information
* @return
*/
public static void saveFile(String path, String fileName, String json) throws IOException {
@ -104,9 +111,10 @@ public class FileUtils {
/**
* 删除json文件
* Delete json file
*
* @param path 文件路径信息
* @param fileName 文件名
* @param path 文件路径信息 file path information
* @param fileName 文件名 file name
* @return
*/
public static void removeFile(String path, String fileName) {
@ -117,7 +125,7 @@ public class FileUtils {
/**
* Check & create file path
*
* @param fileRelativePath 文件路径信息
* @param fileRelativePath 文件路径信息 - file path information
* @return
*/
public static void checkAndCreatePath(String fileRelativePath) {

View File

@ -9,7 +9,12 @@ import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Point2f;
import java.util.List;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class NDArrayUtils {
// NDArray opencv_core.Mat
public static Mat toOpenCVMat(NDArray points, int rows, int cols) {
@ -30,6 +35,7 @@ public class NDArrayUtils {
}
// NDArray opencv_core.Point2f
// NDArray to opencv_core.Point2f
public static Point2f toOpenCVPoint2f(NDArray points, int rows) {
double[] doubleArray = points.toDoubleArray();
Point2f points2f = new Point2f(rows);
@ -42,6 +48,7 @@ public class NDArrayUtils {
}
// Double array opencv_core.Point2f
// Double array to opencv_core.Point2f
public static Point2f toOpenCVPoint2f(double[] doubleArray, int rows) {
Point2f points2f = new Point2f(rows);
@ -53,6 +60,7 @@ public class NDArrayUtils {
}
// list opencv_core.Point2f
// list to opencv_core.Point2f
public static Point2f toOpenCVPoint2f(List<ai.djl.modality.cv.output.Point> points, int rows) {
Point2f points2f = new Point2f(points.size());

View File

@ -13,7 +13,12 @@ import java.nio.FloatBuffer;
import static org.bytedeco.opencv.global.opencv_calib3d.findHomography;
import static org.bytedeco.opencv.global.opencv_core.cvCreateMat;
/**
*
* @author Calvin
*
* @email 179209347@qq.com
**/
public class OpenCVUtils {
public static Mat affineTransform(

View File

@ -4,6 +4,7 @@ import java.util.UUID;
/**
* 生成文件名
* Generate file name
*/
public class UUIDUtils {

View File

@ -44,7 +44,7 @@ export const constantRoutes = [
path: '/',
name: 'list',
component: () => import('@/views/iocr/list'),
meta: { title: '自定义模版', icon: 'el-icon-setting' }
meta: { title: 'Custom Template', icon: 'el-icon-setting' }
}
]
},
@ -56,14 +56,14 @@ export const constantRoutes = [
path: 'create',
component: () => import('@/views/iocr/index'),
name: 'CreateTemplate',
meta: { title: '新增模板', icon: 'el-icon-picture' },
meta: { title: 'Create Template', icon: 'el-icon-picture' },
hidden: true
},
{
path: 'edit',
component: () => import('@/views/iocr/edit'),
name: 'EditTemplate',
meta: { title: '编辑模板', noCache: true },
meta: { title: 'Edit Template', noCache: true },
hidden: true
}
]
@ -76,7 +76,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/iocrinfer/index'),
name: 'inference',
meta: { title: '基于模版识别', icon: 'el-icon-document-copy' }
meta: { title: 'Template Inference', icon: 'el-icon-document-copy' }
}
]
},
@ -88,7 +88,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/general/index'),
name: 'inference',
meta: { title: '通用文本识别', icon: 'el-icon-picture' }
meta: { title: 'General Inference', icon: 'el-icon-picture' }
}
]
},
@ -100,7 +100,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/table/index'),
name: 'inference',
meta: { title: '表格识别', icon: 'el-icon-s-grid' }
meta: { title: 'Table Inference', icon: 'el-icon-s-grid' }
}
]
},
@ -112,7 +112,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/autotable/index'),
name: 'inference',
meta: { title: '表格自动检测识别', icon: 'el-icon-s-grid' }
meta: { title: 'Auto Table Inference', icon: 'el-icon-s-grid' }
}
]
},

View File

@ -1,9 +1,9 @@
<template>
<div class="tool">
<el-tooltip effect="dark" content="锚点框" placement="right">
<el-tooltip effect="dark" content="Anchor" placement="right">
<i class="el-icon-menu" @click="changeTool('anchor')" />
</el-tooltip>
<el-tooltip effect="dark" content="内容框" placement="right">
<el-tooltip effect="dark" content="Content" placement="right">
<i class="el-icon-s-grid" @click="changeTool('rectangle')" />
</el-tooltip>
</div>

View File

@ -1,19 +1,19 @@
<template>
<div class="bar">
<el-tooltip effect="dark" content="放大" placement="bottom" class="icon">
<el-tooltip effect="dark" content="Zoom In" placement="bottom" class="icon">
<i class="el-icon-zoom-in" @click="changeEvent('zoomIn')" />
</el-tooltip>
<el-tooltip effect="dark" content="缩小" placement="bottom">
<el-tooltip effect="dark" content="Zoom Out" placement="bottom">
<i class="el-icon-zoom-out" @click="changeEvent('zoomOut')" />
</el-tooltip>
<el-tooltip effect="dark" content="移动" placement="bottom">
<el-tooltip effect="dark" content="Move" placement="bottom">
<i class="el-icon-rank" @click="changeEvent('move')" />
</el-tooltip>
<el-tooltip effect="dark" content="全屏" placement="bottom">
<el-tooltip effect="dark" content="Full Screen" placement="bottom">
<i class="el-icon-full-screen" @click="changeEvent('fullScreen')" />
</el-tooltip>
<div class="status">
当前状态{{ currentStatus }}
Current Status{{ currentStatus }}
</div>
</div>
</template>
@ -29,7 +29,7 @@ export default {
},
data() {
return {
//
// Drawing Parameters
config: {
path_lineWidth: 1,
path_strokeStyle: '#409EFF',

View File

@ -16,7 +16,7 @@
ref="view"
v-loading="loading"
class="view"
element-loading-text="加载中..."
element-loading-text="loading..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
@ -172,7 +172,7 @@ export default {
// this.canvas.addEventListener("mousemove", this.drawNavigationLineEvent, false);
// this.listenScroll()
this.addRightMouseEvent()
const _this = this// vuethis
const _this = this
window.onresize = () => {
return (() => {
_this.refresh()
@ -185,6 +185,7 @@ export default {
document.removeEventListener('keydown', this.keydownEvent, false)
this.observer.disconnect()
// window.onresizewindow.onresize
// Since window.onresize is a global event, it will also be executed when the interface is changed on other pages, which may cause problems. Therefore, it is necessary to cancel the window.onresize event when exiting this interface.
window.onresize = null
},
methods: {
@ -199,9 +200,9 @@ export default {
// document.getElementsByClassName('view')[0].addEventListener('mousewheel', (e) => {
// e.preventDefault()
// if (w.ctrlDown) {
// if (e.wheelDeltaY > 0) { //
// if (e.wheelDeltaY > 0) {
// this.topBarEvent('zoomIn')
// } else { //
// } else {
// this.topBarEvent('zoomOut')
// }
// }

View File

@ -2,8 +2,8 @@
<div>
<el-row type="flex" justify="end" :gutter="20">
<el-col :span="6">
<el-input v-model="name" placeholder="请输入模板名称">
<template slot="prepend">模板名称</template>
<el-input v-model="name" placeholder="Please enter the template name">
<template slot="prepend">template name</template>
</el-input>
</el-col>
<el-col :span="10">
@ -12,14 +12,14 @@
size="medium"
round
@click="onFetchAnchors"
>载入标注
>Load
</el-button>
<el-button
type="primary"
size="medium"
round
@click="onSubmit"
>提交标注</el-button>
>Submit</el-button>
</el-col>
</el-row>
<div class="box">
@ -179,7 +179,7 @@ export default {
if (element.type === 'rectangle') {
if (typeof element.field === 'undefined' || element.field === '') {
pass = false
this.$message.error('内容识别字段名称不能为空!')
this.$message.error('Field name for content recognition cannot be empty!')
}
}
})
@ -192,7 +192,7 @@ export default {
if (element1.type === 'rectangle' && element2.type === 'rectangle') {
if (element1.field === element2.field) {
pass = false
this.$message.error('内容识别字段名称不能重名!')
this.$message.error('Field name for content recognition cannot be duplicated!')
}
}
}
@ -201,7 +201,7 @@ export default {
if (this.name === '') {
pass = false
this.$message.error('请输入模板名称!')
this.$message.error('Please enter the template name!')
}
if (pass) {

View File

@ -2,8 +2,8 @@
<div>
<el-row type="flex" justify="end" :gutter="20">
<el-col :span="6">
<el-input v-model="name" placeholder="请输入模板名称">
<template slot="prepend">模板名称</template>
<el-input v-model="name" placeholder="Enter template name">
<template slot="prepend">template name</template>
</el-input>
</el-col>
<el-col :span="4">
@ -24,8 +24,8 @@
v-loading.fullscreen.lock="fullscreenLoading"
type="primary"
round
element-loading-text="拼命加载中"
>新建模版
element-loading-text="loading"
>Create
</el-button>
</el-upload>
</el-col>
@ -36,7 +36,7 @@
:disabled="displayDisabled"
round
@click="onFetchAnchors"
>载入标注
>Load
</el-button>
<el-button
type="primary"
@ -44,7 +44,7 @@
:disabled="displayDisabled"
round
@click="onSubmit"
>提交标注
>Submit
</el-button>
</el-col>
</el-row>
@ -123,7 +123,7 @@ export default {
return pass
}
if (this.name === '') {
this.$message.error('请输入模板名称!')
this.$message.error('Please enter template name!')
return false
}
return pass
@ -212,7 +212,7 @@ export default {
if (element.type === 'rectangle') {
if (typeof element.field === 'undefined' || element.field === '') {
pass = false
this.$message.error('内容识别字段名称不能为空!')
this.$message.error('Field name for content recognition cannot be empty!')
}
}
})
@ -225,7 +225,7 @@ export default {
if (element1.type === 'rectangle' && element2.type === 'rectangle') {
if (element1.field === element2.field) {
pass = false
this.$message.error('内容识别字段名称不能重名!')
this.$message.error('Field name for content recognition cannot be duplicated!')
}
}
}
@ -234,7 +234,7 @@ export default {
if (this.name === '') {
pass = false
this.$message.error('请输入模板名称!')
this.$message.error('Please enter the template name!')
}
if (pass) {

View File

@ -13,12 +13,12 @@
{{ scope.row.uid }}
</template>
</el-table-column>
<el-table-column label="模版名称" align="center">
<el-table-column label="Template Name" align="center">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="模版图片" align="center">
<el-table-column label="Template Image" align="center">
<template slot-scope="scope">
<el-image
style="width: 50px; height: 50px"
@ -28,19 +28,19 @@
</el-table-column>
<el-table-column
fixed="right"
label="操作"
label="Operation"
width="200"
>
<template slot="header">
<router-link :to="{path: '/iocr/create/'}">
<el-button size="mini" type="success">新增</el-button>
<el-button size="mini" type="success">Add</el-button>
</router-link>
</template>
<template slot-scope="scope">
<router-link :to="{path: '/iocr/edit/', query: {atemplate: scope.row}}">
<el-button size="mini" type="primary">编辑</el-button>
<el-button size="mini" type="primary">Edit</el-button>
</router-link>
<el-button size="mini" type="danger" @click="handleDelete(scope.row.uid)">删除</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row.uid)">Delete</el-button>
</template>
</el-table-column>
</el-table>
@ -71,24 +71,24 @@ export default {
this.list = response.data.result
this.listLoading = false
}).catch(function(response) {
console.log(response)//
console.log(response)
})
},
handleDelete(row) {
this.$confirm('此操作将删除设置项,是否继续?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm('This operation will delete the setting, continue?', 'Delete confirmation', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
removeTemplate(row).then(response => {
this.$message({
type: 'success',
message: '删除成功!'
message: 'Deleted successfully!'
})
this.fetchData()
})
}).catch(() => {
console.log('取消成功')
console.log('Cancelled successfully')
})
}
}

View File

@ -1,6 +1,6 @@
<template>
<el-tabs type="border-card">
<el-tab-pane label="参照锚点">
<el-tab-pane label="Anchor Point">
<el-table
ref="anchorTable"
:highlight-current-row="true"
@ -8,14 +8,14 @@
style="width: 100%"
@row-click="handleChange"
>
<el-table-column label="请框选参照锚点字段">
<el-table-column label="Please select the anchor point field">
<template slot-scope="scope">
<el-popconfirm
confirm-button-text="确定"
cancel-button-text="取消"
confirm-button-text="OK"
cancel-button-text="Cancel"
icon="el-icon-info"
icon-color="red"
title="确定删除元素?"
title="Are you sure you want to delete this element?"
@onConfirm="deleteAnchorElement(scope.$index,scope.row)"
>
<i
@ -24,12 +24,12 @@
style="margin:0 10px 0 0"
/>
</el-popconfirm>
参照字段: <el-input v-model="scope.row.value" type="text" size="small" style="width:240px" />
Reference Field: <el-input v-model="scope.row.value" type="text" size="small" style="width:240px" />
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="内容识别区">
<el-tab-pane label="Content Recognition Area">
<el-table
ref="contentTable"
:highlight-current-row="true"
@ -37,14 +37,14 @@
style="width: 100%"
@row-click="handleChange"
>
<el-table-column label="请框选内容识别区">
<el-table-column label="Please select the content recognition area">
<template slot-scope="scope">
<el-popconfirm
confirm-button-text="确定"
cancel-button-text="取消"
confirm-button-text="OK"
cancel-button-text="Cancel"
icon="el-icon-info"
icon-color="red"
title="确定删除元素?"
title="Are you sure you want to delete this element?"
@onConfirm="deleteContentElement(scope.$index,scope.row)"
>
<i
@ -53,7 +53,7 @@
style="margin:0 10px 0 0"
/>
</el-popconfirm>
字段名称: <el-input v-model="scope.row.field" type="text" size="small" style="width:100px" @change="fieldChange" />
Field Name: <el-input v-model="scope.row.field" type="text" size="small" style="width:100px" @change="fieldChange" />
<div>
{{ scope.row.value }}
</div>

View File

@ -1,8 +1,8 @@
<template>
<div class="app-container">
<el-form ref="form" :model="form" label-width="120">
<el-form-item label="选择模板">
<el-select v-model="uid" placeholder="请选择">
<el-form-item label="Select">
<el-select v-model="uid" placeholder="Select">
<el-option
v-for="item in list"
:key="item.uid"
@ -11,7 +11,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="在线图片">
<el-form-item label="Image Url">
<el-input v-model="form.url" />
</el-form-item>
<el-row>
@ -36,9 +36,9 @@
v-loading.fullscreen.lock="fullscreenLoading"
type="primary"
size="small"
element-loading-text="拼命加载中"
element-loading-text="loading"
@click="onSubmit"
>文字识别</el-button>
>Submit</el-button>
</el-form-item>
<el-form-item>
<el-divider />
@ -46,7 +46,7 @@
<el-row>
<el-col :span="8">
<div><img :src="form.base64Img" width="400px" class="avatar"></div>
<el-form-item label="本地图片">
<el-form-item label="Local Image">
<el-upload
ref="upload"
name="imageFile"
@ -61,15 +61,15 @@
:show-file-list="false"
:auto-upload="false"
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button slot="trigger" size="small" type="primary">Select</el-button>
<el-button
v-loading.fullscreen.lock="fullscreenLoading"
style="margin-left: 10px;"
type="success"
size="small"
element-loading-text="拼命加载中"
element-loading-text="loading"
@click="submitUpload"
>上传</el-button>
>Upload</el-button>
<div slot="tip" class="el-upload__tip">Image format: JPG(JPEG), PNG</div>
</el-upload>
</el-form-item>
@ -121,7 +121,7 @@ export default {
getTemplates().then(response => {
this.list = response.data.result
}).catch(function(response) {
console.log(response)//
console.log(response)
})
},
upload() {
@ -130,7 +130,7 @@ export default {
},
submitUpload() {
if (this.uid === '') {
this.$message.error('请选择模板!')
this.$message.error('Please select template!')
} else {
this.fullscreenLoading = true
this.$refs.upload.submit()
@ -156,14 +156,14 @@ export default {
this.$message.error('Image format should be JPG(JPEG) or PNG!')
}
if (this.uid === '') {
this.$message.error('请选择模板!')
this.$message.error('Please select template!')
return false
}
return pass
},
onSubmit() {
if (this.uid === '') {
this.$message.error('请选择模板!')
this.$message.error('Please select template!')
} else {
this.fullscreenLoading = true
infoForImageUrl(this.uid, this.form).then(response => {

View File

@ -26,9 +26,9 @@
v-loading.fullscreen.lock="fullscreenLoading"
type="primary"
size="small"
element-loading-text="拼命加载中"
element-loading-text="loading"
@click="onSubmit"
>文字识别</el-button>
>Submit</el-button>
</el-form-item>
<el-form-item>
<el-divider />
@ -51,15 +51,15 @@
:show-file-list="false"
:auto-upload="false"
>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button slot="trigger" size="small" type="primary">Select</el-button>
<el-button
v-loading.fullscreen.lock="fullscreenLoading"
style="margin-left: 10px;"
type="success"
size="small"
element-loading-text="拼命加载中"
element-loading-text="loading"
@click="submitUpload"
>上传</el-button>
>Upload</el-button>
<div slot="tip" class="el-upload__tip">Image format: JPG(JPEG), PNG</div>
</el-upload>
</el-form-item>

View File

@ -1,174 +0,0 @@
### Download the model and place it in the models directory
- Link: https://github.com/mymagicpower/AIAS/releases/download/apps/distiluse-base-multilingual-cased-v1.zip
## Question Answering System
A Question Answering System (QA) is a more advanced form of an information retrieval system that can accurately and concisely answer questions posed by users in natural language. Its research has risen mainly due to the need for rapid and accurate information acquisition. The Question Answering System is a research direction that is highly concerned and has broad development prospects in the field of artificial intelligence and natural language processing.
### Text Search Engine
This example is based on a text search engine that supports uploading CSV files, using the sentence vector model to extract features, and subsequent retrieval based on the Milvus vector engine.
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/arc.png)
### Key Features
- Low-level feature vector similarity search
-Millisecond-level search of billions of data on a single server
-Nearly real-time search, supporting distributed deployment
-Insertion, deletion, search, update and other operations can be performed on data at any time
### Sentence Vector Model [Supports 15 Languages]
A sentence vector refers to a real-valued vector that maps a sentence to a fixed dimension. Representing variable-length sentences with fixed-length vectors provides services for downstream NLP tasks.
Supports 15 languages:
Arabic, Chinese, Dutch, English, French, German, Italian, Korean, Polish, Portuguese, Russian, Spanish, Turkish.
- Sentence Vector
![img](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/nlp_sdks/Universal-Sentence-Encoder.png)
Sentence Vector Applications:
- Semantic search/Question Answering System, retrieve the most matching text in the corpus with the query through sentence vector similarity
-Text clustering, text is transformed into a fixed-length vector, and similar text can be clustered without supervision through a clustering model
-Text classification, represented as sentence vectors, and trained text classifiers directly with simple classifiers
### 1. Frontend Deployment
### 1.1 Installation and Running:
```bash
# Install dependencies
npm install
# Run
npm run dev
```
#### 1.2 Build the dist installation package:
```bash
npm run build:prod
```
#### 1.3 Nginx deployment operation (Mac environment example):
```bash
cd /usr/local/etc/nginx/
vi /usr/local/etc/nginx/nginx.conf
# Edit nginx.conf
server {
listen 8080;
server_name localhost;
location / {
root /Users/calvin/Documents/qa_system/dist/;
index index.html index.htm;
}
......
# Reload configuration:
sudo nginx -s reload
# Restart after deploying the application:
cd /usr/local/Cellar/nginx/1.19.6/bin
# Quick stop
sudo nginx -s stop
# Start
sudo nginx
```
### 2. Backend Jar Deployment
### 2.1 Environmental Requirements:
- System JDK 1.8+
- application.yml
```bash
# File storage path
file:
mac:
path: ~/file/
linux:
path: /home/aias/file/
windows:
path: file:/D:/aias/file/
# File size /M
maxSize: 3000
...
```
#### 2.2 Run the Program:
```bash
java -jar qa-system-0.1.0.jar
```
### 3. Backend Vector Engine Deployment (Milvus 2.0)
### 3.1 Environmental Requirements:
- Docker runtime environment is required, Docker Desktop can be used in Mac environment
### 3.2 Pull Milvus Vector Engine Image (used to calculate feature vector similarity)
Download the milvus-standalone-docker-compose.yml configuration file and save it as docker-compose.yml
[Standalone installation documentation](https://milvus.io/docs/v2.0.0/install_standalone-docker.md)
```bash
wget https://github.com/milvus-io/milvus/releases/download/v2.0.0/milvus-standalone-docker-compose.yml -O docker-compose.yml
```
#### 3.3 Start Docker Container
```bash
sudo docker-compose up -d
```
#### 3.4 Edit the vector engine connection configuration information
- application.yml
- Edit the vector engine connection IP address 127.0.0.1 to the IP address of the host where the container is located as needed
```bash
################## Vector Engine ################
search:
host: 127.0.0.1
port: 19530
```
### 4. Open the browser
- Enter the address: [http://localhost:8090](http://localhost:8090/)
- Upload CSV data file
1). Click the upload button to upload the CSV file.
[Test data](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/example.csv)
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/data.png)
2). Click the feature extraction button.
Wait for CSV file parsing, feature extraction, and feature storage into the vector engine. Progress information can be seen through the console.
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/storage.png)
- Text search
Enter the text, click the query, and you can see the returned list sorted by similarity.
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/search.png)
### 5. Help information
- swagger interface document:
http://localhost:8089/swagger-ui.html
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/video_search/swagger.png)
- Initialize the vector engine (clear data):
```bash
me.aias.tools.MilvusInit.java
...
```
- Milvus vector engine reference link
[Milvus vector engine official website](https://milvus.io/)
[Milvus vector engine Github](https://github.com/milvus-io)

View File

@ -1,186 +0,0 @@
## 目录:
http://aias.top/
### 下载模型放置于models目录
- 链接: https://pan.baidu.com/s/1HhJrKPLGHrxMo85280THqg?pwd=5644
## 问答系统
问答系统(Question Answering System, QA)是信息检索系统的一种高级形式,它能用准确、简洁的自然语言回答用户用自然语言提出的问题。其研究兴起的主要原因是人们对快速、准确地获取信息的需求。问答系统是人工智能和自然语言处理领域中一个倍受关注并具有广泛发展前景的研究方向。
### 文本搜索引擎
本例子基于文本搜索引擎支持上传csv文件使用句向量模型提取特征并基于milvus向量引擎进行后续检索。
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/arc.png)
#### 主要特性
- 底层使用特征向量相似度搜索
- 单台服务器十亿级数据的毫秒级搜索
- 近实时搜索,支持分布式部署
- 随时对数据进行插入、删除、搜索、更新等操作
### 句向量模型【支持15种语言】
句向量是指将语句映射至固定维度的实数向量。将不定长的句子用定长的向量表示为NLP下游任务提供服务。
支持 15 种语言:
Arabic, Chinese, Dutch, English, French, German, Italian, Korean, Polish, Portuguese, Russian, Spanish, Turkish.
- 句向量
![img](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/nlp_sdks/Universal-Sentence-Encoder.png)
句向量应用:
- 语义搜索/问答系统通过句向量相似性检索语料库中与query最匹配的文本
- 文本聚类,文本转为定长向量,通过聚类模型可无监督聚集相似文本
- 文本分类,表示成句向量,直接用简单分类器即训练文本分类器
### 1. 前端部署
#### 1.1 安装运行:
```bash
# 安装依赖包
npm install
# 运行
npm run dev
```
#### 1.2 构建dist安装包
```bash
npm run build:prod
```
#### 1.3 nginx部署运行(mac环境为例)
```bash
cd /usr/local/etc/nginx/
vi /usr/local/etc/nginx/nginx.conf
# 编辑nginx.conf
server {
listen 8080;
server_name localhost;
location / {
root /Users/calvin/Documents/qa_system/dist/;
index index.html index.htm;
}
......
# 重新加载配置:
sudo nginx -s reload
# 部署应用后,重启:
cd /usr/local/Cellar/nginx/1.19.6/bin
# 快速停止
sudo nginx -s stop
# 启动
sudo nginx
```
### 2. 后端jar部署
#### 2.1 环境要求:
- 系统JDK 1.8+
- application.yml
```bash
# 文件存储路径
file:
mac:
path: ~/file/
linux:
path: /home/aias/file/
windows:
path: file:/D:/aias/file/
# 文件大小 /M
maxSize: 3000
...
```
#### 2.2 运行程序:
```bash
# 运行程序
java -jar qa-system-0.1.0.jar
```
### 3. 后端向量引擎部署Milvus 2.0
#### 3.1 环境要求:
- 需要安装docker运行环境Mac环境可以使用Docker Desktop
#### 3.2 拉取Milvus向量引擎镜像用于计算特征值向量相似度
下载 milvus-standalone-docker-compose.yml 配置文件并保存为 docker-compose.yml
[单机版安装文档](https://milvus.io/docs/v2.0.0/install_standalone-docker.md)
```bash
wget https://github.com/milvus-io/milvus/releases/download/v2.0.0/milvus-standalone-docker-compose.yml -O docker-compose.yml
```
#### 3.3 启动 Docker 容器
```bash
sudo docker-compose up -d
```
#### 3.5 编辑向量引擎连接配置信息
- application.yml
- 根据需要编辑向量引擎连接ip地址127.0.0.1为容器所在的主机ip
```bash
################## 向量引擎 ################
search:
host: 127.0.0.1
port: 19530
```
### 4. 打开浏览器
- 输入地址: http://localhost:8090
- 上传CSV数据文件
1). 点击上传按钮上传CSV文件.
[测试数据](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/example.csv)
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/data.png)
2). 点击特征提取按钮.
等待CSV文件解析特征提取特征存入向量引擎。通过console可以看到进度信息。
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/storage.png)
- 文本搜索
输入文字,点击查询,可以看到返回的清单,根据相似度排序。
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/search.png)
### 5. 帮助信息
- swagger接口文档:
http://localhost:8089/swagger-ui.html
![Screenshot](https://aias-home.oss-cn-beijing.aliyuncs.com/AIAS/8_suite_hub/question_answering_system/swagger.png)
- 初始化向量引擎(清空数据):
me.aias.tools.MilvusInit.java
- Milvus向量引擎参考链接
[Milvus向量引擎官网](https://milvus.io/cn/docs/overview.md)
[Milvus向量引擎Github](https://github.com/milvus-io)
### 官网:
[官网链接](http://www.aias.top/)
### Git地址
[Github链接](https://github.com/mymagicpower/AIAS/blob/main/8_suite_hub/question_answering_system/README.md)
[Gitee链接](https://gitee.com/mymagicpower/AIAS/tree/main/8_suite_hub/question_answering_system)
#### 帮助文档:
- http://aias.top/guides.html
- 1.性能优化常见问题:
- http://aias.top/AIAS/guides/performance.html
- 2.引擎配置包括CPUGPU在线自动加载及本地配置:
- http://aias.top/AIAS/guides/engine_config.html
- 3.模型加载方式(在线自动加载,及本地配置):
- http://aias.top/AIAS/guides/load_model.html
- 4.Windows环境常见问题:
- http://aias.top/AIAS/guides/windows.html

View File

@ -1,14 +0,0 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -1,6 +0,0 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://127.0.0.1:8089'

View File

@ -1,6 +0,0 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = 'http://127.0.0.1:8089'

View File

@ -1,8 +0,0 @@
NODE_ENV = production
# just a flag
ENV = 'staging'
# base api
VUE_APP_BASE_API = '/stage-api'

View File

@ -1,4 +0,0 @@
build/*.js
src/assets
public
dist

View File

@ -1,198 +0,0 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

View File

@ -1,5 +0,0 @@
language: node_js
node_js: 10
script: npm run test
notifications:
email: false

View File

@ -1,14 +0,0 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}

View File

@ -1,35 +0,0 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

View File

@ -1,24 +0,0 @@
module.exports = {
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: {
'^.+\\.vue$': 'vue-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: ['jest-serializer-vue'],
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/tests/unit/coverage',
// 'collectCoverage': true,
'coverageReporters': [
'lcov',
'text-summary'
],
testURL: 'http://localhost/'
}

View File

@ -1,9 +0,0 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

View File

@ -1,65 +0,0 @@
{
"name": "ocr-ui",
"version": "1.0.0",
"description": "Train UI Demo",
"author": "Calvin <179209347@qq.com>",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"crypto-js": "^3.1.9-1",
"axios": "0.18.1",
"core-js": "3.21.0",
"easy-circular-progress": "1.0.4",
"echarts": "^4.2.1",
"element-ui": "2.13.2",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vertx3-eventbus-client": "^3.9.4",
"vue": "2.6.10",
"vue-count-to": "^1.0.13",
"vue-json-viewer": "^2.2.18",
"vue-router": "3.0.6",
"vuex": "3.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"mockjs": "1.0.1-beta3",
"runjs": "4.3.2",
"sass": "1.26.8",
"sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "1.13.2",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"license": "MIT"
}

View File

@ -1,8 +0,0 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
'plugins': {
// to edit target browsers: use "browserslist" field in package.json
'autoprefixer': {}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,11 +0,0 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>

View File

@ -1,61 +0,0 @@
import CryptoJS from 'crypto-js/crypto-js'
// 默认的 KEY 与 iv 如果没有给
const KEY = CryptoJS.enc.Utf8.parse("1234567890123456");
const IV = CryptoJS.enc.Utf8.parse('1234567890123456');
/**
* AES加密 字符串 key iv 返回base64
*/
export function encrypt(word, keyStr, ivStr) {
let key = KEY
let iv = IV
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
// console.log("-=-=-=-", encrypted.ciphertext)
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
/**
* AES 解密 字符串 key iv 返回base64
*
*/
export function decrypt(word, keyStr, ivStr) {
let key = KEY
let iv = IV
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let base64 = CryptoJS.enc.Base64.parse(word);
let src = CryptoJS.enc.Base64.stringify(base64);
var decrypt = CryptoJS.AES.decrypt(src, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
//判断字符是否为空的方法
export function isEmpty(obj){
if(typeof obj == "undefined" || obj == null || obj == ""){
return true;
}else{
return false;
}
}

View File

@ -1,46 +0,0 @@
import request from '@/utils/request'
export function getStorageList() {
return request({
url: 'api/localStorage/list',
method: 'get'
})
}
export function add(data) {
return request({
url: 'api/localStorage',
method: 'post',
data
})
}
export function del(id) {
return request({
url: 'api/localStorage/',
method: 'delete',
data: {
id: id
}
})
}
export function edit(data) {
return request({
url: 'api/localStorage',
method: 'put',
data
})
}
export function extract(id) {
return request({
url: 'api/text/extractFeatures',
method: 'get',
params: {
id: id
}
})
}
export default { getStorageList, add, edit, del, extract }

View File

@ -1,12 +0,0 @@
import request from '@/utils/request'
export function search(data) {
return request({
url: 'api/search/text',
method: 'get',
params: {
topK: data.topK,
text: data.text
}
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,55 +0,0 @@
// flex row
@mixin flex-row {
display: flex;
flex-direction: row;
}
@mixin flex-row-between {
@include flex-row();
justify-content: space-between;
}
@mixin flex-row-between-center {
@include flex-row-between();
align-items: center
}
@mixin flex-row-center {
@include flex-row();
justify-content: center
}
@mixin flex-row-all-center {
@include flex-row-center;
align-items: center
}
@mixin all-height($height) {
height: $height;
line-height: $height
}
@mixin ellipsis($width) {
width: $width;
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis
}
// flex column
@mixin flex-column {
display: flex;
flex-direction: column
}
@mixin flex-column-center {
@include flex-column();
justify-content: center
}
@mixin flex-column-all-center {
@include flex-column-center;
align-items: center
}

View File

@ -1,99 +0,0 @@
@import 'variables';
@mixin colorBtn($color) {
background: $color;
&:hover {
color: $color;
&:before,
&:after {
background: $color;
}
}
}
.blue-btn {
@include colorBtn($blue)
}
.light-blue-btn {
@include colorBtn($light-blue)
}
.red-btn {
@include colorBtn($red)
}
.pink-btn {
@include colorBtn($pink)
}
.green-btn {
@include colorBtn($green)
}
.tiffany-btn {
@include colorBtn($tiffany)
}
.yellow-btn {
@include colorBtn($yellow)
}
.pan-btn {
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border: none;
outline: none;
transition: 600ms ease all;
position: relative;
display: inline-block;
&:hover {
background: #fff;
&:before,
&:after {
width: 100%;
transition: 600ms ease all;
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
right: 0;
height: 2px;
width: 0;
transition: 400ms ease all;
}
&::after {
right: inherit;
top: inherit;
left: 0;
bottom: 0;
}
}
.custom-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
color: #fff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
}

View File

@ -1,117 +0,0 @@
.head-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin: 0 3px 10px 0;
input {
height: 30.5px;
line-height: 30.5px;
}
}
.el-form-item-label {
margin: 0 3px 9px 0;
display: inline-block;
text-align: right;
vertical-align: middle;
font-size: 14px;
color: #606266;
line-height: 30.5px;
padding: 0 7px 0 7px;
}
.el-button+.el-button {
margin-left: 0 !important;
}
.el-select__caret.el-input__icon.el-icon-arrow-up{
line-height: 30.5px;
}
.date-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
height: 30.5px !important;
width: 230px !important;
}
}
.el-avatar {
display: inline-block;
text-align: center;
background: #ccc;
color: #fff;
white-space: nowrap;
position: relative;
overflow: hidden;
vertical-align: middle;
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 16px;
}
.logo-con{
height: 60px;
padding: 13px 0 0;
img{
height: 32px;
width: 135px;
display: block;
//margin: 0 auto;
}
}
#el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial, serif;
font-size: 12px;
letter-spacing: 1px;
}
#el-main-footer {
background: none repeat scroll 0 0 white;
border-top: 1px solid #e7eaec;
overflow: hidden;
padding: 10px 6px 0 6px;
height: 33px;
font-size: 0.7rem !important;
color: #7a8b9a;
letter-spacing: 0.8px;
font-family: Arial, sans-serif !important;
position: fixed;
bottom: 0;
z-index: 99;
width: 100%;
}
.eladmin-upload {
border: 1px dashed #c0ccda;
border-radius: 5px;
height: 45px;
line-height: 45px;
width: 368px;
}
.my-blockquote{
margin: 0 0 10px;
padding: 15px;
line-height: 22px;
border-left: 5px solid #00437B;
border-radius: 0 2px 2px 0;
background-color: #f2f2f2;
}
.my-code{
position: relative;
padding: 15px;
line-height: 20px;
border-left: 5px solid #ddd;
color: #333;
font-family: Courier New, serif;
font-size: 12px
}
.el-tabs{
margin-bottom: 25px;
}

View File

@ -1,79 +0,0 @@
// cover some element-ui styles
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0;
}
}
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}

View File

@ -1,31 +0,0 @@
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**/
/* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #FFBA00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;
$--button-font-weight: 400;
// $--color-text-regular: #1f2d3d;
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
$--table-border:1px solid#dfe6ec;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "../../../node_modules/element-ui/packages/theme-chalk/src/index";
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
theme: $--color-primary;
}

Some files were not shown because too many files have changed in this diff Show More