machinevision-toolbox-python 2.0.1__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {machinevision_toolbox_python-2.0.1.dist-info → machinevision_toolbox_python-2.2.0.dist-info}/METADATA +13 -5
- {machinevision_toolbox_python-2.0.1.dist-info → machinevision_toolbox_python-2.2.0.dist-info}/RECORD +23 -33
- {machinevision_toolbox_python-2.0.1.dist-info → machinevision_toolbox_python-2.2.0.dist-info}/WHEEL +1 -1
- machinevisiontoolbox/BundleAdjust.py +1 -1
- machinevisiontoolbox/Camera.py +95 -80
- machinevisiontoolbox/ImageBlobs.py +10 -4
- machinevisiontoolbox/ImageCore.py +105 -97
- machinevisiontoolbox/ImageFiducials.py +62 -10
- machinevisiontoolbox/ImageIO.py +5 -1
- machinevisiontoolbox/ImageLineFeatures.py +9 -1
- machinevisiontoolbox/ImagePointFeatures.py +29 -8
- machinevisiontoolbox/ImageProcessing.py +1 -1
- machinevisiontoolbox/ImageRegionFeatures.py +25 -6
- machinevisiontoolbox/ImageWholeFeatures.py +113 -72
- machinevisiontoolbox/Sources.py +1 -1
- machinevisiontoolbox/__init__.py +1 -1
- machinevisiontoolbox/base/imageio.py +68 -33
- machinevisiontoolbox/bin/_iminteractive.py +461 -0
- machinevisiontoolbox/bin/bagtool.py +120 -6
- machinevisiontoolbox/bin/imtool.py +34 -5
- machinevisiontoolbox/bin/mvtbtool.py +93 -18
- machinevisiontoolbox/ImageCore.docstring +0 -2877
- machinevisiontoolbox/aruco0.pdf +0 -0
- machinevisiontoolbox/aruco50.pdf +0 -0
- machinevisiontoolbox/bin/labeltool.py +0 -147
- machinevisiontoolbox/blocks/Icons/icons.sh +0 -4
- machinevisiontoolbox/cvfuncs.py +0 -40
- machinevisiontoolbox/docbugs.py +0 -47
- machinevisiontoolbox/newcameras.py +0 -55
- machinevisiontoolbox/test_skimage.py +0 -12
- machinevisiontoolbox/testblobplots.py +0 -17
- machinevisiontoolbox/x.json +0 -296
- {machinevision_toolbox_python-2.0.1.dist-info → machinevision_toolbox_python-2.2.0.dist-info}/entry_points.txt +0 -0
- {machinevision_toolbox_python-2.0.1.dist-info → machinevision_toolbox_python-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,6 +11,7 @@ import cv2
|
|
|
11
11
|
import matplotlib.pyplot as plt
|
|
12
12
|
import numpy as np
|
|
13
13
|
import scipy as sp
|
|
14
|
+
from matplotlib.axes import Axes
|
|
14
15
|
from matplotlib.patches import Polygon
|
|
15
16
|
from matplotlib.ticker import ScalarFormatter
|
|
16
17
|
from spatialmath import SE3, base
|
|
@@ -519,6 +520,11 @@ class ImageWholeFeaturesMixin(_ImageBase if TYPE_CHECKING else object):
|
|
|
519
520
|
.. deprecated:: 2.0.0
|
|
520
521
|
Use ``hist().cdf`` instead.
|
|
521
522
|
"""
|
|
523
|
+
warnings.warn(
|
|
524
|
+
"Deprecated in 2.0.0: use hist().cdf instead of ncdf.",
|
|
525
|
+
DeprecationWarning,
|
|
526
|
+
stacklevel=2,
|
|
527
|
+
)
|
|
522
528
|
hist = self._default_hist()
|
|
523
529
|
return hist.cdf
|
|
524
530
|
|
|
@@ -1193,26 +1199,31 @@ class Histogram:
|
|
|
1193
1199
|
:rtype: ndarray(N) or ndarray(N,P)
|
|
1194
1200
|
|
|
1195
1201
|
.. deprecated:: 2.0.0
|
|
1196
|
-
Use ``
|
|
1202
|
+
Use ``cdf`` instead.
|
|
1197
1203
|
"""
|
|
1204
|
+
warnings.warn(
|
|
1205
|
+
"Deprecated in 2.0.0: use .cdf instead of .ncdf.",
|
|
1206
|
+
DeprecationWarning,
|
|
1207
|
+
stacklevel=2,
|
|
1208
|
+
)
|
|
1198
1209
|
return self.cdf
|
|
1199
1210
|
|
|
1200
1211
|
def plot(
|
|
1201
1212
|
self,
|
|
1202
|
-
type="frequency",
|
|
1203
|
-
block=False,
|
|
1204
|
-
filled=None,
|
|
1205
|
-
stats=True,
|
|
1206
|
-
style="stack",
|
|
1207
|
-
cursor=False,
|
|
1208
|
-
alpha=0.5,
|
|
1209
|
-
title=None,
|
|
1210
|
-
log=False,
|
|
1211
|
-
samescale=False,
|
|
1212
|
-
ax=None,
|
|
1213
|
-
bar=None,
|
|
1214
|
-
**kwargs,
|
|
1215
|
-
):
|
|
1213
|
+
type: str = "frequency",
|
|
1214
|
+
block: bool = False,
|
|
1215
|
+
filled: bool | None = None,
|
|
1216
|
+
stats: bool = True,
|
|
1217
|
+
style: str = "stack",
|
|
1218
|
+
cursor: bool = False,
|
|
1219
|
+
alpha: float = 0.5,
|
|
1220
|
+
title: str | None = None,
|
|
1221
|
+
log: bool = False,
|
|
1222
|
+
samescale: bool = False,
|
|
1223
|
+
ax: Axes | None = None,
|
|
1224
|
+
bar: bool | None = None,
|
|
1225
|
+
**kwargs: Any,
|
|
1226
|
+
) -> None:
|
|
1216
1227
|
"""
|
|
1217
1228
|
Plot histogram
|
|
1218
1229
|
|
|
@@ -1343,38 +1354,9 @@ class Histogram:
|
|
|
1343
1354
|
samescale = True
|
|
1344
1355
|
|
|
1345
1356
|
# figure vertical axis labels and scaling based on histogram type
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
maxy = np.max(y)
|
|
1350
|
-
else:
|
|
1351
|
-
maxy = np.max(y, axis=0)
|
|
1352
|
-
ylabel1 = "frequency"
|
|
1353
|
-
ylabel2 = "frequency"
|
|
1354
|
-
if filled is None:
|
|
1355
|
-
filled = True
|
|
1356
|
-
elif type in ("pdf", "probability"):
|
|
1357
|
-
y = self.pdf
|
|
1358
|
-
if samescale:
|
|
1359
|
-
maxy = np.max(y)
|
|
1360
|
-
else:
|
|
1361
|
-
maxy = np.max(y, axis=0)
|
|
1362
|
-
ylabel1 = "PDF"
|
|
1363
|
-
ylabel2 = "probability density"
|
|
1364
|
-
elif type in ("cf", "cumulative"):
|
|
1365
|
-
y = self.cf
|
|
1366
|
-
maxy = y[
|
|
1367
|
-
-1, 0
|
|
1368
|
-
] # last row values are all the same, total number of pixels in plane
|
|
1369
|
-
ylabel1 = "cumulative frequency"
|
|
1370
|
-
ylabel2 = "cumulative frequency"
|
|
1371
|
-
elif type in ("cdf", "normalized"):
|
|
1372
|
-
y = self.cdf
|
|
1373
|
-
maxy = 1
|
|
1374
|
-
ylabel1 = "CDF"
|
|
1375
|
-
ylabel2 = "normalized cumulative frequency"
|
|
1376
|
-
else:
|
|
1377
|
-
raise ValueError("unknown type")
|
|
1357
|
+
y, maxy, ylabel1, ylabel2 = self._compute_plot_series(type, samescale)
|
|
1358
|
+
if type == "frequency" and filled is None:
|
|
1359
|
+
filled = True
|
|
1378
1360
|
|
|
1379
1361
|
if self.nplanes == 1:
|
|
1380
1362
|
y = y[..., np.newaxis]
|
|
@@ -1391,17 +1373,6 @@ class Histogram:
|
|
|
1391
1373
|
if self.nplanes == 1:
|
|
1392
1374
|
hist_counts = hist_counts[..., np.newaxis]
|
|
1393
1375
|
|
|
1394
|
-
def plane_stats(counts: np.ndarray) -> tuple[float | None, float | None]:
|
|
1395
|
-
total = float(np.sum(counts))
|
|
1396
|
-
if total <= 0:
|
|
1397
|
-
return None, None
|
|
1398
|
-
|
|
1399
|
-
mean = float(np.sum(self.x * counts) / total)
|
|
1400
|
-
cdf = np.cumsum(counts)
|
|
1401
|
-
median_idx = int(np.searchsorted(cdf, 0.5 * total, side="left"))
|
|
1402
|
-
median = float(self.x[min(median_idx, len(self.x) - 1)])
|
|
1403
|
-
return mean, median
|
|
1404
|
-
|
|
1405
1376
|
if self._sorted:
|
|
1406
1377
|
xrange = (self.x[0], self.x[-1])
|
|
1407
1378
|
xlabel = "bin rank"
|
|
@@ -1448,7 +1419,7 @@ class Histogram:
|
|
|
1448
1419
|
ScalarFormatter(useOffset=False, useMathText=True)
|
|
1449
1420
|
)
|
|
1450
1421
|
if stats:
|
|
1451
|
-
mean, median =
|
|
1422
|
+
mean, median = self._histogram_plane_stats(hist_counts[:, i])
|
|
1452
1423
|
if mean is not None and median is not None:
|
|
1453
1424
|
ax.axvline(
|
|
1454
1425
|
mean,
|
|
@@ -1549,18 +1520,7 @@ class Histogram:
|
|
|
1549
1520
|
x = np.r_[xrange[0], x, xrange[1]]
|
|
1550
1521
|
_, ax = plt.subplots(1, 1)
|
|
1551
1522
|
|
|
1552
|
-
patchcolor =
|
|
1553
|
-
goodcolors = [c for c in "rgbykcm"]
|
|
1554
|
-
if self.colordict is None:
|
|
1555
|
-
self.colordict = {c: i for i, c in enumerate(goodcolors[:n])}
|
|
1556
|
-
colors = list(self.colordict.keys())
|
|
1557
|
-
patchcolor = [c.lower() for c in colors]
|
|
1558
|
-
else:
|
|
1559
|
-
for color, i in self.colordict.items():
|
|
1560
|
-
if color.lower() in "rgbykcm":
|
|
1561
|
-
patchcolor.append(color.lower())
|
|
1562
|
-
else:
|
|
1563
|
-
patchcolor.append(goodcolors.pop(0))
|
|
1523
|
+
colors, patchcolor = self._resolve_overlay_colors(n, colors)
|
|
1564
1524
|
|
|
1565
1525
|
if filled:
|
|
1566
1526
|
for i in range(n):
|
|
@@ -1580,7 +1540,7 @@ class Histogram:
|
|
|
1580
1540
|
ax.plot(x, np.r_[0, y[:, i], 0], color=patchcolor[i], **kwargs)
|
|
1581
1541
|
|
|
1582
1542
|
if stats:
|
|
1583
|
-
mean, median =
|
|
1543
|
+
mean, median = self._histogram_plane_stats(np.sum(hist_counts, axis=1))
|
|
1584
1544
|
if mean is not None and median is not None:
|
|
1585
1545
|
ax.axvline(
|
|
1586
1546
|
mean,
|
|
@@ -1617,6 +1577,87 @@ class Histogram:
|
|
|
1617
1577
|
set_window_title(title)
|
|
1618
1578
|
safe_plt_show(block=block)
|
|
1619
1579
|
|
|
1580
|
+
def _compute_plot_series(
|
|
1581
|
+
self, type: str, samescale: bool
|
|
1582
|
+
) -> tuple[np.ndarray, np.ndarray | float, str, str]:
|
|
1583
|
+
"""Compute plot()'s y-values, y-axis max, and y-axis labels for a
|
|
1584
|
+
given ``type``. Pure computation over self.h/self.pdf/self.cf/
|
|
1585
|
+
self.cdf -- no mutation."""
|
|
1586
|
+
if type == "frequency":
|
|
1587
|
+
y = self.h
|
|
1588
|
+
if samescale:
|
|
1589
|
+
maxy = np.max(y)
|
|
1590
|
+
else:
|
|
1591
|
+
maxy = np.max(y, axis=0)
|
|
1592
|
+
ylabel1 = "frequency"
|
|
1593
|
+
ylabel2 = "frequency"
|
|
1594
|
+
elif type in ("pdf", "probability"):
|
|
1595
|
+
y = self.pdf
|
|
1596
|
+
if samescale:
|
|
1597
|
+
maxy = np.max(y)
|
|
1598
|
+
else:
|
|
1599
|
+
maxy = np.max(y, axis=0)
|
|
1600
|
+
ylabel1 = "PDF"
|
|
1601
|
+
ylabel2 = "probability density"
|
|
1602
|
+
elif type in ("cf", "cumulative"):
|
|
1603
|
+
y = self.cf
|
|
1604
|
+
maxy = y[
|
|
1605
|
+
-1, 0
|
|
1606
|
+
] # last row values are all the same, total number of pixels in plane
|
|
1607
|
+
ylabel1 = "cumulative frequency"
|
|
1608
|
+
ylabel2 = "cumulative frequency"
|
|
1609
|
+
elif type in ("cdf", "normalized"):
|
|
1610
|
+
y = self.cdf
|
|
1611
|
+
maxy = 1
|
|
1612
|
+
ylabel1 = "CDF"
|
|
1613
|
+
ylabel2 = "normalized cumulative frequency"
|
|
1614
|
+
else:
|
|
1615
|
+
raise ValueError("unknown type")
|
|
1616
|
+
|
|
1617
|
+
return y, maxy, ylabel1, ylabel2
|
|
1618
|
+
|
|
1619
|
+
def _histogram_plane_stats(
|
|
1620
|
+
self, counts: np.ndarray
|
|
1621
|
+
) -> tuple[float | None, float | None]:
|
|
1622
|
+
"""Mean and median of a single plane's histogram counts, or
|
|
1623
|
+
(None, None) if the plane is empty. Used by both plot()'s stack
|
|
1624
|
+
and overlay styles."""
|
|
1625
|
+
total = float(np.sum(counts))
|
|
1626
|
+
if total <= 0:
|
|
1627
|
+
return None, None
|
|
1628
|
+
|
|
1629
|
+
mean = float(np.sum(self.x * counts) / total)
|
|
1630
|
+
cdf = np.cumsum(counts)
|
|
1631
|
+
median_idx = int(np.searchsorted(cdf, 0.5 * total, side="left"))
|
|
1632
|
+
median = float(self.x[min(median_idx, len(self.x) - 1)])
|
|
1633
|
+
return mean, median
|
|
1634
|
+
|
|
1635
|
+
def _resolve_overlay_colors(
|
|
1636
|
+
self, n: int, colors: list[str]
|
|
1637
|
+
) -> tuple[list[str], list[str]]:
|
|
1638
|
+
"""Resolve per-plane display colors for plot()'s style="overlay".
|
|
1639
|
+
|
|
1640
|
+
If self.colordict is unset, assigns a default one as a side effect
|
|
1641
|
+
(preserved intentionally -- this is existing behavior, not
|
|
1642
|
+
introduced by extracting this into its own method) and returns the
|
|
1643
|
+
colors derived from it; otherwise maps the existing colordict's
|
|
1644
|
+
color names onto a fallback palette for any non-standard names.
|
|
1645
|
+
"""
|
|
1646
|
+
patchcolor = []
|
|
1647
|
+
goodcolors = [c for c in "rgbykcm"]
|
|
1648
|
+
if self.colordict is None:
|
|
1649
|
+
self.colordict = {c: i for i, c in enumerate(goodcolors[:n])}
|
|
1650
|
+
colors = list(self.colordict.keys())
|
|
1651
|
+
patchcolor = [c.lower() for c in colors]
|
|
1652
|
+
else:
|
|
1653
|
+
for color, i in self.colordict.items():
|
|
1654
|
+
if color.lower() in "rgbykcm":
|
|
1655
|
+
patchcolor.append(color.lower())
|
|
1656
|
+
else:
|
|
1657
|
+
patchcolor.append(goodcolors.pop(0))
|
|
1658
|
+
|
|
1659
|
+
return colors, patchcolor
|
|
1660
|
+
|
|
1620
1661
|
def peaks(self, **kwargs: Any) -> np.ndarray | list[np.ndarray]:
|
|
1621
1662
|
r"""
|
|
1622
1663
|
Histogram peaks
|
machinevisiontoolbox/Sources.py
CHANGED
machinevisiontoolbox/__init__.py
CHANGED
|
@@ -707,13 +707,14 @@ def idisp(
|
|
|
707
707
|
|
|
708
708
|
# display the image
|
|
709
709
|
if len(im.shape) == 3:
|
|
710
|
-
#
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if colororder
|
|
714
|
-
|
|
715
|
-
h = ax.imshow(
|
|
710
|
+
# color image, display in RGB or RGBA order; imshow handles alpha
|
|
711
|
+
# natively for both uint8 (0-255) and float (0.0-1.0) arrays
|
|
712
|
+
target = "RGBA" if "A" in colororder else "RGB"
|
|
713
|
+
idx = [colororder.find(c) for c in target if colororder.find(c) >= 0]
|
|
714
|
+
im_rgb = im[:, :, idx] # reorder planes → RGB or RGBA
|
|
715
|
+
h = ax.imshow(im_rgb, norm=norm, cmap=cmap, **options)
|
|
716
716
|
else:
|
|
717
|
+
# monochrome image, display in indexed mode, where pixel value is mapped through the colormap
|
|
717
718
|
if norm is None:
|
|
718
719
|
# exclude NaN values
|
|
719
720
|
if vrange is None:
|
|
@@ -889,7 +890,19 @@ def idisp(
|
|
|
889
890
|
# At this point title is guaranteed to be a string
|
|
890
891
|
assert title is not None
|
|
891
892
|
cv2.namedWindow(title, cv2.WINDOW_AUTOSIZE)
|
|
892
|
-
|
|
893
|
+
|
|
894
|
+
if im.ndim == 3:
|
|
895
|
+
# color image, display in BGR order, but allow user to specify color order of input image
|
|
896
|
+
idx = []
|
|
897
|
+
for c in "BGR":
|
|
898
|
+
j = colororder.find(c)
|
|
899
|
+
if j >= 0:
|
|
900
|
+
idx.append(j)
|
|
901
|
+
cv2.imshow(title, im[:, :, idx]) # convert → BGR order and display
|
|
902
|
+
else:
|
|
903
|
+
# monochrome image, display in indexed mode, where pixel value is mapped through the colormap
|
|
904
|
+
cv2.imshow(title, im) # make sure BGR format image
|
|
905
|
+
|
|
893
906
|
cv2.waitKey(1)
|
|
894
907
|
|
|
895
908
|
if fps is not None:
|
|
@@ -1028,7 +1041,7 @@ def iread(
|
|
|
1028
1041
|
headers={"User-Agent": "machinevisiontoolbox-python/1.0"},
|
|
1029
1042
|
)
|
|
1030
1043
|
try:
|
|
1031
|
-
with urllib.request.urlopen(req, context=ctx) as resp:
|
|
1044
|
+
with urllib.request.urlopen(req, context=ctx, timeout=10) as resp:
|
|
1032
1045
|
if resp.status != 200:
|
|
1033
1046
|
raise ValueError(f"HTTP {resp.status} fetching {filename}")
|
|
1034
1047
|
array = np.asarray(bytearray(resp.read()), dtype="uint8")
|
|
@@ -1126,7 +1139,9 @@ def iread_iter(
|
|
|
1126
1139
|
|
|
1127
1140
|
# read a single file
|
|
1128
1141
|
path = Path(mvtb_path_to_datafile("images", path))
|
|
1129
|
-
image = cv2.imread(
|
|
1142
|
+
image = cv2.imread(
|
|
1143
|
+
path.as_posix(), cv2.IMREAD_UNCHANGED
|
|
1144
|
+
) # read BGR or BGRA image, or greyscale if that's what the file contains
|
|
1130
1145
|
if image is None:
|
|
1131
1146
|
raise ValueError(f"Could not read {filename}")
|
|
1132
1147
|
yield (convert(image, **kwargs), str(path))
|
|
@@ -1153,7 +1168,7 @@ def convert(
|
|
|
1153
1168
|
"""
|
|
1154
1169
|
Convert image
|
|
1155
1170
|
|
|
1156
|
-
:param image: input image
|
|
1171
|
+
:param image: input image in BGR or BGRA order, as read by OpenCV
|
|
1157
1172
|
:type image: ndarray(H,W), ndarray(H,W,P)
|
|
1158
1173
|
:param mono: convert to grey scale, synonym for ``grey``
|
|
1159
1174
|
:type mono: bool, optional
|
|
@@ -1207,20 +1222,29 @@ def convert(
|
|
|
1207
1222
|
)
|
|
1208
1223
|
image_original = image
|
|
1209
1224
|
|
|
1225
|
+
# handle color plane order and alpha channel
|
|
1226
|
+
# note that Torch doesn't like views of arrays with non-standard strides, so we use fancy indexing which implicitly makes a copy
|
|
1227
|
+
|
|
1210
1228
|
if image.ndim == 3 and image.shape[2] in (3, 4):
|
|
1211
|
-
#
|
|
1212
|
-
if not alpha:
|
|
1213
|
-
# optionally remove the alpha plane
|
|
1214
|
-
image = image[:, :, :3]
|
|
1229
|
+
# color image, convert to RGB order if requested, otherwise leave in BGR order
|
|
1215
1230
|
if rgb:
|
|
1216
|
-
#
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1231
|
+
# RGB order is more common in Python, but OpenCV reads in BGR order, so convert to RGB
|
|
1232
|
+
if alpha and image.shape[2] == 4:
|
|
1233
|
+
# BGRA→RGBA
|
|
1234
|
+
image = image[:, :, [2, 1, 0, 3]] # BGRA → RGBA
|
|
1235
|
+
colororder = "RGBA"
|
|
1236
|
+
else:
|
|
1237
|
+
# BGR→RGB
|
|
1238
|
+
image = image[:, :, [2, 1, 0]]
|
|
1239
|
+
colororder = "RGB"
|
|
1220
1240
|
else:
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1241
|
+
# leave in BGR order, which is more efficient for OpenCV processing, but less common in Python
|
|
1242
|
+
if alpha and image.shape[2] == 4:
|
|
1243
|
+
colororder = "BGRA"
|
|
1244
|
+
else:
|
|
1245
|
+
# BGRA→BGR
|
|
1246
|
+
image = image[:, :, :3] # BGRA → BGR
|
|
1247
|
+
colororder = "BGR"
|
|
1224
1248
|
|
|
1225
1249
|
mono = mono or gray or grey
|
|
1226
1250
|
if mono and len(image.shape) == 3:
|
|
@@ -1284,32 +1308,43 @@ def iwrite(
|
|
|
1284
1308
|
:return: successful write
|
|
1285
1309
|
:rtype: bool
|
|
1286
1310
|
|
|
1287
|
-
Writes the image ``im`` to ``filename`` using cv2.imwrite(), passing any
|
|
1288
|
-
|
|
1289
|
-
``filename``.
|
|
1311
|
+
Writes the image ``im`` to ``filename`` using cv2.imwrite(), passing any keyword
|
|
1312
|
+
arguments as options. The file type is taken from the extension in ``filename``.
|
|
1290
1313
|
|
|
1291
1314
|
Example:
|
|
1292
1315
|
|
|
1293
1316
|
.. code-block:: python
|
|
1294
1317
|
|
|
1295
|
-
from machinevisiontoolbox import iwrite
|
|
1296
|
-
|
|
1297
|
-
image = np.zeros((20,20)) # 20x20 black image
|
|
1298
|
-
iwrite(image, "black.png")
|
|
1318
|
+
from machinevisiontoolbox import iwrite import numpy as np image =
|
|
1319
|
+
np.zeros((20,20)) # 20x20 black image iwrite(image, "black.png")
|
|
1299
1320
|
|
|
1300
1321
|
|
|
1301
1322
|
.. note::
|
|
1302
1323
|
- supports 8-bit greyscale and color images
|
|
1303
1324
|
- supports uint16 for PNG, JPEG 2000, and TIFF formats
|
|
1304
1325
|
- supports float32
|
|
1305
|
-
-
|
|
1326
|
+
- supports alpha channel for PNG and TIFF formats
|
|
1327
|
+
- color image planes, with planes specified by ``colororder``, are reordered to
|
|
1328
|
+
OpenCV order: BGR or BGRA. For example, if ``colororder`` is "GRBA", the planes
|
|
1329
|
+
are reordered to "BGRA" for writing. If ``colororder`` is "BGR" or "BGRA", no reordering is done.
|
|
1306
1330
|
|
|
1307
|
-
:seealso: :func:`iread` `cv2.imwrite
|
|
1331
|
+
:seealso: :func:`iread` `cv2.imwrite
|
|
1332
|
+
<https://docs.opencv.org/4.x/d4/da8/group__imgcodecs.html#gabbc7ef1aa2edfaa87772f1202d67e0ce>`_
|
|
1308
1333
|
"""
|
|
1309
|
-
if im.ndim > 2
|
|
1310
|
-
#
|
|
1311
|
-
|
|
1334
|
+
if im.ndim > 2:
|
|
1335
|
+
# color image, convert to OpenCV BGR order for writing
|
|
1336
|
+
if im.shape[2] not in (3, 4):
|
|
1337
|
+
raise ValueError("color image must have 3 or 4 planes")
|
|
1338
|
+
if colororder is not None:
|
|
1339
|
+
idx = []
|
|
1340
|
+
for c in "BGRA":
|
|
1341
|
+
j = colororder.find(c)
|
|
1342
|
+
if j >= 0:
|
|
1343
|
+
idx.append(j)
|
|
1344
|
+
im_bgra = im[:, :, idx] # reorder color planes → BGRA
|
|
1345
|
+
return cv2.imwrite(filename, im_bgra, **kwargs)
|
|
1312
1346
|
else:
|
|
1347
|
+
# monochrome image
|
|
1313
1348
|
return cv2.imwrite(filename, im, **kwargs)
|
|
1314
1349
|
|
|
1315
1350
|
|