Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
M
MicScanSystemLeiCaV1.0
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
徐生海
MicScanSystemLeiCaV1.0
Commits
e0887ee2
Commit
e0887ee2
authored
Jan 21, 2026
by
徐生海
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial commit
parent
638f0371
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
197 additions
and
121 deletions
+197
-121
SlideFocusingBusiness.py
AppBackEnd/BackEndBusiness/SlideFocusingBusiness.py
+6
-6
ColorAdjustmentSelector.py
AppCore/AppDBModels/CameraModels/ColorAdjustmentSelector.py
+83
-0
WeightModelInterface.py
AppCore/AppDBModels/CameraModels/WeightModelInterface.py
+0
-10
DBModels.py
AppCore/AppDBModels/DBModels.py
+1
-0
CameraImpl.py
AppCore/AppImpls/CameraImpl.py
+10
-1
DBModels.py
AppCore/DBModels.py
+1
-0
AppUtils.py
AppUtils/AppUtils.py
+47
-0
DataBaseDeploy.py
AppUtils/DataBaseDeploy.py
+9
-1
FocusingUtil.py
AppUtils/FocusingUtil.py
+15
-81
MicScanSystemLeiCa.py
MicScanSystemLeiCa.py
+25
-22
No files found.
AppBackEnd/BackEndBusiness/SlideFocusingBusiness.py
View file @
e0887ee2
...
...
@@ -328,17 +328,17 @@ class SlideFocusingBusiness(Qt.QObject, AppBusinessController):
FocusingCount
=
0
else
:
MaxFocusingCount
=
FocusingConfigModel
.
RetryCount
(
model
.
ObjectiveLevel
,
ScanStatusLevel
.
Low_First_Focus_Status
)
MaxFocusingCount
=
min
(
8
,
MaxFocusingCount
)
if
FocusingCount
<=
MaxFocusingCount
:
if
RECODE_SUCCEED
==
self
.
appendTrayFocusingArray
(
FocusingCount
,
Index
,
model
,
tray_model
,
TrayFocusArrays
):
"""如果重试次数超过设置的最大重试次数 退出定焦"""
self
.
changed_focusing_item_point
(
model
,
Locations
)
FocusingCount
+=
1
else
:
"""如果发生没有找到焦点 添加点位 并重新定焦"""
"""定焦不成功 移动一行一列重新定焦 行列数不能大于最大行最大列"""
MaxFocusingCount
=
FocusingConfigModel
.
RetryCount
(
model
.
ObjectiveLevel
,
ScanStatusLevel
.
Low_First_Focus_Status
)
MaxFocusingCount
=
min
(
8
,
MaxFocusingCount
)
if
FocusingCount
<=
MaxFocusingCount
:
if
RECODE_SUCCEED
==
self
.
appendTrayFocusingArray
(
FocusingCount
,
Index
,
model
,
tray_model
,
TrayFocusArrays
):
"""如果重试次数超过设置的最大重试次数 退出定焦"""
self
.
changed_focusing_item_point
(
model
,
Locations
)
FocusingCount
+=
1
...
...
AppCore/AppDBModels/CameraModels/ColorAdjustmentSelector.py
0 → 100644
View file @
e0887ee2
#!D:/IDE/anaconda3/envs/Microscope_Scan python
# -*- coding: UTF-8 -*-
"""
@FileName:ColorAdjustmentSelector.py
@Description:
@Author:XSH
@Time:2026/1/20 20:13
@Department:研发部
@Copyright:©2026 乐辰生物
"""
from
PyQt5
import
Qt
from
datetime
import
datetime
,
date
import
copy
from
AppCore.AppDBModels.CoreModel
import
(
CoreModel
,
DBManager
,
dbController
)
from
sqlalchemy.orm
import
declarative_base
,
mapper
,
sessionmaker
,
Session
from
sqlalchemy.orm
import
(
declarative_base
,
Mapped
,
mapped_column
,
relationship
,
mapper
,
sessionmaker
)
from
sqlalchemy
import
Table
,
Engine
,
create_engine
,
Column
,
ForeignKey
,
and_
,
or_
,
insert
,
delete
,
update
,
select
,
func
,
ColumnElement
,
distinct
from
sqlalchemy.types
import
(
Text
,
VARCHAR
,
REAL
,
Float
,
Integer
,
String
,
SmallInteger
,
DateTime
,
Date
,
ARRAY
,
Boolean
,
JSON
)
from
sqlalchemy.orm.decl_api
import
DeclarativeBase
,
MetaData
from
AppUtils.utils
import
*
from
AppCore.Levels
import
*
class
ColorAdjustmentSelector
(
CoreModel
):
__tablename__
=
't_ColorAdjustmentSelector'
ID
:
Mapped
[
int
]
=
mapped_column
(
primary_key
=
True
,
nullable
=
False
,
doc
=
'颜色通道选择器序号'
,
comment
=
'颜色通道选择器序号'
)
AdjustmentSelector
:
Mapped
[
str
]
=
mapped_column
(
Text
,
default
=
''
,
nullable
=
True
,
doc
=
'颜色通道选择器 '
,
comment
=
'颜色通道选择器 '
)
AdjustmentHue
:
Mapped
[
float
]
=
mapped_column
(
default
=
1.56494
,
nullable
=
True
,
doc
=
'颜色通道色相 '
,
comment
=
'颜色通道色相 '
)
AdjustmentSaturation
:
Mapped
[
float
]
=
mapped_column
(
default
=
1.56494
,
nullable
=
True
,
doc
=
'颜色通道饱和度 '
,
comment
=
'颜色通道饱和度 '
)
def
__init__
(
self
,
**
args
):
super
(
ColorAdjustmentSelector
,
self
)
.
__init__
()
self
.
initialize
()
self
.
initParameter
(
**
args
)
def
initValue
(
self
):
super
(
ColorAdjustmentSelector
,
self
)
.
initValue
()
self
.
ID
=
None
self
.
AdjustmentSelector
=
None
self
.
AdjustmentHue
=
None
self
.
AdjustmentSaturation
=
None
@
property
def
isAvailable
(
self
)
->
bool
:
return
self
.
hasAdjustmentSelector
and
self
.
hasAdjustmentHue
and
self
.
hasAdjustmentSaturation
__field__
=
(
'ID'
,
'AdjustmentSelector'
,
'AdjustmentHue'
,
'AdjustmentSaturation'
,
)
__types__
=
(
int
,
str
,
float
,
float
,
)
@
classmethod
def
initModels
(
cls
)
->
list
:
return
[
cls
.
instance
(
ID
=
1
,
AdjustmentSelector
=
"Red"
,
AdjustmentHue
=
0.15625
,
AdjustmentSaturation
=
1.04688
,),
cls
.
instance
(
ID
=
2
,
AdjustmentSelector
=
"Yellow"
,
AdjustmentHue
=
0.0625
,
AdjustmentSaturation
=
1.07031
,),
cls
.
instance
(
ID
=
3
,
AdjustmentSelector
=
"Green"
,
AdjustmentHue
=
0.21875
,
AdjustmentSaturation
=
1.32031
,),
cls
.
instance
(
ID
=
4
,
AdjustmentSelector
=
"Cyan"
,
AdjustmentHue
=
0.3125
,
AdjustmentSaturation
=
1.05469
,),
cls
.
instance
(
ID
=
5
,
AdjustmentSelector
=
"Blue"
,
AdjustmentHue
=
0.03125
,
AdjustmentSaturation
=
0.98438
,),
cls
.
instance
(
ID
=
6
,
AdjustmentSelector
=
"Magenta"
,
AdjustmentHue
=
1.40625
,
AdjustmentSaturation
=
0.95313
,),
]
@
property
def
hasID
(
self
)
->
bool
:
return
hasattr
(
self
,
'ID'
)
and
isinstance
(
self
.
ID
,
(
int
,
float
))
@
property
def
hasAdjustmentSelector
(
self
)
->
bool
:
return
hasattr
(
self
,
'AdjustmentSelector'
)
and
isinstance
(
self
.
AdjustmentSelector
,
str
)
@
property
def
hasAdjustmentHue
(
self
)
->
bool
:
return
hasattr
(
self
,
'AdjustmentHue'
)
and
isinstance
(
self
.
AdjustmentHue
,
(
int
,
float
))
@
property
def
hasAdjustmentSaturation
(
self
)
->
bool
:
return
hasattr
(
self
,
'AdjustmentSaturation'
)
and
isinstance
(
self
.
AdjustmentSaturation
,
(
int
,
float
))
AppCore/AppDBModels/CameraModels/WeightModelInterface.py
deleted
100644 → 0
View file @
638f0371
#!D:/IDE/anaconda3/envs/Microscope_Scan python
# -*- coding: UTF-8 -*-
"""
@FileName:WeightModelInterface.py
@Description:
@Author:XSH
@Time:2026/1/4 16:53
@Department:研发部
@Copyright:©2026 乐辰生物
"""
AppCore/AppDBModels/DBModels.py
View file @
e0887ee2
...
...
@@ -38,6 +38,7 @@ from AppCore.AppDBModels.ConfigModels.TrayConfigModel import TrayC
from
AppCore.AppDBModels.CameraModels.CameraInterface
import
CameraInterface
#
from
AppCore.AppDBModels.CameraModels.CameraModel
import
CameraModel
#
from
AppCore.AppDBModels.CameraModels.CameraColorModel
import
CameraColorModel
#
from
AppCore.AppDBModels.CameraModels.ColorAdjustmentSelector
import
ColorAdjustmentSelector
#
from
AppCore.AppDBModels.SlideModels.SlideModelInterface
import
SlideModelInterface
#
from
AppCore.AppDBModels.SlideModels.AllCellModel
import
AllCellModel
#
from
AppCore.AppDBModels.SlideModels.AlreadyScannedModel
import
AlreadyScannedModel
#
...
...
AppCore/AppImpls/CameraImpl.py
View file @
e0887ee2
...
...
@@ -128,7 +128,16 @@ class CameraImpl(Qt.QObject, CameraImplInterface):
self
.
Manager
.
ColorTransformationValue
.
IntValue
=
int
(
ColorParameter
.
ColorTransformationValue21
)
self
.
Manager
.
ColorTransformationValueSelector
.
IntValue
=
int
(
8
)
self
.
Manager
.
ColorTransformationValue
.
IntValue
=
int
(
ColorParameter
.
ColorTransformationValue22
)
models
:
list
[
ColorAdjustmentSelector
]
=
ColorAdjustmentSelector
.
all
()
item
:
ColorAdjustmentSelector
for
item
in
models
:
if
item
.
isAvailable
:
try
:
self
.
Manager
.
ColorAdjustmentSelector
.
Value
=
item
.
AdjustmentSelector
#
self
.
Manager
.
ColorAdjustmentHue
.
Value
=
item
.
AdjustmentHue
#
self
.
Manager
.
ColorAdjustmentSaturation
.
Value
=
item
.
AdjustmentSaturation
#
except
Exception
as
ColorAdjustmentE
:
AppUtils
.
print_Exception
(
ColorAdjustmentE
)
except
Exception
as
e
:
AppUtils
.
print_Exception
(
e
)
...
...
AppCore/DBModels.py
View file @
e0887ee2
...
...
@@ -38,6 +38,7 @@ from AppCore.AppDBModels.ConfigModels.TrayConfigModel import TrayC
from
AppCore.AppDBModels.CameraModels.CameraInterface
import
CameraInterface
#
from
AppCore.AppDBModels.CameraModels.CameraModel
import
CameraModel
#
from
AppCore.AppDBModels.CameraModels.CameraColorModel
import
CameraColorModel
#
from
AppCore.AppDBModels.CameraModels.ColorAdjustmentSelector
import
ColorAdjustmentSelector
#
from
AppCore.AppDBModels.SlideModels.SlideModelInterface
import
SlideModelInterface
#
from
AppCore.AppDBModels.SlideModels.AllCellModel
import
AllCellModel
#
from
AppCore.AppDBModels.SlideModels.AlreadyScannedModel
import
AlreadyScannedModel
#
...
...
AppUtils/AppUtils.py
View file @
e0887ee2
...
...
@@ -270,6 +270,53 @@ class AppUtils:
else
:
return
None
@
staticmethod
def
IsSocketPortAvailable
(
address
:
str
,
port
:
int
)
->
bool
:
"""判断当前IP 和 端口是否可联接"""
try
:
if
not
isinstance
(
address
,
str
)
or
not
isinstance
(
port
,
(
int
,
float
)):
return
False
Socket
:
Qt
.
QTcpServer
=
Qt
.
QTcpServer
()
IsAvailable
=
Socket
.
listen
(
Qt
.
QHostAddress
(
address
),
int
(
port
))
if
IsAvailable
:
Socket
.
close
()
return
True
else
:
return
False
except
Exception
as
e
:
AppUtils
.
print_Exception
(
e
)
return
False
@
staticmethod
def
IsServerAvailable
(
url_string
:
str
)
->
bool
:
"""判断当前IP 和 端口是否可联接"""
try
:
url
:
Qt
.
QUrl
=
Qt
.
QUrl
(
url_string
)
url_authority
:
list
[
str
]
=
url
.
authority
()
.
split
(
":"
)
if
len
(
url_authority
)
<
2
:
HostAddr
=
"0.0.0.0"
HostPort
=
9991
else
:
try
:
HostAddr
,
HostPort
=
url_authority
[
0
],
AppConvert
.
ObjectConvert
(
url_authority
[
1
],
int
)
except
Exception
as
e
:
HostAddr
=
"0.0.0.0"
HostPort
=
9991
if
not
isinstance
(
HostAddr
,
str
):
HostAddr
=
"0.0.0.0"
if
not
isinstance
(
HostPort
,
(
int
,
float
)):
HostPort
=
9991
Socket
:
Qt
.
QTcpServer
=
Qt
.
QTcpServer
()
IsAvailable
=
Socket
.
listen
(
Qt
.
QHostAddress
(
HostAddr
),
int
(
HostPort
))
if
IsAvailable
:
Socket
.
close
()
return
True
else
:
return
False
except
Exception
as
e
:
AppUtils
.
print_Exception
(
e
)
return
False
...
...
AppUtils/DataBaseDeploy.py
View file @
e0887ee2
...
...
@@ -124,6 +124,7 @@ class DataBaseDeploy:
def
initCamera
(
cls
):
cls
.
initCameraModel
()
cls
.
initCameraColorModel
()
cls
.
initColorAdjustmentSelector
()
"""##################################################账号管理表######################################################"""
...
...
@@ -377,7 +378,14 @@ class DataBaseDeploy:
CameraColorModel
.
clear
()
CameraColorModel
.
append_all
(
CameraColorModel
.
initModels
())
@
classmethod
def
initColorAdjustmentSelector
(
cls
):
modules
:
list
[
ColorAdjustmentSelector
]
=
ColorAdjustmentSelector
.
initModels
()
if
ColorAdjustmentSelector
.
has_limit
(
len
(
modules
)):
return
ColorAdjustmentSelector
.
create_table
()
ColorAdjustmentSelector
.
clear
()
ColorAdjustmentSelector
.
append_all
(
modules
)
...
...
AppUtils/FocusingUtil.py
View file @
e0887ee2
...
...
@@ -239,7 +239,6 @@ class FocusingUtil:
return
int
(
float
(
high_freq_ratio
)
*
10000
)
%
10000
@
staticmethod
def
sq_bre_13
(
image
:
np
.
ndarray
,
thresh
:
int
):
# 检查图像是否存在并正确读取
...
...
@@ -287,75 +286,12 @@ class FocusingUtil:
if
img_data
is
None
:
return
0
if
len
(
img_data
.
shape
)
==
3
and
img_data
.
shape
[
2
]
==
3
:
img_data
=
cv2
.
cvtColor
(
img_data
,
cv2
.
COLOR_BGR2RGB
)
gray
=
cv2
.
cvtColor
(
img_data
,
cv2
.
COLOR_RGB2GRAY
)
gray
=
np
.
array
(
gray
.
astype
(
np
.
int16
))
img_data
=
np
.
array
(
img_data
.
astype
(
np
.
int16
))
img_data_
=
img_data
[:
-
3
,
:,
:]
gray_0
=
img_data_
[:,
:,
0
]
gray_1
=
img_data_
[:,
:,
1
]
gray_2
=
img_data_
[:,
:,
2
]
gray_
=
gray
[:
-
3
,
:]
else
:
img_data_
=
img_data
[:
-
3
,
:]
gray
=
np
.
array
(
img_data
[:,
:],
dtype
=
np
.
int16
)
gray_
=
gray
[:
-
3
,
:]
gray_0
=
None
gray_1
=
None
gray_2
=
None
_thresh
:
int
=
int
(
abs
(
thresh
))
if
isinstance
(
thresh
,
(
float
,
int
))
else
150
_thresh2
:
int
=
int
(
abs
(
thresh2
))
if
isinstance
(
thresh2
,
(
int
,
float
))
else
220
try
:
edge_strength1
=
gray_
-
gray
[
2
:
-
1
,
:]
edge_strength2
=
gray
[
1
:
-
2
,
:]
-
gray
[
3
:,
:]
edge_strength3
=
(
edge_strength1
+
edge_strength2
)
/
2
mask
=
gray_
<
_thresh
# mask = np.logical_and(mask, gray[:-3] < _thresh)
# mask = gray_1 < _thresh
if
gray_0
is
not
None
and
gray_1
is
not
None
and
gray_2
is
not
None
:
mask_1
=
(
gray_0
.
astype
(
np
.
int16
)
-
gray_1
.
astype
(
np
.
int16
))
>
10
mask_2
=
(
gray_2
.
astype
(
np
.
int16
)
-
gray_1
.
astype
(
np
.
int16
))
>
10
# mask_3 = gray_2 > gray_0
mask_
=
mask_1
|
mask_2
# 定义结构元素
kernel
=
cv2
.
getStructuringElement
(
cv2
.
MORPH_RECT
,
(
3
,
3
))
# 开运算:先腐蚀后膨胀
mask_
=
cv2
.
morphologyEx
(
mask_
.
astype
(
np
.
uint8
),
cv2
.
MORPH_OPEN
,
kernel
)
mask_
=
mask_
>
0
mask
=
mask_
&
mask
set_data
=
edge_strength3
[
mask
]
if
len
(
set_data
)
==
0
:
return
0
gradient
=
np
.
mean
(
set_data
**
2
)
return
int
(
gradient
.
item
()
*
1000
)
except
Exception
as
e
:
AppUtils
.
print_Exception
(
e
)
return
0
@
staticmethod
def
sq_bre_15
(
img_data
:
np
.
ndarray
,
thresh
:
int
,
thresh2
:
int
):
"""
:param img_data: numpy
:param thresh: int
:param thresh2: int
:return: int mask_mean * 1000大于3000时代表图像清晰
"""
# 检查图像是否存在并正确读取
isColor
:
bool
=
False
if
img_data
is
None
:
return
0
if
len
(
img_data
.
shape
)
==
3
and
img_data
.
shape
[
2
]
==
3
:
img_data
=
cv2
.
cvtColor
(
img_data
,
cv2
.
COLOR_BGR2RGB
)
img_data_
=
img_data
[:
-
3
,
:,
:]
gray_0
=
img_data_
[:,
:,
0
]
gray_1
=
img_data_
[:,
:,
1
]
gray
=
np
.
array
(
img_data
[:,
:,
1
],
dtype
=
np
.
int16
)
gray_2
=
img_data_
[:,
:,
2
]
gray_
=
gray
[:
-
3
,
:]
isColor
=
True
else
:
img_data_
=
img_data
[:
-
3
,
:]
...
...
@@ -364,10 +300,9 @@ class FocusingUtil:
gray_0
=
None
gray_1
=
None
gray_2
=
None
isColor
=
False
_thresh
:
int
=
int
(
abs
(
thresh
))
if
isinstance
(
thresh
,
(
float
,
int
))
else
3000
_thresh2
:
int
=
int
(
abs
(
thresh2
))
if
isinstance
(
thresh2
,
(
int
,
float
))
else
3000
Thresh
:
int
=
_thresh2
_thresh
:
int
=
int
(
abs
(
thresh
))
if
isinstance
(
thresh
,
(
float
,
int
))
else
150
_thresh2
:
int
=
int
(
abs
(
thresh2
))
if
isinstance
(
thresh2
,
(
int
,
float
))
else
220
try
:
edge_strength1
=
gray_
-
gray
[
1
:
-
2
:]
edge_strength2
=
gray_
-
gray
[
2
:
-
1
,
:]
...
...
@@ -381,24 +316,23 @@ class FocusingUtil:
mask
=
np
.
logical_or
(
edge_strength_1
,
edge_strength_2
)
# mask = np.logical_and(mask, gray[:-3] < _thresh)
# mask = gray_1 < _thresh
mask_mean
=
0
if
gray_0
is
not
None
and
gray_1
is
not
None
and
gray_2
is
not
None
:
mask_1
=
gray_0
>
gray_1
mask_2
=
gray_2
>
gray_1
mask_3
=
gray_2
>
gray_0
mask_
=
mask_1
&
mask_2
&
mask_3
kernel
=
cv2
.
getStructuringElement
(
cv2
.
MORPH_RECT
,
(
5
,
5
))
mask_
=
cv2
.
morphologyEx
(
mask_
.
astype
(
np
.
uint8
),
cv2
.
MORPH_OPEN
,
kernel
)
mask_
=
mask_
>
0
# mask = mask_ & mask
mask_1
=
np
.
logical_and
(
np
.
abs
(
edge_strength1
)
>
0
,
mask_
)
mask_1
=
np
.
abs
(
edge_strength1
[
mask_1
])
mask_mean
=
np
.
mean
(
mask_1
)
if
np
.
isnan
(
mask_mean
):
mask
=
mask_1
&
mask_2
&
mask_3
&
mask
set_data
=
edge_strength2
[
mask
]
if
len
(
set_data
)
==
0
:
return
0
return
int
(
mask_mean
*
1000
)
>
Thresh
mean
=
np
.
mean
(
set_data
)
gradient1
=
np
.
sum
((
set_data
-
mean
)
**
2
)
set_data2
=
edge_strength3
[
mask
]
# if len(set_data2) == 0:
# return 0
mean
=
np
.
mean
(
set_data2
)
gradient2
=
np
.
sum
((
set_data2
-
mean
)
**
2
)
gradient
=
(
gradient1
+
gradient2
/
2
)
return
int
(
gradient
.
item
())
except
Exception
as
e
:
AppUtils
.
print_Exception
(
e
)
return
0
...
...
MicScanSystemLeiCa.py
View file @
e0887ee2
...
...
@@ -24,17 +24,17 @@ if __name__ == "__main__":
loop
=
QEventLoop
(
app
)
asyncio
.
set_event_loop
(
loop
)
DaemonController
.
Manager
=
loop
from
AppContexts.Contexts
import
*
from
AppUtils.AppUtils
import
AppUtils
from
AppUtils.AppConvert
import
AppConvert
from
AppSettings.AppSetting
import
AppConfig
AppConfig
.
reload
()
if
AppUtils
.
IsServerAvailable
(
AppConfig
.
WebConfig
.
API
):
from
AppUtils.DataBaseDeploy
import
DataBaseDeploy
,
ChamberStatusModel
,
TrayModel
DataBaseDeploy
.
init
()
from
AppSettings.SystemSetting
import
SystemConfig
SystemConfig
.
UpdateMaxLocation
()
TrayModel
.
UpdateMaxLocation
()
from
AppSettings.AppSetting
import
AppConfig
SystemConfig
.
setAsyncioLoop
(
loop
)
AppConfig
.
reload
()
from
AppCore.AppManagers.HardWareManager
import
HardWareManager
from
AppHeadEnd.ControlWindows.MainController
import
MainController
from
AppBackEnd.AppScanningFlow
import
AppScanningFlow
,
AppBusinessInterface
...
...
@@ -51,3 +51,6 @@ if __name__ == "__main__":
DaemonController
.
Start
(
loop
)
sys
.
exit
(
app
.
exec
())
else
:
print
(
"端口被占用,请检查!"
)
sys
.
exit
()
\ No newline at end of file
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