[python] 基於matplotlib-scalebar庫繪製比例尺

2023-06-28 15:00:38

matplotlib-scalebar是一個Python庫,用於在matplotlib圖形中新增比例尺。它允許使用者指定比例尺的大小、位置、字型和顏色,以及比例尺的單位。該庫支援不同的比例尺單位,例如米、英尺、英寸等。matplotlib-scalebar安裝命令如下:

pip install matplotlib-scalebar

比例尺是一種用於描述圖上線段長度與實際相應線段長度之間關係的方法。其基本公式為:比例尺 = 圖上距離 / 實際距離。比例尺的表示方法可以分為三種:

  1. 數位式,採用數位的比例形式或分數形式來表示比例尺的大小。例如:1:10000或1/10000。
  2. 線段式,在圖上繪製一條線段,並註明圖上該線段所代表的實際距離。
  3. 文字式,用文字描述圖上的距離與實際距離之間的比例關係。例如:圖上每1釐米代表實際距離 100 米。

matplotlib-scalebar僅適用於線段式比例尺的繪製。因為在matplotlib中,我們可以通過文字繪製函數直接在圖上新增數位式或文字式的比例尺。

本文所有程式碼見:Python-Study-Notes

# jupyter notebook環境去除warning
import warnings
warnings.filterwarnings("ignore")
import matplotlib_scalebar
# 列印matplotlib_scalebar版本
print("matplotlib_scalebar version",matplotlib_scalebar.__version__)

import matplotlib as plt
print("matplotlob version",plt.__version__)
matplotlib_scalebar version 0.8.1
matplotlob version 3.5.3

1 使用說明

1.1 快速入門

以下程式碼展示了一個matplotlib-scalebar的使用範例,matplotlib-scalebar提供ScaleBar類來建立預設比例尺:

ScaleBar(dx= 0.08, units= "cm", length_fraction=0.5)

其中dx,units和length_fraction都是基本引數,dx表示圖中每個橫向畫素座標實際代表0.08cm的長度,units表示使用cm釐米作為基準單位,length_fraction=0.5表示預設比例尺長度佔實際繪圖區域橫向總長度的比例為50%。

預設比例尺的含義為:matplotlib_scalebar.scalebar會根據我們預置的比例尺引數圖,挑選合適規格的標準比例尺來表示。如下所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
from matplotlib_scalebar.scalebar import ScaleBar

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.5)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

如上圖所示,比例尺由一根橫線和橫線下的文字標識組成。該比例尺表示圖中橫向方向上,橫線的長度等於實際1dm(分米)。以文中matplotlib-scalebar繪圖程式碼為例說明計算該比例尺的步驟:

  1. 輸入引數:影象橫向畫素個數為256,每個畫素表示0.08cm,預設比例尺佔橫向方向總畫素的length_fraction=0.5。那麼預設比例尺的長度為256*0.08*0.5,也就是10.24cm,共佔橫向128個畫素。
  2. 判斷10.24cm是否能進位換算為預設高一級的長度單位,例如10.24cm可換算為1.024dm。
  3. 判斷1.024達到預設的哪種比例尺數值規格,如1、2、5、10、15等。根據給定值,找出第一個大於等於給定值的規格數值。例如1.024應以比例尺規格值1表示,4.99應以比例尺規格值2表示。
  4. 根據上一步結果,當前比例規格為1dm,那麼比例尺的繪圖長度將從預設長度改為1dm對應的長度。繪圖長度的計算方式為256*0.5*1/1.024,也就是125個畫素。
  5. 在圖中繪製長度為125個畫素,標識為1dm的比例尺。

在matplotlib-scalebar,對於米制單位,預設比例尺數值規格為:

[1, 2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 200, 500, 750]

預設比例尺單位規格為:

{'m': 1.0,
 'Ym': 1e+24,
 'Zm': 1e+21,
 'Em': 1e+18,
 'Pm': 1000000000000000.0,
 'Tm': 1000000000000.0,
 'Gm': 1000000000.0,
 'Mm': 1000000.0,
 'km': 1000.0,
 'dm': 0.1,
 'cm': 0.01,
 'mm': 0.001,
 'µm': 1e-06,
 'um': 1e-06,
 'nm': 1e-09,
 'pm': 1e-12,
 'fm': 1e-15,
 'am': 1e-18,
 'zm': 1e-21,
 'ym': 1e-24}

matplotlib-scalebar關於比例尺的計算詳細函數見matplotlib_scalebar/dimension.py的draw函數。

按照以上比例尺的計算步驟,如果dx= 0.01, units= "m", length_fraction=1。那麼實際應該使用預設數值規格為2,單位規格為m,佔橫向200個畫素的比例尺。如下所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
from matplotlib_scalebar.scalebar import ScaleBar

# 載入matplotlib自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx= 0.01, units= "m", length_fraction=1)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

在前面展示的是表示橫向方向長度的比例尺,如果想建立表示縱向方向的比例尺,則在初始ScaleBar類時設定rotation="vertical"即可。要注意縱向比例尺是根據影象高度來計算的,如下程式碼所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
from matplotlib_scalebar.scalebar import ScaleBar

# 載入自帶圖片資料,並將圖片寬改為512,高改為128,可以對比不設定rotation="vertical"時的效果
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((128, 512))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx=0.01,
                    units="m",
                    length_fraction=1,
                    rotation="vertical",
                    scale_loc="right",
                    border_pad=1,
                    pad=0.5)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

1.2 ScaleBar類說明

ScaleBar類建構函式的引數如下所示:

dx (float): x軸的長度,以當前繪圖單位表示。  
units (str, optional): 標尺的單位。預設為"m"。  
dimension (str, optional): 標尺的屬性維度。預設為"si-length"。  
label (str or None, optional): 標尺的標籤文字。預設為None。  
length_fraction (float, optional): 標尺的長度與總長度的比例。預設為None。  
height_fraction (float, optional): 標尺的高度與總高度的比例。預設為None,該引數已經廢除,使用width_fraction替代。  
width_fraction (float, optional): 標尺的寬度與總寬度的比例。預設為None。  
location (tuple or None, optional): 標尺的位置。預設為None。  
pad (tuple or None, optional): 內邊距。預設為None。  
border_pad (tuple or None, optional): 外邊距。預設為None。  
sep (tuple or None, optional): 標籤文字與標尺之間的間隔。預設為None。  
frameon (bool or None, optional): 是否顯示標尺背景框。預設為None。  
color (str or tuple or None, optional): 標尺的顏色。預設為None。  
box_color (str or tuple or None, optional): 標尺線框的顏色。預設為None。  
box_alpha (float or None, optional): 標尺線框的透明度。預設為None。  
scale_loc (str or None, optional): 標尺放置的位置。預設為None。  
label_loc (str or None, optional): 標籤文字放置的位置。預設為None。  
font_properties (str or None, optional): 字型樣式。預設為None。  
label_formatter (str or None, optional): 標籤文字格式化函數。預設為None,該引數已經廢除,使用scale_formatter替代。  
scale_formatter (str or None, optional): 標尺刻度格式化函數。預設為None。  
fixed_value (float or None, optional): 固定的標尺值。預設為None。  
fixed_units (str or None, optional): 固定的標尺單位。預設為None。  
animated (bool, optional): 是否以動畫的形式進行顯示。預設為False。  
rotation (float or None, optional): 標籤文字的旋轉角度。預設為None。  
bbox_to_anchor (str or tuple or None, optional): 標籤文字的位置基準。預設為None,一些matplotlib_scalebar版本可能不支援該引數。  
bbox_transform (str or None, optional): 標籤文字的變換函數。預設為None,一些matplotlib_scalebar版本可能不支援該引數。 

ScaleBar一些主要引數決定了比例尺的展示效果,下圖展示了ScaleBar主要引數的作用域:

值得注意的是,ScaleBar提供了兩種計算比例尺規格的方式:

  1. 第一種是1.1節提到的計算方式,根據dx、units、length_fraction值建立預置比例尺引數,然後根據這些引數自動確定比例尺的繪製規格標準。推薦使用該方式建立比例尺。
  2. 第二種是直接通過fixed_value和fixed_units確定比例尺的繪製規格標準,然後結合dx引數完成比例尺的繪製,這種情況主要適用於需要設定特定數值。

接下來,對ScaleBar的主要引數進行介紹。

1.2.1 dx, units, dimension

dx為必須輸入引數,表示一個畫素點代表的實際大小。units表示單位,dimension表示單位屬性(所屬單位制),可選的長度單位引數如下表所示:

dimension units
si-length km, m, cm, um
imperial-length in, ft, yd, mi
si-length-reciprocal 1/m, 1/cm
angle deg

如果使用GeoPandas繪製地圖的比例尺則需要根據座標系的型別來確定dx,具體如何在GeoPandas中確定dx見:Python繪製資料地圖3-GeoPandas使用要點

將比例尺的標識改為imperial-length英制長度的範例程式碼如下:


# 載入matplotlib自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

ax.imshow(im, cmap="gray")

scalebar = ScaleBar(dx=0.0315, units="in", dimension="imperial-length", length_fraction=0.25)
ax.add_artist(scalebar)
<matplotlib_scalebar.scalebar.ScaleBar at 0x7fb634727850>

1.2.2 label, label_loc, scale_loc

label設定標尺的標籤文字。label_loc設定標籤文字相對於比例尺的位置,可選值有: bottom, top, left, right, none(不顯示標籤文字)。 scale_loc設定比例尺標註值相對於比例尺的位置,可選值有: bottom, top, left, right, none(不顯示標註文字)。範例程式碼如下:


# 載入matplotlib自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

ax.imshow(im, cmap="gray")

scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, 
                    label="scale bar",label_loc="left", scale_loc="top")
ax.add_artist(scalebar)
<matplotlib_scalebar.scalebar.ScaleBar at 0x7fb6347f1fd0>

1.2.3 length_fraction, width_fraction

length_fraction設定比例尺相對於圖形的長度,如果不指定值,在程式碼內部會以為0.2(20%)賦值。width_fraction設定比例尺相對於圖形的寬度,如果不指定值,在程式碼內部會以為0.01(1%)賦值。本文在1.1節提到過,在這種情況下比例尺標註值只能取以下數位確定的:1、2、5、10、15等。如果需要特定的值,需要指定fixed_value和fixed_units。範例程式碼如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, width_fraction=0.05)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

1.2.4 標尺位置與邊距

  • location:設定圖例的位置,該引數作用與matplotlib的圖例位置設定引數相同,取值可以是:upper right, upper left, lower left, lower right, right, center left, center right, lower center, upper center或center。預設值為None,表示使用matplotlib的預設值。
  • loc:location的別名。
  • pad:內邊距,預設為None,表示使用matplotlib的預設值0.2。
  • border_pad:外邊距,預設為None,表示使用matplotlib的預設值0.1。
  • sep:標籤文字與標尺之間的間隔,預設為None,表示使用matplotlib的預設值5。
  • frameon:是否顯示標尺背景框,預設為None,表示使用matplotlib的預設值True,該背景框預設為白色背景。

範例程式碼如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
# 如果將frameon設定為False,對於當前背景為黑色的圖片需要修改標尺顏色以更好視覺化效果。
scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, 
                    location="upper left", pad = 0.1, border_pad=0.5, 
                    sep=2, frameon=True)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

1.2.5 顏色

matplotlib-scalebar通過color引數設定標尺及標註文字的顏色,通過box_color和box_alpha設定背景框的顏色和透明度。範例程式碼如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, 
                    color="white", box_color = "blue", box_alpha=0.7)
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

1.2.6 font_properties和scale_formatter

font_properties設定標籤文字的字型屬性,具體使用見matplotlib的FontProperties
scale_formatter呼叫類似lambda value, unit: f"{value} {unit}"這類自定義函數來自定義比例尺的標註值,預設為none。

範例程式碼如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar1 = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, 
                   scale_formatter = lambda value, unit: f"scalebar")

scalebar2 = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, location='center left',
                   scale_formatter = lambda value, unit: f"value: {value}/{unit}")
scalebar3 = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, location='center',
                   font_properties={'style':'italic','weight':'bold','size':12})
    
# 新增比例尺
ax.add_artist(scalebar1)
ax.add_artist(scalebar2)
ax.add_artist(scalebar3)

plt.show()

1.2.7 fixed_value, fixed_units

fixed_value和fixed_units用於自定義比例尺標註值,當fixed_value預設為none表示根據dx自動確定比例尺的標註值。比例尺的長度會根據dx和這兩個引數而自動調整。範例程式碼如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))

fig, ax = plt.subplots()
ax.axis("off")

# 繪圖
ax.imshow(im, cmap="gray")

# 建立一個比例尺物件
scalebar = ScaleBar(dx= 0.08, units= "cm", length_fraction=0.25, 
                    fixed_value=0.5, fixed_units= "cm")
# 新增比例尺
ax.add_artist(scalebar)

plt.show()

1.2.8 rotation

rotation表示是基於x軸還是基於y軸建立比例尺。rotation可取horizontal或vertical。如果調整rotation,可能需要調整scale_loc和label_loc以實現合理的比例尺佈局。如果改變rotation的值後,比例尺標註值顯示有問題,可以嘗試升級matplotlib版本解決。rotation預設為None,表示使用matplotlib的預設值。如下:

# 載入自帶圖片資料,並將圖片寬高都修改為256
with cbook.get_sample_data("s1045.ima.gz") as dfile:
    im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
fig, ax = plt.subplots()
ax.axis("off")

ax.imshow(im, cmap="gray")

scalebar = ScaleBar(
    0.08,
    "cm",
    length_fraction=0.25,
    rotation="vertical",
    scale_loc="right",
    border_pad=1,
    pad=0.1,
)
ax.add_artist(scalebar)
<matplotlib_scalebar.scalebar.ScaleBar at 0x7fb63452f790>

2 繪圖範例

plywood-gallery-matplotlib-scalebar提供了一個互動式matplotlib-scalebar的繪圖範例,每個範例給出了不同圖例引數詳細的繪製程式碼,非常推薦學習和使用。繪圖範例內容如下:

總體繪圖效果如下:

以下程式碼展示不同繪圖範例的效果。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib_scalebar.scalebar import ScaleBar
import matplotlib.image as mpimg

# 新增比例尺程式碼
def imshow_bar(im, scalebar,ax):
    ax.axis("off")
    ax.imshow(im)
    ax.add_artist(scalebar)

fig, _ = plt.subplots(figsize=(14, 14))
# 調整子圖間距
plt.subplots_adjust(wspace=0.05, hspace=0.05)

# 圖1
ax = plt.subplot(441)
img = mpimg.imread("image/orange.png")
scalebar = ScaleBar(0.3, "mm", scale_formatter=lambda value, unit: f"{value/5} limo")
imshow_bar(img, scalebar,ax=ax)

# 圖2
ax = plt.subplot(442)
img = mpimg.imread("image/orange.png")
scalebar = ScaleBar(0.3, "mm", border_pad=1)
imshow_bar(img, scalebar,ax=ax)

# 圖3
ax = plt.subplot(443)
img = mpimg.imread("image/green.png")
scalebar = ScaleBar(0.3, "mm", pad=1)
imshow_bar(img, scalebar,ax=ax)

# 圖4
ax = plt.subplot(444)
img = mpimg.imread("image/green.png")
scalebar = ScaleBar(1, "px", dimension="pixel-length", length_fraction=0.3)
imshow_bar(img, scalebar,ax=ax)

# 圖5
ax = plt.subplot(445)
img = mpimg.imread("image/yellow.png")
scalebar = ScaleBar(0.03 / 2.54, "in", dimension="imperial-length", length_fraction=0.3)
imshow_bar(img, scalebar,ax=ax)

# 圖6
ax = plt.subplot(4,4,6)
img = mpimg.imread("image/yellow.png")
scalebar = ScaleBar(0.3, "mm", height_fraction=0.05)
imshow_bar(img, scalebar,ax=ax)

# 圖7
ax = plt.subplot(4,4,7)
img = mpimg.imread("image/purple.png")
scalebar = ScaleBar(0.3, "mm", rotation="vertical")
imshow_bar(img, scalebar,ax=ax)

# 圖8
ax = plt.subplot(4,4,8)
img = mpimg.imread("image/purple.png")
scalebar = ScaleBar(0.3, "mm", color="blue", scale_loc="right")
imshow_bar(img, scalebar,ax=ax)

# 圖9
ax = plt.subplot(4,4,9)
img = mpimg.imread("image/red.png")
scalebar = ScaleBar(0.3, "mm", box_color="skyblue", box_alpha=0.3)
imshow_bar(img, scalebar,ax=ax)

# 圖10
ax = plt.subplot(4,4,10)
img = mpimg.imread("image/red.png")
scalebar = ScaleBar(0.3, "mm", label="Lemon", label_loc="right")
imshow_bar(img, scalebar,ax=ax)

# 圖11
ax = plt.subplot(4,4,11)
img = mpimg.imread("image/zoom1.png")
scalebar = ScaleBar(0.3 / 5, "mm", sep=10)
imshow_bar(img, scalebar,ax=ax)

# 圖12
ax = plt.subplot(4,4,12)
img = mpimg.imread("image/zoom2.png")
scalebar = ScaleBar(0.3 / 100, "mm", label="Lemon", label_loc="bottom")
imshow_bar(img, scalebar,ax=ax)

# 圖13
ax = plt.subplot(4,4,13)
img = mpimg.imread("image/zoom3.png")
scalebar = ScaleBar(0.3 / 10000, "mm", length_fraction=1, font_properties="serif")
imshow_bar(img, scalebar,ax=ax)

# 圖14
ax = plt.subplot(4,4,14)
img = mpimg.imread("image/zoom4.png")
scalebar = ScaleBar(0.3 / 10000000, "mm", frameon=False, label="Lemon")
imshow_bar(img, scalebar,ax=ax)

# 圖15
ax = plt.subplot(4,4,15)
img = mpimg.imread("image/zoom4.png")
scalebar = ScaleBar(0.3 / 10000000, "mm", fixed_units="mm", fixed_value=1e-6, font_properties="monospace", location="lower left")
imshow_bar(img, scalebar,ax=ax)

# 圖16
ax = plt.subplot(4,4,16)
img = mpimg.imread("image/zoom4.png")
scalebar = ScaleBar(0.3 / 10000000, "mm", fixed_units="pm", fixed_value=1000, location="upper left")
imshow_bar(img, scalebar,ax=ax)

# 儲存圖片
plt.savefig("res.jpg",dpi=300)
plt.show()

3 參考