vna-calibrate.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import os
  2. import sys
  3. import warnings
  4. from functools import partial
  5. import pathlib
  6. import skrf
  7. from skrf.network import two_port_reflect, four_oneports_2_twoport
  8. from skrf.calibration import OnePort, TwoPortOnePath, EnhancedResponse
  9. from skrf.media import DefinedGammaZ0
  10. # global variables
  11. cal_dir = pathlib.Path(sys.argv[1])
  12. measurement_dir = pathlib.Path(sys.argv[2])
  13. export_dir = pathlib.Path(sys.argv[3])
  14. def get_standards(medium, port=1):
  15. if port != 1 or port != 2:
  16. raise ValueError
  17. # modeled SOLT
  18. short_model = skrf.Network(cal_dir / "model" / "short.s1p")
  19. if not short_model.exists():
  20. short_model = medium.short()
  21. open_model = skrf.Network(cal_dir / "model" / "open.s1p")
  22. if not open_model.exists():
  23. open_model = medium.open()
  24. load_model = skrf.Network(cal_dir / "model" / "load.s1p")
  25. if not load_model.exists():
  26. load_model = medium.match()
  27. thru_model = skrf.Network(cal_dir / "model" / "thru.s2p")
  28. if not thru_model.exists():
  29. thru_model = medium.thru(nports=2)
  30. # measured SOLT
  31. if (cal_dir / "actual").exists():
  32. cal_dir = cal_dir / "actual"
  33. short_actual = skrf.Network(cal_dir / "short.s1p")
  34. open_actual = skrf.Network(cal_dir / "open.s1p")
  35. load_actual = skrf.Network(cal_dir / "load.s1p")
  36. thru_actual = skrf.Network(cal_dir / "thru.s2p")
  37. if port == 1:
  38. modeled = [short_model, open_model, load_model]
  39. actual = [short_actual, open_actual, load_actual]
  40. elif port == 2:
  41. modeled = [short_model, open_model, load_model]
  42. actual = [short_actual, open_actual, load_actual]
  43. for idx, network in enumerate(modeled):
  44. modeled[idx] = skrf.network.two_port_reflect(network, network)
  45. for idx, network in enumerate(actual):
  46. actual[idx] = skrf.network.two_port_reflect(network, network)
  47. # Thru is a two-port network
  48. modeled.append(thru_model)
  49. actual.append(thru_actual)
  50. freq = open_actual.freq
  51. return frequency, modeled, actual
  52. def replace_filename_segment(f, orig, replace):
  53. suffix = f.suffix
  54. name_segments = f.stem.split("-")
  55. if name_segments[-1] != orig:
  56. raise ValueError
  57. if replace:
  58. name_segments[-1] = replace
  59. else:
  60. del name_segments[-1]
  61. return f.with_name("-".join(name_segments) + suffix)
  62. def one_half_ports_to_two_ports(network):
  63. s11 = network.s11
  64. s22 = s11
  65. try:
  66. s21 = network.s21
  67. s12 = s21
  68. except:
  69. s21 = skrf.Network(frequency=network.f, s=[0] * len(network.f))
  70. s12 = s21
  71. return four_oneports_2_twoport(s11, s12, s21, s22)
  72. def two_port_one_path(dut_forward, dut_backward=None, export_file='/dev/null'):
  73. line = DefinedGammaZ0(frequency=frequency)
  74. frequency, ideals, measured = get_standards(line)
  75. frequency = skrf.frequency.overlap_freq(frequency, dut_forward.frequency)
  76. dut_forward = dut_forward.interpolate(frequency)
  77. dut_backward = dut_backward.interpolate(frequency)
  78. #global short_std, open_std, load_std, thru_std
  79. # pull overlapping frequency information from measurements,
  80. # and interpolate the overlapping parts.
  81. frequency = skrf.frequency.overlap_freq(open_std.frequency, dut_forward.frequency)
  82. dut_forward = dut_forward.interpolate(frequency)
  83. dut_backward = dut_backward.interpolate(frequency)
  84. _open_std = open_std.interpolate(frequency)
  85. _short_std = short_std.interpolate(frequency)
  86. _load_std = load_std.interpolate(frequency)
  87. _thru_std = thru_std.interpolate(frequency)
  88. # convert 1.5-port measurements to 2-port data by faking data
  89. short_std_2 = one_half_ports_to_two_ports(_short_std)
  90. open_std_2 = one_half_ports_to_two_ports(_open_std)
  91. load_std_2 = one_half_ports_to_two_ports(_load_std)
  92. thru_std_2 = one_half_ports_to_two_ports(_thru_std)
  93. # apply calibration to DUT
  94. cal = TwoPortOnePath(measured=measured, ideals=ideals, n_thrus=1)
  95. if not dut_backward:
  96. # Enhanced Response
  97. with warnings.catch_warnings():
  98. warnings.simplefilter("ignore")
  99. dut_correction = cal.apply_cal(dut_forward)
  100. else:
  101. # Full correction or FakeFlip
  102. dut_correction = cal.apply_cal((dut_forward, dut_backward))
  103. dut_correction.write_touchstone(export_file)
  104. print(export_file)
  105. def one_port(dut, export_file='/dev/null'):
  106. # pull overlapping frequency information from measurements,
  107. # and interpolate the overlapping parts.
  108. frequency = skrf.frequency.overlap_freq(open_std.frequency, dut.frequency)
  109. dut = dut.interpolate(frequency)
  110. _open_std = open_std.interpolate(frequency)
  111. _short_std = short_std.interpolate(frequency)
  112. _load_std = load_std.interpolate(frequency)
  113. # List of 'ideal' responses of the calibration standards
  114. # and corresponding measurements to the 'ideals'.
  115. line = DefinedGammaZ0(frequency=frequency)
  116. ideals, measured = get_standards(line)
  117. # the Calibration object
  118. cal = OnePort(measured=measured, ideals=ideals)
  119. # apply calibration to DUT
  120. dut_correction = cal.apply_cal(dut)
  121. dut_correction.write_touchstone(export_file)
  122. print(export_file)
  123. execution_list = []
  124. # Two-Port Calibration
  125. for f in measurement_dir.glob("*.s2p"):
  126. if f.name.endswith("-forward.s2p"):
  127. # Full Calibration
  128. f_backward = replace_filename_segment(f, "forward", "backward")
  129. output = export_dir / replace_filename_segment(f, "forward", "").name
  130. if f_backward.exists():
  131. s2p_forward = skrf.Network(f)
  132. s2p_backward = skrf.Network(f_backward)
  133. execution_list += [partial(two_port_one_path, s2p_forward, s2p_backward, export_file=output)]
  134. else:
  135. raise FileNotFoundError("%s calibration failed: %s not found!" % (f.name, f_backward.name))
  136. elif f.name.endswith("-backward.s2p"):
  137. pass
  138. elif f.name.endswith("-fakeflip.s2p"):
  139. # FakeFlip Calibration
  140. s2p_forward = skrf.Network(f)
  141. output = export_dir / replace_filename_segment(f, "fakeflip", "").name
  142. execution_list += [partial(two_port_one_path, s2p_forward, s2p_forward, export_file=output)]
  143. else:
  144. # Enhanced Response
  145. s2p_forward = skrf.Network(f)
  146. output = export_dir / f.name
  147. execution_list += [partial(two_port_one_path, s2p_forward, export_file=output)]
  148. # One-Port Calibration
  149. for f in measurement_dir.glob("*.s1p"):
  150. s1p = skrf.Network(f)
  151. output = export_dir / f.name
  152. execution_list += [partial(one_port, s1p, output)]
  153. for task in execution_list:
  154. task()