Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Xavier Thompson
cython
Commits
8105941c
Commit
8105941c
authored
Aug 15, 2011
by
Mark Florisson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support slicing memoryview objects
parent
155a4ef5
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
469 additions
and
96 deletions
+469
-96
Cython/Compiler/CythonScope.py
Cython/Compiler/CythonScope.py
+4
-8
Cython/Compiler/ExprNodes.py
Cython/Compiler/ExprNodes.py
+4
-1
Cython/Compiler/MemoryView.py
Cython/Compiler/MemoryView.py
+16
-9
Cython/Compiler/PyrexTypes.py
Cython/Compiler/PyrexTypes.py
+1
-3
Cython/Utility/MemoryView.pyx
Cython/Utility/MemoryView.pyx
+218
-68
tests/run/memoryview.pyx
tests/run/memoryview.pyx
+73
-0
tests/run/memoryviewattrs.pyx
tests/run/memoryviewattrs.pyx
+13
-7
tests/run/numpy_memoryview.pyx
tests/run/numpy_memoryview.pyx
+140
-0
No files found.
Cython/Compiler/CythonScope.py
View file @
8105941c
...
@@ -83,20 +83,16 @@ class CythonScope(ModuleScope):
...
@@ -83,20 +83,16 @@ class CythonScope(ModuleScope):
#
#
# The view sub-scope
# The view sub-scope
#
#
self
.
viewscope
=
viewscope
=
ModuleScope
(
u'cython.view'
,
self
,
None
)
self
.
viewscope
=
viewscope
=
ModuleScope
(
u'view'
,
self
,
None
)
self
.
declare_module
(
'view'
,
viewscope
,
None
).
as_module
=
viewscope
# Hacky monkey patch
self
.
viewscope
.
global_scope
=
self
.
global_scope
self
.
declare_module
(
'view'
,
viewscope
,
None
)
viewscope
.
is_cython_builtin
=
True
viewscope
.
is_cython_builtin
=
True
viewscope
.
pxd_file_loaded
=
True
viewscope
.
pxd_file_loaded
=
True
cythonview_testscope_utility_code
.
declare_in_scope
(
viewscope
)
cythonview_testscope_utility_code
.
declare_in_scope
(
viewscope
)
view_utility_scope
=
MemoryView
.
view_utility_code
.
declare_in_scope
(
viewscope
)
view_utility_scope
=
MemoryView
.
view_utility_code
.
declare_in_scope
(
viewscope
)
MemoryView
.
memview_fromslice_utility_code
.
from_scope
=
view_utility_scope
#
MemoryView.memview_fromslice_utility_code.from_scope = view_utility_scope
MemoryView
.
memview_fromslice_utility_code
.
declare_in_scope
(
viewscope
)
#
MemoryView.memview_fromslice_utility_code.declare_in_scope(viewscope)
def
create_cython_scope
(
context
,
create_testscope
):
def
create_cython_scope
(
context
,
create_testscope
):
...
...
Cython/Compiler/ExprNodes.py
View file @
8105941c
...
@@ -2452,7 +2452,10 @@ class IndexNode(ExprNode):
...
@@ -2452,7 +2452,10 @@ class IndexNode(ExprNode):
if
isinstance
(
index
,
SliceNode
):
if
isinstance
(
index
,
SliceNode
):
suboffsets_dim
=
i
suboffsets_dim
=
i
self
.
memslice_slice
=
True
self
.
memslice_slice
=
True
axes
.
append
((
access
,
'strided'
))
if
packing
==
'contig'
and
index
.
step
.
is_none
:
axes
.
append
((
access
,
'contig'
))
else
:
axes
.
append
((
access
,
'strided'
))
# Coerce start, stop and step to temps of the right type
# Coerce start, stop and step to temps of the right type
for
attr
in
(
'start'
,
'stop'
,
'step'
):
for
attr
in
(
'start'
,
'stop'
,
'step'
):
...
...
Cython/Compiler/MemoryView.py
View file @
8105941c
...
@@ -412,15 +412,18 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
...
@@ -412,15 +412,18 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
for
temp
in
temps
:
for
temp
in
temps
:
code
.
funcstate
.
release_temp
(
temp
)
code
.
funcstate
.
release_temp
(
temp
)
def
empty_slice
(
pos
):
none
=
ExprNodes
.
NoneNode
(
pos
)
return
ExprNodes
.
SliceNode
(
pos
,
start
=
none
,
stop
=
none
,
step
=
none
)
def
unellipsify
(
indices
,
ndim
):
def
unellipsify
(
indices
,
ndim
):
result
=
[]
result
=
[]
seen_ellipsis
=
False
seen_ellipsis
=
False
for
index
in
indices
:
for
index
in
indices
:
if
isinstance
(
index
,
ExprNodes
.
EllipsisNode
):
if
isinstance
(
index
,
ExprNodes
.
EllipsisNode
):
none
=
ExprNodes
.
NoneNode
(
index
.
pos
)
full_slice
=
empty_slice
(
index
.
pos
)
full_slice
=
ExprNodes
.
SliceNode
(
index
.
pos
,
start
=
none
,
stop
=
none
,
step
=
none
)
if
seen_ellipsis
:
if
seen_ellipsis
:
result
.
append
(
full_slice
)
result
.
append
(
full_slice
)
else
:
else
:
...
@@ -430,6 +433,10 @@ def unellipsify(indices, ndim):
...
@@ -430,6 +433,10 @@ def unellipsify(indices, ndim):
else
:
else
:
result
.
append
(
index
)
result
.
append
(
index
)
if
len
(
result
)
<
ndim
:
nslices
=
ndim
-
len
(
result
)
result
.
extend
([
empty_slice
(
indices
[
-
1
].
pos
)]
*
nslices
)
return
result
return
result
def
get_memoryview_flag
(
access
,
packing
):
def
get_memoryview_flag
(
access
,
packing
):
...
@@ -950,7 +957,7 @@ def _resolve_AttributeNode(env, node):
...
@@ -950,7 +957,7 @@ def _resolve_AttributeNode(env, node):
scope
=
env
scope
=
env
for
modname
in
modnames
:
for
modname
in
modnames
:
mod
=
scope
.
lookup
(
modname
)
mod
=
scope
.
lookup
(
modname
)
if
not
mod
:
if
not
mod
or
not
mod
.
as_module
:
raise
CompileError
(
raise
CompileError
(
node
.
pos
,
"undeclared name not builtin: %s"
%
modname
)
node
.
pos
,
"undeclared name not builtin: %s"
%
modname
)
scope
=
mod
.
as_module
scope
=
mod
.
as_module
...
@@ -1001,8 +1008,8 @@ cython_array_utility_code = load_memview_cy_utility(
...
@@ -1001,8 +1008,8 @@ cython_array_utility_code = load_memview_cy_utility(
context
=
context
,
context
=
context
,
requires
=
[
view_utility_code
])
requires
=
[
view_utility_code
])
memview_fromslice_utility_code
=
load_memview_cy_utility
(
# memview_fromslice_utility_code = load_memview_cy_utility(
"MemviewFromSlice"
,
# "MemviewFromSlice",
context
=
context
,
# context=context,
requires
=
[
view_utility_code
],
# requires=[view_utility_code],
)
# )
\ No newline at end of file
\ No newline at end of file
Cython/Compiler/PyrexTypes.py
View file @
8105941c
...
@@ -488,6 +488,7 @@ class MemoryViewSliceType(PyrexType):
...
@@ -488,6 +488,7 @@ class MemoryViewSliceType(PyrexType):
entry
.
utility_code_definition
=
\
entry
.
utility_code_definition
=
\
MemoryView
.
CopyFuncUtilCode
(
self
,
to_memview
)
MemoryView
.
CopyFuncUtilCode
(
self
,
to_memview
)
elif
attribute
in
(
"is_c_contig"
,
"is_f_contig"
):
# is_c_contig and is_f_contig functions
# is_c_contig and is_f_contig functions
for
(
c_or_f
,
cython_name
)
in
((
'c'
,
'is_c_contig'
),
(
'fortran'
,
'is_f_contig'
)):
for
(
c_or_f
,
cython_name
)
in
((
'c'
,
'is_c_contig'
),
(
'fortran'
,
'is_f_contig'
)):
...
@@ -564,9 +565,6 @@ class MemoryViewSliceType(PyrexType):
...
@@ -564,9 +565,6 @@ class MemoryViewSliceType(PyrexType):
return
True
return
True
def
get_to_py_function
(
self
,
env
,
obj
):
def
get_to_py_function
(
self
,
env
,
obj
):
import
MemoryView
env
.
use_utility_code
(
MemoryView
.
memview_fromslice_utility_code
)
to_py_func
,
from_py_func
=
self
.
dtype_object_conversion_funcs
(
env
)
to_py_func
,
from_py_func
=
self
.
dtype_object_conversion_funcs
(
env
)
to_py_func
=
"(PyObject *(*)(char *)) "
+
to_py_func
to_py_func
=
"(PyObject *(*)(char *)) "
+
to_py_func
from_py_func
=
"(int (*)(char *, PyObject *)) "
+
from_py_func
from_py_func
=
"(int (*)(char *, PyObject *)) "
+
from_py_func
...
...
Cython/Utility/MemoryView.pyx
View file @
8105941c
This diff is collapsed.
Click to expand it.
tests/run/memoryview.pyx
View file @
8105941c
...
@@ -587,3 +587,76 @@ def assign_temporary_to_object(object[:] mslice):
...
@@ -587,3 +587,76 @@ def assign_temporary_to_object(object[:] mslice):
"""
"""
buf
=
mslice
buf
=
mslice
buf
[
1
]
=
{
3
-
2
:
2
+
(
2
*
4
)
-
2
}
buf
[
1
]
=
{
3
-
2
:
2
+
(
2
*
4
)
-
2
}
def
test_slicing
(
arg
):
"""
Test simple slicing
>>> test_slicing(IntMockBuffer("A", range(8 * 14 * 11), shape=(8, 14, 11)))
acquired A
3 9 2
1232 -44 4
-1 -1 -1
released A
Test direct slicing, negative slice oob in dim 2
>>> test_slicing(IntMockBuffer("A", range(1 * 2 * 3), shape=(1, 2, 3)))
acquired A
0 0 2
48 -12 4
-1 -1 -1
released A
Test indirect slicing
>>> L = [[range(k * 12 + j * 4, k * 12 + j * 4 + 4) for j in xrange(3)] for k in xrange(5)]
>>> test_slicing(IntMockBuffer("A", L, shape=(5, 3, 4)))
acquired A
2 0 2
8 -4 4
0 0 -1
released A
"""
cdef
int
[::
view
.
generic
,
::
view
.
generic
,
:]
_a
=
arg
a
=
_a
b
=
a
[
2
:
8
:
2
,
-
4
:
1
:
-
1
,
1
:
3
]
print
b
.
shape
[
0
],
b
.
shape
[
1
],
b
.
shape
[
2
]
print
b
.
strides
[
0
],
b
.
strides
[
1
],
b
.
strides
[
2
]
print
b
.
suboffsets
[
0
],
b
.
suboffsets
[
1
],
b
.
suboffsets
[
2
]
cdef
int
i
,
j
,
k
for
i
in
range
(
b
.
shape
[
0
]):
for
j
in
range
(
b
.
shape
[
1
]):
for
k
in
range
(
b
.
shape
[
2
]):
itemA
=
a
[
2
+
2
*
i
,
-
4
-
j
,
1
+
k
]
itemB
=
b
[
i
,
j
,
k
]
assert
itemA
==
itemB
,
(
i
,
j
,
k
,
itemA
,
itemB
)
def
test_slicing_and_indexing
(
arg
):
"""
>>> a = IntStridedMockBuffer("A", range(10 * 3 * 5), shape=(10, 3, 5))
>>> test_slicing_and_indexing(a)
acquired A
5 2
60 8
126 113
[111]
released A
"""
cdef
int
[:,
:,
:]
_a
=
arg
a
=
_a
b
=
a
[
-
5
:,
1
,
1
::
2
]
c
=
b
[
4
:
1
:
-
1
,
::
-
1
]
d
=
c
[
2
,
1
:
2
]
print
b
.
shape
[
0
],
b
.
shape
[
1
]
print
b
.
strides
[
0
],
b
.
strides
[
1
]
cdef
int
i
,
j
for
i
in
range
(
b
.
shape
[
0
]):
for
j
in
range
(
b
.
shape
[
1
]):
itemA
=
a
[
-
5
+
i
,
1
,
1
+
2
*
j
]
itemB
=
b
[
i
,
j
]
assert
itemA
==
itemB
,
(
i
,
j
,
itemA
,
itemB
)
print
c
[
1
,
1
],
c
[
2
,
0
]
print
[
d
[
i
]
for
i
in
range
(
d
.
shape
[
0
])]
\ No newline at end of file
tests/run/memoryviewattrs.pyx
View file @
8105941c
...
@@ -19,24 +19,30 @@ def test_shape_stride_suboffset():
...
@@ -19,24 +19,30 @@ def test_shape_stride_suboffset():
u'''
u'''
>>> test_shape_stride_suboffset()
>>> test_shape_stride_suboffset()
5 7 11
5 7 11
616 88 8
77 11 1
-1 -1 -1
-1 -1 -1
<BLANKLINE>
5 7 11
5 7 11
8 40 280
1 5 35
-1 -1 -1
-1 -1 -1
<BLANKLINE>
5 7 11
5 7 11
616 88 8
77 11 1
-1 -1 -1
-1 -1 -1
'''
'''
cdef
unsigned
long
[:,:,:]
larr
=
array
((
5
,
7
,
11
),
sizeof
(
unsigned
long
),
'L
'
)
cdef
char
[:,:,:]
larr
=
array
((
5
,
7
,
11
),
1
,
'b
'
)
print
larr
.
shape
[
0
],
larr
.
shape
[
1
],
larr
.
shape
[
2
]
print
larr
.
shape
[
0
],
larr
.
shape
[
1
],
larr
.
shape
[
2
]
print
larr
.
strides
[
0
],
larr
.
strides
[
1
],
larr
.
strides
[
2
]
print
larr
.
strides
[
0
],
larr
.
strides
[
1
],
larr
.
strides
[
2
]
print
larr
.
suboffsets
[
0
],
larr
.
suboffsets
[
1
],
larr
.
suboffsets
[
2
]
print
larr
.
suboffsets
[
0
],
larr
.
suboffsets
[
1
],
larr
.
suboffsets
[
2
]
larr
=
array
((
5
,
7
,
11
),
sizeof
(
unsigned
long
),
'L'
,
mode
=
'fortran'
)
print
larr
=
array
((
5
,
7
,
11
),
1
,
'b'
,
mode
=
'fortran'
)
print
larr
.
shape
[
0
],
larr
.
shape
[
1
],
larr
.
shape
[
2
]
print
larr
.
shape
[
0
],
larr
.
shape
[
1
],
larr
.
shape
[
2
]
print
larr
.
strides
[
0
],
larr
.
strides
[
1
],
larr
.
strides
[
2
]
print
larr
.
strides
[
0
],
larr
.
strides
[
1
],
larr
.
strides
[
2
]
print
larr
.
suboffsets
[
0
],
larr
.
suboffsets
[
1
],
larr
.
suboffsets
[
2
]
print
larr
.
suboffsets
[
0
],
larr
.
suboffsets
[
1
],
larr
.
suboffsets
[
2
]
cdef
unsigned
long
[:,:,:]
c_contig
=
larr
.
copy
()
print
cdef
char
[:,:,:]
c_contig
=
larr
.
copy
()
print
c_contig
.
shape
[
0
],
c_contig
.
shape
[
1
],
c_contig
.
shape
[
2
]
print
c_contig
.
shape
[
0
],
c_contig
.
shape
[
1
],
c_contig
.
shape
[
2
]
print
c_contig
.
strides
[
0
],
c_contig
.
strides
[
1
],
c_contig
.
strides
[
2
]
print
c_contig
.
strides
[
0
],
c_contig
.
strides
[
1
],
c_contig
.
strides
[
2
]
print
c_contig
.
suboffsets
[
0
],
c_contig
.
suboffsets
[
1
],
c_contig
.
suboffsets
[
2
]
print
c_contig
.
suboffsets
[
0
],
c_contig
.
suboffsets
[
1
],
c_contig
.
suboffsets
[
2
]
...
@@ -161,7 +167,7 @@ def test_is_contiguous():
...
@@ -161,7 +167,7 @@ def test_is_contiguous():
print
fort_contig
.
is_c_contig
(),
fort_contig
.
is_f_contig
()
print
fort_contig
.
is_c_contig
(),
fort_contig
.
is_f_contig
()
cdef
int
[:,:,:]
strided
=
fort_contig
cdef
int
[:,:,:]
strided
=
fort_contig
print
strided
.
is_c_contig
(),
strided
.
is_f_contig
()
print
strided
.
is_c_contig
(),
strided
.
is_f_contig
()
print
print
fort_contig
=
fort_contig
.
copy_fortran
()
fort_contig
=
fort_contig
.
copy_fortran
()
print
fort_contig
.
is_c_contig
(),
fort_contig
.
is_f_contig
()
print
fort_contig
.
is_c_contig
(),
fort_contig
.
is_f_contig
()
print
strided
.
is_c_contig
(),
strided
.
is_f_contig
()
print
strided
.
is_c_contig
(),
strided
.
is_f_contig
()
...
...
tests/run/numpy_memoryview.pyx
0 → 100644
View file @
8105941c
# tag: numpy
# mode: run
"""
Test slicing for memoryviews and memoryviewslices
"""
cimport
numpy
as
np
import
numpy
def
get_array
():
# We need to type our array to get a __pyx_get_buffer() that typechecks
# for np.ndarray and calls __getbuffer__ in numpy.pxd
cdef
np
.
ndarray
[
int
,
ndim
=
3
]
a
a
=
numpy
.
arange
(
8
*
14
*
11
).
reshape
(
8
,
14
,
11
)
return
a
a
=
get_array
()
def
ae
(
*
args
):
"assert equals"
for
x
in
args
:
if
x
!=
args
[
0
]:
raise
AssertionError
(
args
)
#
### Test slicing memoryview slices
#
def
test_partial_slicing
(
array
):
"""
>>> test_partial_slicing(a)
"""
cdef
int
[:,
:,
:]
a
=
array
obj
=
array
[
4
]
cdef
int
[:,
:]
b
=
a
[
4
,
:]
cdef
int
[:,
:]
c
=
a
[
4
]
ae
(
b
.
shape
[
0
],
c
.
shape
[
0
],
obj
.
shape
[
0
])
ae
(
b
.
shape
[
1
],
c
.
shape
[
1
],
obj
.
shape
[
1
])
ae
(
b
.
strides
[
0
],
c
.
strides
[
0
],
obj
.
strides
[
0
])
ae
(
b
.
strides
[
1
],
c
.
strides
[
1
],
obj
.
strides
[
1
])
def
test_ellipsis
(
array
):
"""
>>> test_ellipsis(a)
"""
cdef
int
[:,
:,
:]
a
=
array
cdef
int
[:,
:]
b
=
a
[...,
4
]
b_obj
=
array
[...,
4
]
cdef
int
[:,
:]
c
=
a
[
4
,
...]
c_obj
=
array
[
4
,
...]
cdef
int
[:,
:]
d
=
a
[
2
:
8
,
...,
2
]
d_obj
=
array
[
2
:
8
,
...,
2
]
ae
(
tuple
([
b
.
shape
[
i
]
for
i
in
range
(
2
)]),
b_obj
.
shape
)
ae
(
tuple
([
b
.
strides
[
i
]
for
i
in
range
(
2
)]),
b_obj
.
strides
)
for
i
in
range
(
b
.
shape
[
0
]):
for
j
in
range
(
b
.
shape
[
1
]):
ae
(
b
[
i
,
j
],
b_obj
[
i
,
j
])
ae
(
tuple
([
c
.
shape
[
i
]
for
i
in
range
(
2
)]),
c_obj
.
shape
)
ae
(
tuple
([
c
.
strides
[
i
]
for
i
in
range
(
2
)]),
c_obj
.
strides
)
for
i
in
range
(
c
.
shape
[
0
]):
for
j
in
range
(
c
.
shape
[
1
]):
ae
(
c
[
i
,
j
],
c_obj
[
i
,
j
])
ae
(
tuple
([
d
.
shape
[
i
]
for
i
in
range
(
2
)]),
d_obj
.
shape
)
ae
(
tuple
([
d
.
strides
[
i
]
for
i
in
range
(
2
)]),
d_obj
.
strides
)
for
i
in
range
(
d
.
shape
[
0
]):
for
j
in
range
(
d
.
shape
[
1
]):
ae
(
d
[
i
,
j
],
d_obj
[
i
,
j
])
cdef
int
[:]
e
=
a
[...,
5
,
6
]
e_obj
=
array
[...,
5
,
6
]
ae
(
e
.
shape
[
0
],
e_obj
.
shape
[
0
])
ae
(
e
.
strides
[
0
],
e_obj
.
strides
[
0
])
#
### Test slicing memoryview objects
#
def
test_partial_slicing_memoryview
(
array
):
"""
>>> test_partial_slicing_memoryview(a)
"""
cdef
int
[:,
:,
:]
_a
=
array
a
=
_a
obj
=
array
[
4
]
b
=
a
[
4
,
:]
c
=
a
[
4
]
ae
(
b
.
shape
[
0
],
c
.
shape
[
0
],
obj
.
shape
[
0
])
ae
(
b
.
shape
[
1
],
c
.
shape
[
1
],
obj
.
shape
[
1
])
ae
(
b
.
strides
[
0
],
c
.
strides
[
0
],
obj
.
strides
[
0
])
ae
(
b
.
strides
[
1
],
c
.
strides
[
1
],
obj
.
strides
[
1
])
def
test_ellipsis_memoryview
(
array
):
"""
>>> test_ellipsis_memoryview(a)
"""
cdef
int
[:,
:,
:]
_a
=
array
a
=
_a
b
=
a
[...,
4
]
b_obj
=
array
[...,
4
]
c
=
a
[
4
,
...]
c_obj
=
array
[
4
,
...]
d
=
a
[
2
:
8
,
...,
2
]
d_obj
=
array
[
2
:
8
,
...,
2
]
ae
(
tuple
([
b
.
shape
[
i
]
for
i
in
range
(
2
)]),
b_obj
.
shape
)
ae
(
tuple
([
b
.
strides
[
i
]
for
i
in
range
(
2
)]),
b_obj
.
strides
)
for
i
in
range
(
b
.
shape
[
0
]):
for
j
in
range
(
b
.
shape
[
1
]):
ae
(
b
[
i
,
j
],
b_obj
[
i
,
j
])
ae
(
tuple
([
c
.
shape
[
i
]
for
i
in
range
(
2
)]),
c_obj
.
shape
)
ae
(
tuple
([
c
.
strides
[
i
]
for
i
in
range
(
2
)]),
c_obj
.
strides
)
for
i
in
range
(
c
.
shape
[
0
]):
for
j
in
range
(
c
.
shape
[
1
]):
ae
(
c
[
i
,
j
],
c_obj
[
i
,
j
])
ae
(
tuple
([
d
.
shape
[
i
]
for
i
in
range
(
2
)]),
d_obj
.
shape
)
ae
(
tuple
([
d
.
strides
[
i
]
for
i
in
range
(
2
)]),
d_obj
.
strides
)
for
i
in
range
(
d
.
shape
[
0
]):
for
j
in
range
(
d
.
shape
[
1
]):
ae
(
d
[
i
,
j
],
d_obj
[
i
,
j
])
e
=
a
[...,
5
,
6
]
e_obj
=
array
[...,
5
,
6
]
ae
(
e
.
shape
[
0
],
e_obj
.
shape
[
0
])
ae
(
e
.
strides
[
0
],
e_obj
.
strides
[
0
])
\ 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