Merge branch 'issue64' into devel
This commit is contained in:
commit
3cb9a61270
4 changed files with 152 additions and 49 deletions
|
|
@ -119,6 +119,54 @@ and add it after the JP2 header box, but before the codestream box ::
|
||||||
. (truncated)
|
. (truncated)
|
||||||
.
|
.
|
||||||
|
|
||||||
|
Create an image with an alpha layer?
|
||||||
|
====================================
|
||||||
|
|
||||||
|
OpenJPEG can create JP2 files with more than 3 components, but by default, any
|
||||||
|
extra components are not described. In order to specify an alpha layer as such,
|
||||||
|
we need to rewrap such an image in a set of boxes that includes a channel
|
||||||
|
definition box.
|
||||||
|
|
||||||
|
This example is based on SciPy example code found at
|
||||||
|
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . ::
|
||||||
|
|
||||||
|
>>> import numpy as np
|
||||||
|
>>> import glymur
|
||||||
|
>>> from glymur import Jp2k
|
||||||
|
>>> rgb = Jp2k(glymur.data.goodstuff()).read()
|
||||||
|
>>> lx, ly = rgb.shape[0:2]
|
||||||
|
>>> X, Y = np.ogrid[0:lx, 0:ly]
|
||||||
|
>>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2
|
||||||
|
>>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8)
|
||||||
|
>>> alpha[mask] = 0
|
||||||
|
>>> rgba = np.concatenate((rgb, alpha), axis=2)
|
||||||
|
>>> jp2 = Jp2k('tmp.jp2', 'wb')
|
||||||
|
>>> jp2.write(rgba)
|
||||||
|
|
||||||
|
The first three channels are color channels, but the fourth will be identified
|
||||||
|
as an alpha channel::
|
||||||
|
|
||||||
|
>>> from glymur.core import COLOR, OPACITY
|
||||||
|
>>> ctype = [COLOR, COLOR, COLOR, OPACITY]
|
||||||
|
|
||||||
|
And finally we have to specify just exactly how each channel is to be
|
||||||
|
interpreted. The color channels are straightforward, but the alpha channel
|
||||||
|
in this case is to be applied against the entire image (it is possible to
|
||||||
|
apply an alpha channel to a single color channel, but we aren't doing that). ::
|
||||||
|
|
||||||
|
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
|
||||||
|
>>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
|
||||||
|
>>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc)
|
||||||
|
|
||||||
|
It's easiest to take the existing jp2 jacket and just add the channel
|
||||||
|
definition box in the appropriate spot. The channel definition box **must**
|
||||||
|
go into the jp2 header box, and then we can rewrap the image. ::
|
||||||
|
|
||||||
|
>>> boxes = jp2.box # The box attribute is the list of JP2 boxes
|
||||||
|
>>> boxes[2].box.append(cdef)
|
||||||
|
>>> jp2_rgba = jp2.wrap("goodstuff_rgba.jp2", boxes=boxes)
|
||||||
|
|
||||||
|
|
||||||
Work with XMP UUIDs?
|
Work with XMP UUIDs?
|
||||||
====================
|
====================
|
||||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,14 @@ _COLORSPACE_MAP_DISPLAY = {
|
||||||
ROMM_RGB: 'ROMM-RGB'}
|
ROMM_RGB: 'ROMM-RGB'}
|
||||||
|
|
||||||
# enumerated color channel types
|
# enumerated color channel types
|
||||||
_COLOR = 0
|
COLOR = 0
|
||||||
_OPACITY = 1
|
OPACITY = 1
|
||||||
_PRE_MULTIPLIED_OPACITY = 2
|
PRE_MULTIPLIED_OPACITY = 2
|
||||||
_UNSPECIFIED = 65535
|
_UNSPECIFIED = 65535
|
||||||
_COLOR_TYPE_MAP_DISPLAY = {
|
_COLOR_TYPE_MAP_DISPLAY = {
|
||||||
_COLOR: 'color',
|
COLOR: 'color',
|
||||||
_OPACITY: 'opacity',
|
OPACITY: 'opacity',
|
||||||
_PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
||||||
_UNSPECIFIED: 'unspecified'}
|
_UNSPECIFIED: 'unspecified'}
|
||||||
|
|
||||||
# color channel definitions.
|
# color channel definitions.
|
||||||
|
|
@ -58,6 +58,7 @@ RED = 1
|
||||||
GREEN = 2
|
GREEN = 2
|
||||||
BLUE = 3
|
BLUE = 3
|
||||||
GREY = 1
|
GREY = 1
|
||||||
|
WHOLE_IMAGE = 0
|
||||||
|
|
||||||
# enumerated color channel associations
|
# enumerated color channel associations
|
||||||
_COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3},
|
_COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3},
|
||||||
|
|
|
||||||
|
|
@ -426,14 +426,23 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
longname : str
|
longname : str
|
||||||
more verbose description of the box.
|
more verbose description of the box.
|
||||||
index : int
|
index : int
|
||||||
number of the channel
|
number of the channel. Defaults to monotonically increasing sequence,
|
||||||
|
i.e. [0, 1, 2, ...]
|
||||||
channel_type : int
|
channel_type : int
|
||||||
type of the channel
|
type of the channel
|
||||||
association : int
|
association : int
|
||||||
index of the associated color
|
index of the associated color
|
||||||
"""
|
"""
|
||||||
def __init__(self, index, channel_type, association, **kwargs):
|
def __init__(self, index=None, channel_type=None, association=None, **kwargs):
|
||||||
Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition')
|
Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition')
|
||||||
|
|
||||||
|
# channel type and association must be specified.
|
||||||
|
if channel_type is None or association is None:
|
||||||
|
raise IOError("channel_type and association must be specified.")
|
||||||
|
|
||||||
|
if index is None:
|
||||||
|
index = list(range(len(channel_type)))
|
||||||
|
|
||||||
if len(index) != len(channel_type) or len(index) != len(association):
|
if len(index) != len(channel_type) or len(index) != len(association):
|
||||||
msg = "Length of channel definition box inputs must be the same."
|
msg = "Length of channel definition box inputs must be the same."
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
@ -504,8 +513,9 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
channel_type = data[1:num_components * 6:3]
|
channel_type = data[1:num_components * 6:3]
|
||||||
association = data[2:num_components * 6:3]
|
association = data[2:num_components * 6:3]
|
||||||
|
|
||||||
box = ChannelDefinitionBox(index, channel_type, association,
|
box = ChannelDefinitionBox(index=index, channel_type=channel_type,
|
||||||
length=length, offset=offset)
|
association=association, length=length,
|
||||||
|
offset=offset)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import pkg_resources
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
from glymur.jp2box import *
|
from glymur.jp2box import *
|
||||||
|
from glymur.core import COLOR, OPACITY
|
||||||
|
from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE
|
||||||
|
|
||||||
|
|
||||||
# Doc tests should be run as well.
|
# Doc tests should be run as well.
|
||||||
|
|
@ -84,12 +86,19 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_rgb(self):
|
def test_cdef_no_inputs(self):
|
||||||
|
"""channel_type and association are required inputs."""
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
glymur.jp2box.ChannelDefinitionBox()
|
||||||
|
|
||||||
|
def test_rgb_with_index(self):
|
||||||
"""Just regular RGB."""
|
"""Just regular RGB."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
channel_type = [COLOR, COLOR, COLOR]
|
||||||
|
association = [RED, GREEN, BLUE]
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
||||||
channel_type=[0, 0, 0],
|
channel_type=channel_type,
|
||||||
association=[1, 2, 3])
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -101,15 +110,41 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [box.box_id for box in jp2h.box]
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
|
||||||
self.assertEqual(jp2h.box[2].channel_type, (0, 0, 0))
|
self.assertEqual(jp2h.box[2].channel_type,
|
||||||
self.assertEqual(jp2h.box[2].association, (1, 2, 3))
|
(COLOR, COLOR, COLOR))
|
||||||
|
self.assertEqual(jp2h.box[2].association,
|
||||||
|
(RED, GREEN, BLUE))
|
||||||
|
|
||||||
|
def test_rgb(self):
|
||||||
|
"""Just regular RGB, but don't supply the optional index."""
|
||||||
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
channel_type = [COLOR, COLOR, COLOR]
|
||||||
|
association = [RED, GREEN, BLUE]
|
||||||
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
|
self.jp2h.box = boxes
|
||||||
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
jp2h = jp2.box[2]
|
||||||
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2))
|
||||||
|
self.assertEqual(jp2h.box[2].channel_type,
|
||||||
|
(COLOR, COLOR, COLOR))
|
||||||
|
self.assertEqual(jp2h.box[2].association,
|
||||||
|
(RED, GREEN, BLUE))
|
||||||
|
|
||||||
def test_rgba(self):
|
def test_rgba(self):
|
||||||
"""Just regular RGBA."""
|
"""Just regular RGBA."""
|
||||||
j2k = Jp2k(self.four_planes)
|
j2k = Jp2k(self.four_planes)
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2, 3],
|
channel_type = (COLOR, COLOR, COLOR, OPACITY)
|
||||||
channel_type=[0, 0, 0, 1],
|
association = (RED, GREEN, BLUE, WHOLE_IMAGE)
|
||||||
association=[1, 2, 3, 0])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -121,15 +156,16 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [box.box_id for box in jp2h.box]
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
self.assertEqual(jp2h.box[2].index, (0, 1, 2, 3))
|
self.assertEqual(jp2h.box[2].index, (0, 1, 2, 3))
|
||||||
self.assertEqual(jp2h.box[2].channel_type, (0, 0, 0, 1))
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
self.assertEqual(jp2h.box[2].association, (1, 2, 3, 0))
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
def test_bad_rgba(self):
|
def test_bad_rgba(self):
|
||||||
"""R, G, and B must be specified."""
|
"""R, G, and B must be specified."""
|
||||||
j2k = Jp2k(self.four_planes)
|
j2k = Jp2k(self.four_planes)
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2, 3],
|
channel_type = (COLOR, COLOR, OPACITY, OPACITY)
|
||||||
channel_type=[0, 0, 1, 1],
|
association = (RED, GREEN, BLUE, WHOLE_IMAGE)
|
||||||
association=[1, 2, 3, 0])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_rgb, cdef]
|
boxes = [self.ihdr, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -140,9 +176,10 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
def test_grey(self):
|
def test_grey(self):
|
||||||
"""Just regular greyscale."""
|
"""Just regular greyscale."""
|
||||||
j2k = Jp2k(self.one_plane)
|
j2k = Jp2k(self.one_plane)
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0],
|
channel_type = (COLOR,)
|
||||||
channel_type=[0],
|
association = (GREY,)
|
||||||
association=[1])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -154,15 +191,16 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [box.box_id for box in jp2h.box]
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
self.assertEqual(jp2h.box[2].index, (0,))
|
self.assertEqual(jp2h.box[2].index, (0,))
|
||||||
self.assertEqual(jp2h.box[2].channel_type, (0,))
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
self.assertEqual(jp2h.box[2].association, (1,))
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
def test_grey_alpha(self):
|
def test_grey_alpha(self):
|
||||||
"""Just regular greyscale plus alpha."""
|
"""Just regular greyscale plus alpha."""
|
||||||
j2k = Jp2k(self.two_planes)
|
j2k = Jp2k(self.two_planes)
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1],
|
channel_type = (COLOR, OPACITY)
|
||||||
channel_type=[0, 1],
|
association = (GREY, WHOLE_IMAGE)
|
||||||
association=[1, 0])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -174,17 +212,19 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [box.box_id for box in jp2h.box]
|
boxes = [box.box_id for box in jp2h.box]
|
||||||
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
self.assertEqual(boxes, ['ihdr', 'colr', 'cdef'])
|
||||||
self.assertEqual(jp2h.box[2].index, (0, 1))
|
self.assertEqual(jp2h.box[2].index, (0, 1))
|
||||||
self.assertEqual(jp2h.box[2].channel_type, (0, 1))
|
self.assertEqual(jp2h.box[2].channel_type, channel_type)
|
||||||
self.assertEqual(jp2h.box[2].association, (1, 0))
|
self.assertEqual(jp2h.box[2].association, association)
|
||||||
|
|
||||||
def test_bad_grey_alpha(self):
|
def test_bad_grey_alpha(self):
|
||||||
"""A greyscale image with alpha layer must specify Y"""
|
"""A greyscale image with alpha layer must specify a color channel"""
|
||||||
j2k = Jp2k(self.two_planes)
|
j2k = Jp2k(self.two_planes)
|
||||||
|
|
||||||
|
channel_type = (OPACITY, OPACITY)
|
||||||
|
association = (GREY, WHOLE_IMAGE)
|
||||||
|
|
||||||
# This cdef box
|
# This cdef box
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1],
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
channel_type=[1, 1],
|
association=association)
|
||||||
association=[0, 1])
|
|
||||||
boxes = [self.ihdr, self.colr_gr, cdef]
|
boxes = [self.ihdr, self.colr_gr, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, self.jp2c]
|
||||||
|
|
@ -196,9 +236,10 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
"""There can only be one channel definition box in the jp2 header."""
|
"""There can only be one channel definition box in the jp2 header."""
|
||||||
j2k = Jp2k(self.j2kfile)
|
j2k = Jp2k(self.j2kfile)
|
||||||
|
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
channel_type = (COLOR, COLOR, COLOR)
|
||||||
channel_type=[0, 0, 0],
|
association = (RED, GREEN, BLUE)
|
||||||
association=[1, 2, 3])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
|
boxes = [self.ihdr, cdef, self.colr_rgb, cdef]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
|
|
@ -214,9 +255,10 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
boxes = [self.ihdr, self.colr_rgb]
|
boxes = [self.ihdr, self.colr_rgb]
|
||||||
self.jp2h.box = boxes
|
self.jp2h.box = boxes
|
||||||
|
|
||||||
cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
channel_type = (COLOR, COLOR, COLOR)
|
||||||
channel_type=[0, 0, 0],
|
association = (RED, GREEN, BLUE)
|
||||||
association=[1, 2, 3])
|
cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
|
association=association)
|
||||||
|
|
||||||
boxes = [self.jP, self.ftyp, self.jp2h, cdef, self.jp2c]
|
boxes = [self.jP, self.ftyp, self.jp2h, cdef, self.jp2c]
|
||||||
|
|
||||||
|
|
@ -228,18 +270,20 @@ class TestChannelDefinition(unittest.TestCase):
|
||||||
# Channel types are limited to 0, 1, 2, 65535
|
# Channel types are limited to 0, 1, 2, 65535
|
||||||
# Should reject if not all of index, channel_type, association the
|
# Should reject if not all of index, channel_type, association the
|
||||||
# same length.
|
# same length.
|
||||||
|
channel_type = (COLOR, COLOR, 3)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
box = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
channel_type=[0, 0, 3],
|
association=association)
|
||||||
association=[1, 2, 3])
|
|
||||||
|
|
||||||
def test_wrong_lengths(self):
|
def test_wrong_lengths(self):
|
||||||
# Should reject if not all of index, channel_type, association the
|
# Should reject if not all of index, channel_type, association the
|
||||||
# same length.
|
# same length.
|
||||||
|
channel_type = (COLOR, COLOR)
|
||||||
|
association = (RED, GREEN, BLUE)
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
box = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2],
|
box = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type,
|
||||||
channel_type=[0, 0],
|
association=association)
|
||||||
association=[1, 2, 3])
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue