Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
mmdetection3d
Commits
f0e332bd
"git@developer.sourcefind.cn:OpenDAS/mmdetection3d.git" did not exist on "57e470abe22973a17b0610bd260105e7f6c8da0d"
Commit
f0e332bd
authored
May 14, 2020
by
liyinhao
Browse files
change name, delete data_path, change the order of the code
parent
90e3b50e
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
82 additions
and
85 deletions
+82
-85
mmdet3d/core/evaluation/indoor_eval.py
mmdet3d/core/evaluation/indoor_eval.py
+39
-39
mmdet3d/datasets/__init__.py
mmdet3d/datasets/__init__.py
+4
-4
mmdet3d/datasets/indoor_base_dataset.py
mmdet3d/datasets/indoor_base_dataset.py
+25
-25
mmdet3d/datasets/scannet_dataset.py
mmdet3d/datasets/scannet_dataset.py
+4
-6
mmdet3d/datasets/sunrgbd_dataset.py
mmdet3d/datasets/sunrgbd_dataset.py
+2
-3
tests/test_scannet_dataset.py
tests/test_scannet_dataset.py
+4
-4
tests/test_sunrgbd_dataset.py
tests/test_sunrgbd_dataset.py
+4
-4
No files found.
mmdet3d/core/evaluation/indoor_eval.py
View file @
f0e332bd
...
@@ -4,6 +4,45 @@ import torch
...
@@ -4,6 +4,45 @@ import torch
from
mmdet3d.core.bbox.iou_calculators.iou3d_calculator
import
bbox_overlaps_3d
from
mmdet3d.core.bbox.iou_calculators.iou3d_calculator
import
bbox_overlaps_3d
def
boxes3d_depth_to_lidar
(
boxes3d
,
mid_to_bottom
=
True
):
""" Boxes3d Depth to Lidar.
Flip X-right,Y-forward,Z-up to X-forward,Y-left,Z-up.
Args:
boxes3d (ndarray): (N, 7) [x, y, z, w, l, h, r] in depth coords.
Return:
boxes3d_lidar (ndarray): (N, 7) [x, y, z, l, h, w, r] in LiDAR coords.
"""
boxes3d_lidar
=
boxes3d
.
copy
()
boxes3d_lidar
[...,
[
0
,
1
,
2
,
3
,
4
,
5
]]
=
boxes3d_lidar
[...,
[
1
,
0
,
2
,
4
,
3
,
5
]]
boxes3d_lidar
[...,
1
]
*=
-
1
if
mid_to_bottom
:
boxes3d_lidar
[...,
2
]
-=
boxes3d_lidar
[...,
5
]
/
2
return
boxes3d_lidar
def
get_iou_gpu
(
bb1
,
bb2
):
"""Get IoU.
Compute IoU of two bounding boxes.
Args:
bb1 (ndarray): [x, y, z, w, l, h, ry] in LiDAR.
bb2 (ndarray): [x, y, z, h, w, l, ry] in LiDAR.
Returns:
ans_iou (tensor): The answer of IoU.
"""
bb1
=
torch
.
from_numpy
(
bb1
).
float
().
cuda
()
bb2
=
torch
.
from_numpy
(
bb2
).
float
().
cuda
()
iou3d
=
bbox_overlaps_3d
(
bb1
,
bb2
,
mode
=
'iou'
,
coordinate
=
'lidar'
)
return
iou3d
.
cpu
().
numpy
()
def
average_precision
(
recalls
,
precisions
,
mode
=
'area'
):
def
average_precision
(
recalls
,
precisions
,
mode
=
'area'
):
"""Calculate average precision (for single or multiple scales).
"""Calculate average precision (for single or multiple scales).
...
@@ -51,25 +90,6 @@ def average_precision(recalls, precisions, mode='area'):
...
@@ -51,25 +90,6 @@ def average_precision(recalls, precisions, mode='area'):
return
ap
return
ap
def
get_iou_gpu
(
bb1
,
bb2
):
"""Get IoU.
Compute IoU of two bounding boxes.
Args:
bb1 (ndarray): [x, y, z, w, l, h, ry] in LiDAR.
bb2 (ndarray): [x, y, z, h, w, l, ry] in LiDAR.
Returns:
ans_iou (tensor): The answer of IoU.
"""
bb1
=
torch
.
from_numpy
(
bb1
).
float
().
cuda
()
bb2
=
torch
.
from_numpy
(
bb2
).
float
().
cuda
()
iou3d
=
bbox_overlaps_3d
(
bb1
,
bb2
,
mode
=
'iou'
,
coordinate
=
'lidar'
)
return
iou3d
.
cpu
().
numpy
()
def
eval_det_cls
(
pred
,
gt
,
ovthresh
=
None
):
def
eval_det_cls
(
pred
,
gt
,
ovthresh
=
None
):
"""Generic functions to compute precision/recall for object detection
"""Generic functions to compute precision/recall for object detection
for a single class.
for a single class.
...
@@ -261,26 +281,6 @@ def eval_map_rec(det_infos, gt_infos, ovthresh=None):
...
@@ -261,26 +281,6 @@ def eval_map_rec(det_infos, gt_infos, ovthresh=None):
return
rec
,
prec
,
ap
return
rec
,
prec
,
ap
def
boxes3d_depth_to_lidar
(
boxes3d
,
mid_to_bottom
=
True
):
""" Boxes3d Depth to Lidar.
Flip X-right,Y-forward,Z-up to X-forward,Y-left,Z-up.
Args:
boxes3d (ndarray): (N, 7) [x, y, z, w, l, h, r] in depth coords.
Return:
boxes3d_lidar (ndarray): (N, 7) [x, y, z, l, h, w, r] in LiDAR coords.
"""
boxes3d_lidar
=
boxes3d
.
copy
()
boxes3d_lidar
[...,
[
0
,
1
,
2
,
3
,
4
,
5
]]
=
boxes3d_lidar
[...,
[
1
,
0
,
2
,
4
,
3
,
5
]]
boxes3d_lidar
[...,
1
]
*=
-
1
if
mid_to_bottom
:
boxes3d_lidar
[...,
2
]
-=
boxes3d_lidar
[...,
5
]
/
2
return
boxes3d_lidar
def
indoor_eval
(
gt_annos
,
dt_annos
,
metric
,
label2cat
):
def
indoor_eval
(
gt_annos
,
dt_annos
,
metric
,
label2cat
):
"""Scannet Evaluation.
"""Scannet Evaluation.
...
...
mmdet3d/datasets/__init__.py
View file @
f0e332bd
...
@@ -12,8 +12,8 @@ from .pipelines import (GlobalRotScale, IndoorFlipData, IndoorGlobalRotScale,
...
@@ -12,8 +12,8 @@ from .pipelines import (GlobalRotScale, IndoorFlipData, IndoorGlobalRotScale,
IndoorPointsColorNormalize
,
ObjectNoise
,
IndoorPointsColorNormalize
,
ObjectNoise
,
ObjectRangeFilter
,
ObjectSample
,
PointShuffle
,
ObjectRangeFilter
,
ObjectSample
,
PointShuffle
,
PointsRangeFilter
,
RandomFlip3D
)
PointsRangeFilter
,
RandomFlip3D
)
from
.scannet_dataset
import
Scan
n
et
Base
Dataset
from
.scannet_dataset
import
Scan
N
etDataset
from
.sunrgbd_dataset
import
S
unrgbdBase
Dataset
from
.sunrgbd_dataset
import
S
UNRGBD
Dataset
__all__
=
[
__all__
=
[
'KittiDataset'
,
'GroupSampler'
,
'DistributedGroupSampler'
,
'KittiDataset'
,
'GroupSampler'
,
'DistributedGroupSampler'
,
...
@@ -23,6 +23,6 @@ __all__ = [
...
@@ -23,6 +23,6 @@ __all__ = [
'ObjectRangeFilter'
,
'PointsRangeFilter'
,
'Collect3D'
,
'ObjectRangeFilter'
,
'PointsRangeFilter'
,
'Collect3D'
,
'IndoorLoadPointsFromFile'
,
'IndoorPointsColorNormalize'
,
'IndoorLoadPointsFromFile'
,
'IndoorPointsColorNormalize'
,
'IndoorPointSample'
,
'IndoorLoadAnnotations3D'
,
'IndoorPointsColorJitter'
,
'IndoorPointSample'
,
'IndoorLoadAnnotations3D'
,
'IndoorPointsColorJitter'
,
'IndoorGlobalRotScale'
,
'IndoorFlipData'
,
'S
unrgbdBase
Dataset'
,
'IndoorGlobalRotScale'
,
'IndoorFlipData'
,
'S
UNRGBD
Dataset'
,
'Scan
n
et
Base
Dataset'
,
'IndoorBaseDataset'
'Scan
N
etDataset'
,
'IndoorBaseDataset'
]
]
mmdet3d/datasets/indoor_base_dataset.py
View file @
f0e332bd
...
@@ -32,6 +32,27 @@ class IndoorBaseDataset(torch_data.Dataset):
...
@@ -32,6 +32,27 @@ class IndoorBaseDataset(torch_data.Dataset):
self
.
pipeline
=
Compose
(
pipeline
)
self
.
pipeline
=
Compose
(
pipeline
)
self
.
with_label
=
with_label
self
.
with_label
=
with_label
def
__len__
(
self
):
return
len
(
self
.
data_infos
)
def
get_data_info
(
self
,
index
):
info
=
self
.
data_infos
[
index
]
sample_idx
=
info
[
'point_cloud'
][
'lidar_idx'
]
pts_filename
=
self
.
_get_pts_filename
(
sample_idx
)
input_dict
=
dict
(
pts_filename
=
pts_filename
)
if
self
.
with_label
:
annos
=
self
.
_get_ann_info
(
index
,
sample_idx
)
input_dict
.
update
(
annos
)
if
len
(
input_dict
[
'gt_bboxes_3d'
])
==
0
:
return
None
return
input_dict
def
_rand_another
(
self
,
idx
):
pool
=
np
.
where
(
self
.
flag
==
self
.
flag
[
idx
])[
0
]
return
np
.
random
.
choice
(
pool
)
def
__getitem__
(
self
,
idx
):
def
__getitem__
(
self
,
idx
):
if
self
.
test_mode
:
if
self
.
test_mode
:
return
self
.
prepare_test_data
(
idx
)
return
self
.
prepare_test_data
(
idx
)
...
@@ -42,11 +63,6 @@ class IndoorBaseDataset(torch_data.Dataset):
...
@@ -42,11 +63,6 @@ class IndoorBaseDataset(torch_data.Dataset):
continue
continue
return
data
return
data
def
prepare_test_data
(
self
,
index
):
input_dict
=
self
.
get_data_info
(
index
)
example
=
self
.
pipeline
(
input_dict
)
return
example
def
prepare_train_data
(
self
,
index
):
def
prepare_train_data
(
self
,
index
):
input_dict
=
self
.
get_data_info
(
index
)
input_dict
=
self
.
get_data_info
(
index
)
if
input_dict
is
None
:
if
input_dict
is
None
:
...
@@ -56,23 +72,10 @@ class IndoorBaseDataset(torch_data.Dataset):
...
@@ -56,23 +72,10 @@ class IndoorBaseDataset(torch_data.Dataset):
return
None
return
None
return
example
return
example
def
get_data_info
(
self
,
index
):
def
prepare_test_data
(
self
,
index
):
info
=
self
.
data_infos
[
index
]
input_dict
=
self
.
get_data_info
(
index
)
sample_idx
=
info
[
'point_cloud'
][
'lidar_idx'
]
example
=
self
.
pipeline
(
input_dict
)
pts_filename
=
self
.
_get_pts_filename
(
sample_idx
)
return
example
input_dict
=
dict
(
pts_filename
=
pts_filename
)
if
self
.
with_label
:
annos
=
self
.
_get_ann_info
(
index
,
sample_idx
)
input_dict
.
update
(
annos
)
if
len
(
input_dict
[
'gt_bboxes_3d'
])
==
0
:
return
None
return
input_dict
def
_rand_another
(
self
,
idx
):
pool
=
np
.
where
(
self
.
flag
==
self
.
flag
[
idx
])[
0
]
return
np
.
random
.
choice
(
pool
)
def
_generate_annotations
(
self
,
output
):
def
_generate_annotations
(
self
,
output
):
"""Generate Annotations.
"""Generate Annotations.
...
@@ -126,6 +129,3 @@ class IndoorBaseDataset(torch_data.Dataset):
...
@@ -126,6 +129,3 @@ class IndoorBaseDataset(torch_data.Dataset):
gt_annos
=
[
copy
.
deepcopy
(
info
[
'annos'
])
for
info
in
self
.
data_infos
]
gt_annos
=
[
copy
.
deepcopy
(
info
[
'annos'
])
for
info
in
self
.
data_infos
]
ret_dict
=
indoor_eval
(
gt_annos
,
results
,
metric
,
self
.
label2cat
)
ret_dict
=
indoor_eval
(
gt_annos
,
results
,
metric
,
self
.
label2cat
)
return
ret_dict
return
ret_dict
def
__len__
(
self
):
return
len
(
self
.
data_infos
)
mmdet3d/datasets/scannet_dataset.py
View file @
f0e332bd
...
@@ -7,7 +7,7 @@ from .indoor_base_dataset import IndoorBaseDataset
...
@@ -7,7 +7,7 @@ from .indoor_base_dataset import IndoorBaseDataset
@
DATASETS
.
register_module
()
@
DATASETS
.
register_module
()
class
Scan
n
et
Base
Dataset
(
IndoorBaseDataset
):
class
Scan
N
etDataset
(
IndoorBaseDataset
):
CLASSES
=
(
'cabinet'
,
'bed'
,
'chair'
,
'sofa'
,
'table'
,
'door'
,
'window'
,
CLASSES
=
(
'cabinet'
,
'bed'
,
'chair'
,
'sofa'
,
'table'
,
'door'
,
'window'
,
'bookshelf'
,
'picture'
,
'counter'
,
'desk'
,
'curtain'
,
'bookshelf'
,
'picture'
,
'counter'
,
'desk'
,
'curtain'
,
...
@@ -24,10 +24,8 @@ class ScannetBaseDataset(IndoorBaseDataset):
...
@@ -24,10 +24,8 @@ class ScannetBaseDataset(IndoorBaseDataset):
super
().
__init__
(
root_path
,
ann_file
,
pipeline
,
classes
,
test_mode
,
super
().
__init__
(
root_path
,
ann_file
,
pipeline
,
classes
,
test_mode
,
with_label
)
with_label
)
self
.
data_path
=
osp
.
join
(
root_path
,
'scannet_train_instance_data'
)
def
_get_pts_filename
(
self
,
sample_idx
):
def
_get_pts_filename
(
self
,
sample_idx
):
pts_filename
=
osp
.
join
(
self
.
data
_path
,
f
'
{
sample_idx
}
_vert.npy'
)
pts_filename
=
osp
.
join
(
self
.
root
_path
,
f
'
{
sample_idx
}
_vert.npy'
)
return
pts_filename
return
pts_filename
def
_get_ann_info
(
self
,
index
,
sample_idx
):
def
_get_ann_info
(
self
,
index
,
sample_idx
):
...
@@ -41,9 +39,9 @@ class ScannetBaseDataset(IndoorBaseDataset):
...
@@ -41,9 +39,9 @@ class ScannetBaseDataset(IndoorBaseDataset):
gt_bboxes_3d
=
np
.
zeros
((
1
,
6
),
dtype
=
np
.
float32
)
gt_bboxes_3d
=
np
.
zeros
((
1
,
6
),
dtype
=
np
.
float32
)
gt_labels
=
np
.
zeros
(
1
,
).
astype
(
np
.
bool
)
gt_labels
=
np
.
zeros
(
1
,
).
astype
(
np
.
bool
)
gt_bboxes_3d_mask
=
np
.
zeros
(
1
,
).
astype
(
np
.
bool
)
gt_bboxes_3d_mask
=
np
.
zeros
(
1
,
).
astype
(
np
.
bool
)
pts_instance_mask_path
=
osp
.
join
(
self
.
data
_path
,
pts_instance_mask_path
=
osp
.
join
(
self
.
root
_path
,
f
'
{
sample_idx
}
_ins_label.npy'
)
f
'
{
sample_idx
}
_ins_label.npy'
)
pts_semantic_mask_path
=
osp
.
join
(
self
.
data
_path
,
pts_semantic_mask_path
=
osp
.
join
(
self
.
root
_path
,
f
'
{
sample_idx
}
_sem_label.npy'
)
f
'
{
sample_idx
}
_sem_label.npy'
)
anns_results
=
dict
(
anns_results
=
dict
(
...
...
mmdet3d/datasets/sunrgbd_dataset.py
View file @
f0e332bd
...
@@ -7,7 +7,7 @@ from .indoor_base_dataset import IndoorBaseDataset
...
@@ -7,7 +7,7 @@ from .indoor_base_dataset import IndoorBaseDataset
@
DATASETS
.
register_module
()
@
DATASETS
.
register_module
()
class
S
unrgbdBase
Dataset
(
IndoorBaseDataset
):
class
S
UNRGBD
Dataset
(
IndoorBaseDataset
):
CLASSES
=
(
'bed'
,
'table'
,
'sofa'
,
'chair'
,
'toilet'
,
'desk'
,
'dresser'
,
CLASSES
=
(
'bed'
,
'table'
,
'sofa'
,
'chair'
,
'toilet'
,
'desk'
,
'dresser'
,
'night_stand'
,
'bookshelf'
,
'bathtub'
)
'night_stand'
,
'bookshelf'
,
'bathtub'
)
...
@@ -21,10 +21,9 @@ class SunrgbdBaseDataset(IndoorBaseDataset):
...
@@ -21,10 +21,9 @@ class SunrgbdBaseDataset(IndoorBaseDataset):
with_label
=
True
):
with_label
=
True
):
super
().
__init__
(
root_path
,
ann_file
,
pipeline
,
classes
,
test_mode
,
super
().
__init__
(
root_path
,
ann_file
,
pipeline
,
classes
,
test_mode
,
with_label
)
with_label
)
self
.
data_path
=
osp
.
join
(
root_path
,
'sunrgbd_trainval'
)
def
_get_pts_filename
(
self
,
sample_idx
):
def
_get_pts_filename
(
self
,
sample_idx
):
pts_filename
=
osp
.
join
(
self
.
data
_path
,
'lidar'
,
pts_filename
=
osp
.
join
(
self
.
root
_path
,
'lidar'
,
f
'
{
sample_idx
:
06
d
}
.npy'
)
f
'
{
sample_idx
:
06
d
}
.npy'
)
return
pts_filename
return
pts_filename
...
...
tests/test_scannet_dataset.py
View file @
f0e332bd
...
@@ -2,12 +2,12 @@ import numpy as np
...
@@ -2,12 +2,12 @@ import numpy as np
import
pytest
import
pytest
import
torch
import
torch
from
mmdet3d.datasets
import
Scan
n
et
Base
Dataset
from
mmdet3d.datasets
import
Scan
N
etDataset
def
test_getitem
():
def
test_getitem
():
np
.
random
.
seed
(
0
)
np
.
random
.
seed
(
0
)
root_path
=
'./tests/data/scannet'
root_path
=
'./tests/data/scannet
/scannet_train_instance_data
'
ann_file
=
'./tests/data/scannet/scannet_infos.pkl'
ann_file
=
'./tests/data/scannet/scannet_infos.pkl'
class_names
=
(
'cabinet'
,
'bed'
,
'chair'
,
'sofa'
,
'table'
,
'door'
,
class_names
=
(
'cabinet'
,
'bed'
,
'chair'
,
'sofa'
,
'table'
,
'door'
,
'window'
,
'bookshelf'
,
'picture'
,
'counter'
,
'desk'
,
'window'
,
'bookshelf'
,
'picture'
,
'counter'
,
'desk'
,
...
@@ -36,7 +36,7 @@ def test_getitem():
...
@@ -36,7 +36,7 @@ def test_getitem():
]),
]),
]
]
scannet_dataset
=
Scan
n
et
Base
Dataset
(
root_path
,
ann_file
,
pipelines
)
scannet_dataset
=
Scan
N
etDataset
(
root_path
,
ann_file
,
pipelines
)
data
=
scannet_dataset
[
0
]
data
=
scannet_dataset
[
0
]
points
=
data
[
'points'
].
_data
points
=
data
[
'points'
].
_data
gt_bboxes_3d
=
data
[
'gt_bboxes_3d'
].
_data
gt_bboxes_3d
=
data
[
'gt_bboxes_3d'
].
_data
...
@@ -77,7 +77,7 @@ def test_evaluate():
...
@@ -77,7 +77,7 @@ def test_evaluate():
pytest
.
skip
()
pytest
.
skip
()
root_path
=
'./tests/data/scannet'
root_path
=
'./tests/data/scannet'
ann_file
=
'./tests/data/scannet/scannet_infos.pkl'
ann_file
=
'./tests/data/scannet/scannet_infos.pkl'
scannet_dataset
=
Scan
n
et
Base
Dataset
(
root_path
,
ann_file
)
scannet_dataset
=
Scan
N
etDataset
(
root_path
,
ann_file
)
results
=
[]
results
=
[]
pred_boxes
=
dict
()
pred_boxes
=
dict
()
pred_boxes
[
'box3d_lidar'
]
=
np
.
array
([[
pred_boxes
[
'box3d_lidar'
]
=
np
.
array
([[
...
...
tests/test_sunrgbd_dataset.py
View file @
f0e332bd
...
@@ -2,12 +2,12 @@ import numpy as np
...
@@ -2,12 +2,12 @@ import numpy as np
import
pytest
import
pytest
import
torch
import
torch
from
mmdet3d.datasets
import
S
unrgbdBase
Dataset
from
mmdet3d.datasets
import
S
UNRGBD
Dataset
def
test_getitem
():
def
test_getitem
():
np
.
random
.
seed
(
0
)
np
.
random
.
seed
(
0
)
root_path
=
'./tests/data/sunrgbd'
root_path
=
'./tests/data/sunrgbd
/sunrgbd_trainval
'
ann_file
=
'./tests/data/sunrgbd/sunrgbd_infos.pkl'
ann_file
=
'./tests/data/sunrgbd/sunrgbd_infos.pkl'
class_names
=
(
'bed'
,
'table'
,
'sofa'
,
'chair'
,
'toilet'
,
'desk'
,
class_names
=
(
'bed'
,
'table'
,
'sofa'
,
'chair'
,
'toilet'
,
'desk'
,
'dresser'
,
'night_stand'
,
'bookshelf'
,
'bathtub'
)
'dresser'
,
'night_stand'
,
'bookshelf'
,
'bathtub'
)
...
@@ -28,7 +28,7 @@ def test_getitem():
...
@@ -28,7 +28,7 @@ def test_getitem():
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
,
'gt_bboxes_3d'
,
'gt_labels'
]),
dict
(
type
=
'Collect3D'
,
keys
=
[
'points'
,
'gt_bboxes_3d'
,
'gt_labels'
]),
]
]
sunrgbd_dataset
=
S
unrgbdBase
Dataset
(
root_path
,
ann_file
,
pipelines
)
sunrgbd_dataset
=
S
UNRGBD
Dataset
(
root_path
,
ann_file
,
pipelines
)
data
=
sunrgbd_dataset
[
0
]
data
=
sunrgbd_dataset
[
0
]
points
=
data
[
'points'
].
_data
points
=
data
[
'points'
].
_data
gt_bboxes_3d
=
data
[
'gt_bboxes_3d'
].
_data
gt_bboxes_3d
=
data
[
'gt_bboxes_3d'
].
_data
...
@@ -67,7 +67,7 @@ def test_evaluate():
...
@@ -67,7 +67,7 @@ def test_evaluate():
pytest
.
skip
()
pytest
.
skip
()
root_path
=
'./tests/data/sunrgbd'
root_path
=
'./tests/data/sunrgbd'
ann_file
=
'./tests/data/sunrgbd/sunrgbd_infos.pkl'
ann_file
=
'./tests/data/sunrgbd/sunrgbd_infos.pkl'
sunrgbd_dataset
=
S
unrgbdBase
Dataset
(
root_path
,
ann_file
)
sunrgbd_dataset
=
S
UNRGBD
Dataset
(
root_path
,
ann_file
)
results
=
[]
results
=
[]
pred_boxes
=
dict
()
pred_boxes
=
dict
()
pred_boxes
[
'box3d_lidar'
]
=
np
.
array
([[
pred_boxes
[
'box3d_lidar'
]
=
np
.
array
([[
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment