可以参考配套的文档(作者的文档)
技术要点:类的继承,友元函数,自动补全,按键事件,listwidget,json数据解析,qlist,qmap,使用const宏定义,
类的继承:class
CubeAnnotationItem
:
public
AnnotationItem
友元函数:friend
AnnotationContainer;
宏定义:
//
for
image
result
const
QString
SUFFIX_SEG_COLOR("_segment_color.png");
const
QString
SUFFIX_SEG_LABELID("_segment_labelId.png");
const
QString
SUFFIX_SEG3D_COLOR("_segment3d_color.png");
const
QString
SUFFIX_SEG3D_LABELID("_segment3d_labelId.png");
/*
LabelDialog(选择label的一个对话框)
*
简介:
*
界面:ui->lineEdit,LabelLineEdit
类型,绑定了
ui->listWidget(详见
LabelLineEdit
的声明)
*
ui->listWidget,用于显示label,且有一个纯色的icon代表label对应的颜色
*/
/*
LabelLineEdit (自动补全,绑定QlistWidget,按键上下选中)
*
简介:用作LabelDialog的一个部件,可绑定一个QListWidget实现一些联动的效果
*
功能:a.
输入时根据
labelListWidget
的
items
的
text
提供自动补全
*
b.
当
labelListWidget
的选中项改变时,相应的改变输入框中的text
*
c.
按下
up和down
键,可改变
labelListWidget
的选中项。
*/
class LabelLineEdit: public QLineEdit{ friend LabelDialog; public: // labelListWidget 构造时默认为 nullptr explicit LabelLineEdit(QWidget *parent = nullptr); // 设置 labelListWidget,并实现功能 a 和 b (自动补全 和 相应list的选中) void setLabelListWidget(QListWidget* listWidget); protected: // 处理 up 和 down 键的按下事件,实现功能 c (改变list的选中项) void keyPressEvent(QKeyEvent *event); private: QListWidget* labelListWidget; }; |
LabelLineEdit::LabelLineEdit(QWidget *parent):QLineEdit(parent), labelListWidget(nullptr) { } void LabelLineEdit::setLabelListWidget(QListWidget *listWidget){ labelListWidget = listWidget; // 功能 a: 输入时根据 labelListWidget 的 items 的 text 提供自动补全 QCompleter* completer = new QCompleter(this); completer->setCompletionMode(QCompleter::InlineCompletion); completer->setModel(labelListWidget->model()); this->setCompleter(completer); // 功能 b: 当 labelListWidget 的选中项改变时,相应的改变输入框中的text connect(labelListWidget, &QListWidget::currentItemChanged, [this](QListWidgetItem *currentItem){ this->setText(currentItem->text()); }); } void LabelLineEdit::keyPressEvent(QKeyEvent *event){ // 功能 c: 按下 up和down 键,可改变 labelListWidget 的选中项 if (event->key() == Qt::Key_Up){ if (labelListWidget!=nullptr && labelListWidget->count()>0){ int newRow = std::max(0, std::min(labelListWidget->currentRow() - 1, labelListWidget->count()-1)); labelListWidget->setCurrentRow(newRow); this->setText(labelListWidget->currentItem()->text()); } }else if (event->key() == Qt::Key_Down){ if (labelListWidget!=nullptr && labelListWidget->count()>0){ int newRow = std::max(0, std::min(labelListWidget->currentRow() + 1, labelListWidget->count()-1)); labelListWidget->setCurrentRow(newRow); this->setText(labelListWidget->currentItem()->text()); } }else{ QLineEdit::keyPressEvent(event); } } |
/*
LabelManager -LabelProperty(json格式存储label数据,解析json数据)
*
简介:用来存储label的相关性质的数据类型,存储了label、color、visible、id
*
Json:该类的数据与json相互转化时的格式为
*
{
*
"color":
[
*
Double,
//
R
*
Double,
//
G
*
Double
//
B
*
],
*
"id":
Double,
*
"label":
String,
*
"visible":
Bool
*
}
//---------------------------------LabelProperty-------------------------------------// LabelProperty::LabelProperty(QString label, QColor color, bool visible, int id) : label(label), color(color), visible(visible),id(id) { } LabelProperty::LabelProperty():label(), color(), visible(true), id(-1) { } QJsonObject LabelProperty::toJsonObject(){ QJsonArray colorJson; colorJson.append(color.red()); colorJson.append(color.green()); colorJson.append(color.blue()); QJsonObject json; json.insert("label", label); json.insert("id", id); json.insert("color", colorJson); json.insert("visible", visible); return json; } void LabelProperty::fromJsonObject(QJsonObject json) { if (json.contains("label")){ QJsonValue value = json.value("label"); if (value.isString()){ label = value.toString(); }else{ throw JsonException("value of <label> is illegal"); } }else{ throw JsonException("no data <label>"); } if (json.contains("color")){ QJsonValue value = json.value("color"); if (value.isArray()){ QJsonArray array = value.toArray(); if (!array.at(0).isDouble() || !array.at(1).isDouble() || !array.at(2).isDouble()){ throw JsonException("value of <color> is illegal"); } int r=static_cast<int>(array.at(0).toDouble()); int g=static_cast<int>(array.at(1).toDouble()); int b=static_cast<int>(array.at(2).toDouble()); color = QColor(r,g,b); }else{ throw JsonException("value of <color> is illegal"); } }else{ throw JsonException("no data <color>"); } if (json.contains("visible")){ QJsonValue value = json.value("visible"); if (value.isBool()){ visible = value.toBool(); }else{ throw JsonException("value of <visible> is illegal"); } }else{ throw JsonException("no data <visible>"); } if (json.contains("id")){ QJsonValue value = json.value("id"); if (value.isDouble()){ id = static_cast<int>(value.toDouble()); }else{ throw JsonException("value of <id> is illegal"); } }else{ throw JsonException("no data <id>"); } } |
/*
LabelManager(增加删除修改查询label,)
*
简介:管理label相关的数据的类,主要与ui->labelListWidget同步
*
注意:在addLabel前应检查是否已经存在该label
*
*
Json:该类的数据与json相互转化时的格式为
*
[
LabelProperty,
LabelProperty,
...
]
//
Array中的元素的格式为LabelProperty的格式
*/
使用map存储labels;
QMap<QString,
LabelProperty>
labels;
/*
CustomListWidget
*
简介:作为MainWindow中的部件,根据所需提供了更方便的函数接口以及事件响应
*
是MainWindow中
annoListWidget、labelListWidget、fileListWidget的类型
*
界面:每个
item
从左至右组成为:一个用于check的小框(可有可无),一个icon(可有可无),以及text
*
事件:鼠标若点击到listwidget空白区域,即没有item的区域,则清除list的所有选中状态
*
键盘按下
up/down
键改变list的选中项
*
注意:a.
该类应为单选模式(SingleSelection),且不应该被更改;
*
b.
该类不应出现不同的item的text相同的情况,
*
这种情况在该项目中是不会发生的,因为:
*
1.
annoList中每个label相同的标注被一个各不相同的instance
id标识
*
2.
fileList中不会出现文件重名的情况
*
3.
labelList在加入item时会根据labelManager检查label是否已经存在
*/
Listwidget中的右键菜单
void MainWindow::provideLabelContextMenu(const QPoint &pos) { QPoint globalPos = ui->labelListWidget->mapToGlobal(pos); QModelIndex modelIdx = ui->labelListWidget->indexAt(pos); if (!modelIdx.isValid()) return; int row = modelIdx.row(); auto item = ui->labelListWidget->item(row); QMenu submenu; submenu.addAction("Change Color"); submenu.addAction("Delete"); QAction* rightClickItem = submenu.exec(globalPos); if (rightClickItem){ if (rightClickItem->text().contains("Delete")){ removeLabelRequest(item->text()); }else if (rightClickItem->text().contains("Change Color")){ QColor color = QColorDialog::getColor(Qt::white, this, "Choose Color"); if (color.isValid()){ labelManager.setColor(item->text(),color); } } } } |
/*
FileManager(静态函数方便调用,)
*
简介:管理与文件相关的状态的类型
*
功能:a.
实现静态函数作为更便捷的处理文件名、读写文件的接口
*
b.
打开文件或文件夹,生成默认的输出文件名(包括label和annotation的输出文件)
*
c.
记录距离上次保存后是否有未保存的修改
*
d.
可选中文件,切换显示的图像,并与
ui->fileListWidget
同步
*/
/*
RectAnnotationItem(绘制矩形,标记用json格式存储)
*
简介:用于2D
Detection的标注类型,比父类多记录了一个像素坐标的矩形
rect
表示bounding
box
*
*
Json:该类的数据与json相互转化时的格式如下,points表示两个矩形的左上和右下顶点
*
{
*
"label":
String
*
"id":
Double
*
"points":
[
*
[
*
Double,
*
Double
*
],
*
[
*
Double,
*
Double
*
]
*
]
*
}
*/
/*
SegStroke(绘制多边形)
*
简介:用于表示2D分割标注中一
"笔画"
的数据类型
*
笔画:笔画共分为三种,分别为圆形画笔、方形画笔、轮廓
*
对应的type的值分别为
"circle_pen"
"square_pen"
"contour"
*
当type为画笔时,points中记录的是画笔中心经过路径,penWidth记录画笔的大小;
*
当type为轮廓时,points依次记录轮廓上的点,并用多边形连接它们,特别地,当点比较稠密时,可视为光滑闭合曲线
*
当type为轮廓时,penWidth无意义,一般记为1,也可不记
*
*
Json:该类的数据与json相互转化时的格式如下,
*
{
*
"type":
String
//
取值为
"circle_pen"
或
"square_pen"
或
"contour"
*
"penWidth":
Double
*
"points":
[
[Double,
Double],
[Double,
Double],
...
]
//Array类型,表示xy坐标
*
}
*/
/*
Basic_SegAnnotationItem
*
简介:用于表示Segmentation标注的类模板,该项目中认为一个分割标注可由多个笔画(stroke_type类型)合并组成
*
对应2D和3D分割标注,stroke_type分别为SegStroke和SegStroke3D
*
*
Json:该类的数据与json相互转化时的格式如下
*
{
*
"label":
String
*
"id":
Double
*
"strokes":
[
stroke_type,
stroke_type,
...
]
//
Array的元素的格式为SegStroke或SegStroke3D对应的格式
*
}
*/
Common(一些基本的功能,多个命名空间)
|