Python : 用matplotlib做游標卡尺模擬

這是我碩士論文要用到的模擬
廢話不多說就直接來了吧

下面是我寫的程式碼
程式碼解釋我哪天有空再來補
'''
matplotlib_animation_sample.py

This is a 1-D vernier scale simmulation.
Before reading codes below, there're some concepts you need to know:
1.accuracy:
    the accuracy of the vernier is calculate by the formula:
        abs(round(main_steps/vernier_steps)-main_steps/vernier_steps)
    means the difference between main_steps/vernier_steps and its closest integer
    <ex>
        if main_staps = 39, vernier_steps = 20
        the accuracy of the vernier is 4-39/29 = 1/20 = 0.05 mm
2.steps and gap:
    in vernier scale mechanism
    "vernier_steps * vernier_gap   =   main_steps * main_gap"
    vernier_steps: how many steps between all vernier scale lines
    main_steps: how many steps between all main scale lines
    vernier_gap:   how many pixels between two vernier scale lines
    main_gap:   how many pixels between two main scale lines
    in general cases
    main_gap   = 1mm = 1*mm_unit
    main_steps = the length of the vernier scale (in mm)
'''
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
import time

#define key parameters
if len(sys.argv)==1:   # default
    main_steps = 39    # total 39 steps between all lines in main scale
    vernier_steps = 20 # total 20 steps between all lines in vernier scale
elif len(sys.argv)==2:
    main_steps = int(sys.argv[1])
    vernier_steps = 20
else:    
    main_steps = int(sys.argv[1])
    vernier_steps = int(sys.argv[2])

mm_unit = 1000       # 1 mm contains 1000 pixels
main_gap = mm_unit # main scale's accuracy is 1mm 

#parameters follow by key parameters and other global variables
vernier_gap = int(main_steps*main_gap/vernier_steps) # design to be int
line_height = 5
frame_size = main_steps*main_gap #use vernier length as the frame_size
x0 = 0
vernier_pos = 0
vernier_accuracy = 0
vernier_accuracy_dig = 0

#prepare plotting objects
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

def drawMainScale(plt_pos,frame_size):
    global x0
    ydata = [0,line_height]
    line_list = []
    x0 = int(math.floor(plt_pos))
    for x in range(x0,x0+frame_size+1):
        xdata = [x, x] #start and end pt have same x
        ln, = ax1.plot(xdata,ydata)
        ln.set_color('black')
        line_list.append(ln)
    return line_list

def drawVernierScale(frame_size):
    ydata = [0,line_height]
    line_list = []
    for x in range(0,frame_size+1):
        xdata = [x, x] #both start and end pt are in same x
        ln, = ax2.plot(xdata,ydata)
        ln.set_color('black')
        line_list.append(ln)
    return line_list
    
def drawMatch(pos):
    vernier_match = pos
    #print('vernier_match:', vernier_match)
    dots = 100
    xdata = [vernier_match]*dots
    ydata = np.linspace(0,line_height,dots,endpoint=True)
    ln, = ax2.plot(xdata,ydata,'rs')
    return ln
            
def init():
    global frame_size,vernier_steps,vernier_accuracy,vernier_accuracy_dig
    if main_steps%vernier_steps==0:
        print('your vernier scale is useless')
        quit()
    vernier_accuracy = abs(round(main_steps/vernier_steps)-main_steps/vernier_steps)
    vernier_accuracy_copy = vernier_accuracy
    while True:
        vernier_accuracy_copy = vernier_accuracy_copy*10
        vernier_accuracy_dig += 1
        if vernier_accuracy_copy >=1:
            break
    print('vernier_accuracy_dig:',vernier_accuracy_dig)
    ax1.set_xlim(0, main_steps)
    ax1.set_ylim(0, 6)    
    ax2.set_xlim(0, vernier_steps)
    ax2.set_ylim(0, 6)
    line_list = drawVernierScale(vernier_steps)
    return line_list

def update(i): #i is an int from 0 to frames-1, and keep looping
    ax1.clear() #avoid memory-leak
    ax2.clear()
    ax1.set_xlim(0, main_steps)
    ax1.set_ylim(0, 6)    
    ax2.set_xlim(0, vernier_steps)
    ax2.set_ylim(0, 6)
    global x0, vernier_pos
    plt_pos = round(i/main_gap,2)
    ax1.set_xlim(plt_pos, plt_pos + main_steps) 
    line_list = drawVernierScale(vernier_steps)
    line_list.extend(drawMainScale(plt_pos, main_steps))
    match = 0
    for x in range(x0,x0+main_steps):
        if (main_gap*x-i)%vernier_gap==0 :
            n = (main_gap*x-i)/vernier_gap #frame is moving, so -i is needed
            if n==0:
                #x0 is the range at main scale that align by vernier zero 
                vernier_pos = x0
            else:
                #align pos - distance between align pos and vernier zero
                vernier_pos = x -n*vernier_gap/mm_unit
            line_list.append(drawMatch(n))
            print('i:',i,'x0:',x0,'x:',x,'n:',n,'main_gap*x-i:',main_gap*x-i,'n*vernier_gap/mm_unit',n*vernier_gap/mm_unit,'vernier_pos',vernier_pos)
            match += 1
    analog_pos = i/mm_unit
    vernier_pos_str = str(round(vernier_pos,vernier_accuracy_dig))
    vernier_accuracy_str = str(round(vernier_accuracy,vernier_accuracy_dig))
    if match>1:
        ax1.set_title('analog pos: %.3f (mm)\nvernier pos: N/A (match=%d)'
                      %(analog_pos,match))
    else:
        ax1.set_title('analog pos: %.3f (mm)\nvernier pos:%s (mm)\naccuracy:%s (mm)'\
                      %(analog_pos,vernier_pos_str,vernier_accuracy_str))
    return line_list

def main():
    ani = FuncAnimation(fig, update, frames = 200000, interval = 20,
                    init_func=init, blit=False)
    #ani.save('animation.mp4', writer='ffmpeg', fps=30)
    plt.show()
        
if __name__ == '__main__':
    main()        


存起來的mp4檔在這裡
我這邊是用主尺39 副尺20(即是一般游標卡尺的規格)來做測試影片



紅色的線是對齊的時候
對齊到主尺的副尺刻度線


好了,今天的筆記到此結束
希望有幫助未來遺忘這些的自己,以及需要的人

留言

這個網誌中的熱門文章

python的list與numpy的array和matrix的關係