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
wangsen
MinerU
Commits
516f4926
"pytorch/git@developer.sourcefind.cn:OpenDAS/fastmoe.git" did not exist on "0ab605cc11dd48d77a7aa8988ebfd2f51eba7a84"
Unverified
Commit
516f4926
authored
Jul 04, 2025
by
Xiaomeng Zhao
Committed by
GitHub
Jul 04, 2025
Browse files
Merge pull request #2873 from myhloli/dev
Dev
parents
6a242ada
7d8f68cb
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
143 additions
and
82 deletions
+143
-82
mineru/backend/pipeline/model_json_to_middle_json.py
mineru/backend/pipeline/model_json_to_middle_json.py
+0
-3
mineru/backend/pipeline/pipeline_magic_model.py
mineru/backend/pipeline/pipeline_magic_model.py
+98
-38
mineru/cli/fast_api.py
mineru/cli/fast_api.py
+9
-9
mineru/utils/block_pre_proc.py
mineru/utils/block_pre_proc.py
+35
-31
pyproject.toml
pyproject.toml
+1
-1
No files found.
mineru/backend/pipeline/model_json_to_middle_json.py
View file @
516f4926
...
@@ -152,9 +152,6 @@ def page_model_info_to_page_info(page_model_info, image_dict, page, image_writer
...
@@ -152,9 +152,6 @@ def page_model_info_to_page_info(page_model_info, image_dict, page, image_writer
"""对block进行fix操作"""
"""对block进行fix操作"""
fix_blocks
=
fix_block_spans
(
block_with_spans
)
fix_blocks
=
fix_block_spans
(
block_with_spans
)
"""同一行被断开的titile合并"""
# merge_title_blocks(fix_blocks)
"""对block进行排序"""
"""对block进行排序"""
sorted_blocks
=
sort_blocks_by_bbox
(
fix_blocks
,
page_w
,
page_h
,
footnote_blocks
)
sorted_blocks
=
sort_blocks_by_bbox
(
fix_blocks
,
page_w
,
page_h
,
footnote_blocks
)
...
...
mineru/backend/pipeline/pipeline_magic_model.py
View file @
516f4926
from
mineru.utils.boxbase
import
bbox_relative_pos
,
calculate_iou
,
bbox_distance
,
is_in
from
mineru.utils.boxbase
import
bbox_relative_pos
,
calculate_iou
,
bbox_distance
,
is_in
,
get_minbox_if_overlap_by_ratio
from
mineru.utils.enum_class
import
CategoryId
,
ContentType
from
mineru.utils.enum_class
import
CategoryId
,
ContentType
...
@@ -13,7 +13,62 @@ class MagicModel:
...
@@ -13,7 +13,62 @@ class MagicModel:
self
.
__fix_by_remove_low_confidence
()
self
.
__fix_by_remove_low_confidence
()
"""删除高iou(>0.9)数据中置信度较低的那个"""
"""删除高iou(>0.9)数据中置信度较低的那个"""
self
.
__fix_by_remove_high_iou_and_low_confidence
()
self
.
__fix_by_remove_high_iou_and_low_confidence
()
"""将部分tbale_footnote修正为image_footnote"""
self
.
__fix_footnote
()
self
.
__fix_footnote
()
"""处理重叠的image_body和table_body"""
self
.
__fix_by_remove_overlap_image_table_body
()
def
__fix_by_remove_overlap_image_table_body
(
self
):
need_remove_list
=
[]
layout_dets
=
self
.
__page_model_info
[
'layout_dets'
]
image_blocks
=
list
(
filter
(
lambda
x
:
x
[
'category_id'
]
==
CategoryId
.
ImageBody
,
layout_dets
))
table_blocks
=
list
(
filter
(
lambda
x
:
x
[
'category_id'
]
==
CategoryId
.
TableBody
,
layout_dets
))
def
add_need_remove_block
(
blocks
):
for
i
in
range
(
len
(
blocks
)):
for
j
in
range
(
i
+
1
,
len
(
blocks
)):
block1
=
blocks
[
i
]
block2
=
blocks
[
j
]
overlap_box
=
get_minbox_if_overlap_by_ratio
(
block1
[
'bbox'
],
block2
[
'bbox'
],
0.8
)
if
overlap_box
is
not
None
:
# 判断哪个区块的面积更小,移除较小的区块
area1
=
(
block1
[
'bbox'
][
2
]
-
block1
[
'bbox'
][
0
])
*
(
block1
[
'bbox'
][
3
]
-
block1
[
'bbox'
][
1
])
area2
=
(
block2
[
'bbox'
][
2
]
-
block2
[
'bbox'
][
0
])
*
(
block2
[
'bbox'
][
3
]
-
block2
[
'bbox'
][
1
])
if
area1
<=
area2
:
block_to_remove
=
block1
large_block
=
block2
else
:
block_to_remove
=
block2
large_block
=
block1
if
block_to_remove
not
in
need_remove_list
:
# 扩展大区块的边界框
x1
,
y1
,
x2
,
y2
=
large_block
[
'bbox'
]
sx1
,
sy1
,
sx2
,
sy2
=
block_to_remove
[
'bbox'
]
x1
=
min
(
x1
,
sx1
)
y1
=
min
(
y1
,
sy1
)
x2
=
max
(
x2
,
sx2
)
y2
=
max
(
y2
,
sy2
)
large_block
[
'bbox'
]
=
[
x1
,
y1
,
x2
,
y2
]
need_remove_list
.
append
(
block_to_remove
)
# 处理图像-图像重叠
add_need_remove_block
(
image_blocks
)
# 处理表格-表格重叠
add_need_remove_block
(
table_blocks
)
# 从布局中移除标记的区块
for
need_remove
in
need_remove_list
:
if
need_remove
in
layout_dets
:
layout_dets
.
remove
(
need_remove
)
def
__fix_axis
(
self
):
def
__fix_axis
(
self
):
need_remove_list
=
[]
need_remove_list
=
[]
...
@@ -46,42 +101,46 @@ class MagicModel:
...
@@ -46,42 +101,46 @@ class MagicModel:
def
__fix_by_remove_high_iou_and_low_confidence
(
self
):
def
__fix_by_remove_high_iou_and_low_confidence
(
self
):
need_remove_list
=
[]
need_remove_list
=
[]
layout_dets
=
self
.
__page_model_info
[
'layout_dets'
]
layout_dets
=
list
(
filter
(
lambda
x
:
x
[
'category_id'
]
in
[
CategoryId
.
Title
,
CategoryId
.
Text
,
CategoryId
.
ImageBody
,
CategoryId
.
ImageCaption
,
CategoryId
.
TableBody
,
CategoryId
.
TableCaption
,
CategoryId
.
TableFootnote
,
CategoryId
.
InterlineEquation_Layout
,
CategoryId
.
InterlineEquationNumber_Layout
,
],
self
.
__page_model_info
[
'layout_dets'
]
)
)
for
i
in
range
(
len
(
layout_dets
)):
for
i
in
range
(
len
(
layout_dets
)):
for
j
in
range
(
i
+
1
,
len
(
layout_dets
)):
for
j
in
range
(
i
+
1
,
len
(
layout_dets
)):
layout_det1
=
layout_dets
[
i
]
layout_det1
=
layout_dets
[
i
]
layout_det2
=
layout_dets
[
j
]
layout_det2
=
layout_dets
[
j
]
if
layout_det1
[
'category_id'
]
in
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]
and
layout_det2
[
'category_id'
]
in
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]:
if
(
calculate_iou
(
layout_det1
[
'bbox'
],
layout_det2
[
'bbox'
])
>
0.9
):
if
layout_det1
[
'score'
]
<
layout_det2
[
'score'
]:
layout_det_need_remove
=
layout_det1
else
:
layout_det_need_remove
=
layout_det2
if
layout_det_need_remove
not
in
need_remove_list
:
if
calculate_iou
(
layout_det1
[
'bbox'
],
layout_det2
[
'bbox'
])
>
0.9
:
need_remove_list
.
append
(
layout_det_need_remove
)
else
:
layout_det_need_remove
=
layout_det1
if
layout_det1
[
'score'
]
<
layout_det2
[
'score'
]
else
layout_det2
continue
else
:
if
layout_det_need_remove
not
in
need_remove_list
:
continue
need_remove_list
.
append
(
layout_det_need_remove
)
for
need_remove
in
need_remove_list
:
for
need_remove
in
need_remove_list
:
layout_dets
.
remove
(
need_remove
)
self
.
__page_model_info
[
'
layout_dets
'
]
.
remove
(
need_remove
)
def
__fix_footnote
(
self
):
def
__fix_footnote
(
self
):
# 3: figure, 5: table, 7: footnote
footnotes
=
[]
footnotes
=
[]
figures
=
[]
figures
=
[]
tables
=
[]
tables
=
[]
for
obj
in
self
.
__page_model_info
[
'layout_dets'
]:
for
obj
in
self
.
__page_model_info
[
'layout_dets'
]:
if
obj
[
'category_id'
]
==
7
:
if
obj
[
'category_id'
]
==
CategoryId
.
TableFootnote
:
footnotes
.
append
(
obj
)
footnotes
.
append
(
obj
)
elif
obj
[
'category_id'
]
==
3
:
elif
obj
[
'category_id'
]
==
CategoryId
.
ImageBody
:
figures
.
append
(
obj
)
figures
.
append
(
obj
)
elif
obj
[
'category_id'
]
==
5
:
elif
obj
[
'category_id'
]
==
CategoryId
.
TableBody
:
tables
.
append
(
obj
)
tables
.
append
(
obj
)
if
len
(
footnotes
)
*
len
(
figures
)
==
0
:
if
len
(
footnotes
)
*
len
(
figures
)
==
0
:
continue
continue
...
@@ -314,10 +373,10 @@ class MagicModel:
...
@@ -314,10 +373,10 @@ class MagicModel:
def
get_imgs
(
self
):
def
get_imgs
(
self
):
with_captions
=
self
.
__tie_up_category_by_distance_v3
(
with_captions
=
self
.
__tie_up_category_by_distance_v3
(
3
,
4
CategoryId
.
ImageBody
,
CategoryId
.
ImageCaption
)
)
with_footnotes
=
self
.
__tie_up_category_by_distance_v3
(
with_footnotes
=
self
.
__tie_up_category_by_distance_v3
(
3
,
CategoryId
.
ImageFootnote
CategoryId
.
ImageBody
,
CategoryId
.
ImageFootnote
)
)
ret
=
[]
ret
=
[]
for
v
in
with_captions
:
for
v
in
with_captions
:
...
@@ -333,10 +392,10 @@ class MagicModel:
...
@@ -333,10 +392,10 @@ class MagicModel:
def
get_tables
(
self
)
->
list
:
def
get_tables
(
self
)
->
list
:
with_captions
=
self
.
__tie_up_category_by_distance_v3
(
with_captions
=
self
.
__tie_up_category_by_distance_v3
(
5
,
6
CategoryId
.
TableBody
,
CategoryId
.
TableCaption
)
)
with_footnotes
=
self
.
__tie_up_category_by_distance_v3
(
with_footnotes
=
self
.
__tie_up_category_by_distance_v3
(
5
,
7
CategoryId
.
TableBody
,
CategoryId
.
TableFootnote
)
)
ret
=
[]
ret
=
[]
for
v
in
with_captions
:
for
v
in
with_captions
:
...
@@ -385,20 +444,21 @@ class MagicModel:
...
@@ -385,20 +444,21 @@ class MagicModel:
all_spans
=
[]
all_spans
=
[]
layout_dets
=
self
.
__page_model_info
[
'layout_dets'
]
layout_dets
=
self
.
__page_model_info
[
'layout_dets'
]
allow_category_id_list
=
[
3
,
5
,
13
,
14
,
15
]
allow_category_id_list
=
[
CategoryId
.
ImageBody
,
CategoryId
.
TableBody
,
CategoryId
.
InlineEquation
,
CategoryId
.
InterlineEquation_YOLO
,
CategoryId
.
OcrText
,
]
"""当成span拼接的"""
"""当成span拼接的"""
# 3: 'image', # 图片
# 5: 'table', # 表格
# 13: 'inline_equation', # 行内公式
# 14: 'interline_equation', # 行间公式
# 15: 'text', # ocr识别文本
for
layout_det
in
layout_dets
:
for
layout_det
in
layout_dets
:
category_id
=
layout_det
[
'category_id'
]
category_id
=
layout_det
[
'category_id'
]
if
category_id
in
allow_category_id_list
:
if
category_id
in
allow_category_id_list
:
span
=
{
'bbox'
:
layout_det
[
'bbox'
],
'score'
:
layout_det
[
'score'
]}
span
=
{
'bbox'
:
layout_det
[
'bbox'
],
'score'
:
layout_det
[
'score'
]}
if
category_id
==
3
:
if
category_id
==
CategoryId
.
ImageBody
:
span
[
'type'
]
=
ContentType
.
IMAGE
span
[
'type'
]
=
ContentType
.
IMAGE
elif
category_id
==
5
:
elif
category_id
==
CategoryId
.
TableBody
:
# 获取table模型结果
# 获取table模型结果
latex
=
layout_det
.
get
(
'latex'
,
None
)
latex
=
layout_det
.
get
(
'latex'
,
None
)
html
=
layout_det
.
get
(
'html'
,
None
)
html
=
layout_det
.
get
(
'html'
,
None
)
...
@@ -407,13 +467,13 @@ class MagicModel:
...
@@ -407,13 +467,13 @@ class MagicModel:
elif
html
:
elif
html
:
span
[
'html'
]
=
html
span
[
'html'
]
=
html
span
[
'type'
]
=
ContentType
.
TABLE
span
[
'type'
]
=
ContentType
.
TABLE
elif
category_id
==
13
:
elif
category_id
==
CategoryId
.
InlineEquation
:
span
[
'content'
]
=
layout_det
[
'latex'
]
span
[
'content'
]
=
layout_det
[
'latex'
]
span
[
'type'
]
=
ContentType
.
INLINE_EQUATION
span
[
'type'
]
=
ContentType
.
INLINE_EQUATION
elif
category_id
==
14
:
elif
category_id
==
CategoryId
.
InterlineEquation_YOLO
:
span
[
'content'
]
=
layout_det
[
'latex'
]
span
[
'content'
]
=
layout_det
[
'latex'
]
span
[
'type'
]
=
ContentType
.
INTERLINE_EQUATION
span
[
'type'
]
=
ContentType
.
INTERLINE_EQUATION
elif
category_id
==
15
:
elif
category_id
==
CategoryId
.
OcrText
:
span
[
'content'
]
=
layout_det
[
'text'
]
span
[
'content'
]
=
layout_det
[
'text'
]
span
[
'type'
]
=
ContentType
.
TEXT
span
[
'type'
]
=
ContentType
.
TEXT
all_spans
.
append
(
span
)
all_spans
.
append
(
span
)
...
@@ -438,4 +498,4 @@ class MagicModel:
...
@@ -438,4 +498,4 @@ class MagicModel:
for
col
in
extra_col
:
for
col
in
extra_col
:
block
[
col
]
=
item
.
get
(
col
,
None
)
block
[
col
]
=
item
.
get
(
col
,
None
)
blocks
.
append
(
block
)
blocks
.
append
(
block
)
return
blocks
return
blocks
\ No newline at end of file
mineru/cli/fast_api.py
View file @
516f4926
...
@@ -34,10 +34,10 @@ async def parse_pdf(
...
@@ -34,10 +34,10 @@ async def parse_pdf(
formula_enable
:
bool
=
Form
(
True
),
formula_enable
:
bool
=
Form
(
True
),
table_enable
:
bool
=
Form
(
True
),
table_enable
:
bool
=
Form
(
True
),
server_url
:
Optional
[
str
]
=
Form
(
None
),
server_url
:
Optional
[
str
]
=
Form
(
None
),
re
u
turn_md
:
bool
=
Form
(
True
),
return_md
:
bool
=
Form
(
True
),
re
u
turn_middle_json
:
bool
=
Form
(
False
),
return_middle_json
:
bool
=
Form
(
False
),
return_model_output
:
bool
=
Form
(
False
),
return_model_output
:
bool
=
Form
(
False
),
re
u
turn_content_list
:
bool
=
Form
(
False
),
return_content_list
:
bool
=
Form
(
False
),
return_images
:
bool
=
Form
(
False
),
return_images
:
bool
=
Form
(
False
),
start_page_id
:
int
=
Form
(
0
),
start_page_id
:
int
=
Form
(
0
),
end_page_id
:
int
=
Form
(
99999
),
end_page_id
:
int
=
Form
(
99999
),
...
@@ -98,11 +98,11 @@ async def parse_pdf(
...
@@ -98,11 +98,11 @@ async def parse_pdf(
server_url
=
server_url
,
server_url
=
server_url
,
f_draw_layout_bbox
=
False
,
f_draw_layout_bbox
=
False
,
f_draw_span_bbox
=
False
,
f_draw_span_bbox
=
False
,
f_dump_md
=
re
u
turn_md
,
f_dump_md
=
return_md
,
f_dump_middle_json
=
re
u
turn_middle_json
,
f_dump_middle_json
=
return_middle_json
,
f_dump_model_output
=
return_model_output
,
f_dump_model_output
=
return_model_output
,
f_dump_orig_pdf
=
False
,
f_dump_orig_pdf
=
False
,
f_dump_content_list
=
re
u
turn_content_list
,
f_dump_content_list
=
return_content_list
,
start_page_id
=
start_page_id
,
start_page_id
=
start_page_id
,
end_page_id
=
end_page_id
,
end_page_id
=
end_page_id
,
)
)
...
@@ -128,16 +128,16 @@ async def parse_pdf(
...
@@ -128,16 +128,16 @@ async def parse_pdf(
if
os
.
path
.
exists
(
parse_dir
):
if
os
.
path
.
exists
(
parse_dir
):
if
re
u
turn_md
:
if
return_md
:
data
[
"md_content"
]
=
get_infer_result
(
".md"
)
data
[
"md_content"
]
=
get_infer_result
(
".md"
)
if
re
u
turn_middle_json
:
if
return_middle_json
:
data
[
"middle_json"
]
=
get_infer_result
(
"_middle.json"
)
data
[
"middle_json"
]
=
get_infer_result
(
"_middle.json"
)
if
return_model_output
:
if
return_model_output
:
if
backend
.
startswith
(
"pipeline"
):
if
backend
.
startswith
(
"pipeline"
):
data
[
"model_output"
]
=
get_infer_result
(
"_model.json"
)
data
[
"model_output"
]
=
get_infer_result
(
"_model.json"
)
else
:
else
:
data
[
"model_output"
]
=
get_infer_result
(
"_model_output.txt"
)
data
[
"model_output"
]
=
get_infer_result
(
"_model_output.txt"
)
if
re
u
turn_content_list
:
if
return_content_list
:
data
[
"content_list"
]
=
get_infer_result
(
"_content_list.json"
)
data
[
"content_list"
]
=
get_infer_result
(
"_content_list.json"
)
if
return_images
:
if
return_images
:
image_paths
=
glob
(
f
"
{
parse_dir
}
/images/*.jpg"
)
image_paths
=
glob
(
f
"
{
parse_dir
}
/images/*.jpg"
)
...
...
mineru/utils/block_pre_proc.py
View file @
516f4926
...
@@ -90,8 +90,8 @@ def prepare_block_bboxes(
...
@@ -90,8 +90,8 @@ def prepare_block_bboxes(
"""经过以上处理后,还存在大框套小框的情况,则删除小框"""
"""经过以上处理后,还存在大框套小框的情况,则删除小框"""
all_bboxes
=
remove_overlaps_min_blocks
(
all_bboxes
)
all_bboxes
=
remove_overlaps_min_blocks
(
all_bboxes
)
all_discarded_blocks
=
remove_overlaps_min_blocks
(
all_discarded_blocks
)
all_discarded_blocks
=
remove_overlaps_min_blocks
(
all_discarded_blocks
)
"""将剩余的bbox做分离处理,防止后面分layout时出错"""
# all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes)
"""粗排序后返回"""
all_bboxes
.
sort
(
key
=
lambda
x
:
x
[
0
]
+
x
[
1
])
all_bboxes
.
sort
(
key
=
lambda
x
:
x
[
0
]
+
x
[
1
])
return
all_bboxes
,
all_discarded_blocks
,
footnote_blocks
return
all_bboxes
,
all_discarded_blocks
,
footnote_blocks
...
@@ -213,35 +213,39 @@ def remove_overlaps_min_blocks(all_bboxes):
...
@@ -213,35 +213,39 @@ def remove_overlaps_min_blocks(all_bboxes):
# 重叠block,小的不能直接删除,需要和大的那个合并成一个更大的。
# 重叠block,小的不能直接删除,需要和大的那个合并成一个更大的。
# 删除重叠blocks中较小的那些
# 删除重叠blocks中较小的那些
need_remove
=
[]
need_remove
=
[]
for
block1
in
all_bboxes
:
for
i
in
range
(
len
(
all_bboxes
)):
for
block2
in
all_bboxes
:
for
j
in
range
(
i
+
1
,
len
(
all_bboxes
)):
if
block1
!=
block2
:
block1
=
all_bboxes
[
i
]
block1_bbox
=
block1
[:
4
]
block2
=
all_bboxes
[
j
]
block2_bbox
=
block2
[:
4
]
block1_bbox
=
block1
[:
4
]
overlap_box
=
get_minbox_if_overlap_by_ratio
(
block2_bbox
=
block2
[:
4
]
block1_bbox
,
block2_bbox
,
0.8
overlap_box
=
get_minbox_if_overlap_by_ratio
(
)
block1_bbox
,
block2_bbox
,
0.8
if
overlap_box
is
not
None
:
)
block_to_remove
=
next
(
if
overlap_box
is
not
None
:
(
block
for
block
in
all_bboxes
if
block
[:
4
]
==
overlap_box
),
# 判断哪个区块的面积更小,移除较小的区块
None
,
area1
=
(
block1
[
2
]
-
block1
[
0
])
*
(
block1
[
3
]
-
block1
[
1
])
)
area2
=
(
block2
[
2
]
-
block2
[
0
])
*
(
block2
[
3
]
-
block2
[
1
])
if
(
block_to_remove
is
not
None
if
area1
<=
area2
:
and
block_to_remove
not
in
need_remove
block_to_remove
=
block1
):
large_block
=
block2
large_block
=
block1
if
block1
!=
block_to_remove
else
block2
else
:
x1
,
y1
,
x2
,
y2
=
large_block
[:
4
]
block_to_remove
=
block2
sx1
,
sy1
,
sx2
,
sy2
=
block_to_remove
[:
4
]
large_block
=
block1
x1
=
min
(
x1
,
sx1
)
y1
=
min
(
y1
,
sy1
)
if
block_to_remove
not
in
need_remove
:
x2
=
max
(
x2
,
sx2
)
x1
,
y1
,
x2
,
y2
=
large_block
[:
4
]
y2
=
max
(
y2
,
sy2
)
sx1
,
sy1
,
sx2
,
sy2
=
block_to_remove
[:
4
]
large_block
[:
4
]
=
[
x1
,
y1
,
x2
,
y2
]
x1
=
min
(
x1
,
sx1
)
need_remove
.
append
(
block_to_remove
)
y1
=
min
(
y1
,
sy1
)
x2
=
max
(
x2
,
sx2
)
if
len
(
need_remove
)
>
0
:
y2
=
max
(
y2
,
sy2
)
for
block
in
need_remove
:
large_block
[:
4
]
=
[
x1
,
y1
,
x2
,
y2
]
need_remove
.
append
(
block_to_remove
)
for
block
in
need_remove
:
if
block
in
all_bboxes
:
all_bboxes
.
remove
(
block
)
all_bboxes
.
remove
(
block
)
return
all_bboxes
return
all_bboxes
\ No newline at end of file
pyproject.toml
View file @
516f4926
...
@@ -43,7 +43,7 @@ vlm = [
...
@@ -43,7 +43,7 @@ vlm = [
"pydantic"
,
"pydantic"
,
]
]
sglang
=
[
sglang
=
[
"sglang[all]>=0.4.
7
,<0.4.9"
,
"sglang[all]>=0.4.
8
,<0.4.9"
,
]
]
pipeline
=
[
pipeline
=
[
"matplotlib>=3.10,<4"
,
"matplotlib>=3.10,<4"
,
...
...
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