data_parsing_utils.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import math
  2. from typing import List, Tuple, Union
  3. import numpy as np
  4. from .circle_math import point_of_intersection
  5. def is_float(element) -> bool:
  6. try:
  7. float(element)
  8. val = float(element)
  9. if math.isnan(val) or math.isinf(val):
  10. raise ValueError
  11. return True
  12. except ValueError:
  13. return False
  14. # (to utf-8)
  15. # status returned
  16. def read_data(data: list) -> str:
  17. for x in range(len(data)):
  18. if type(data[x]) == bytes:
  19. try:
  20. data[x] = data[x].decode('utf-8-sig', 'ignore')
  21. except:
  22. return 'Not an utf-8-sig line №: ' + str(x)
  23. return 'data read, but not parsed'
  24. # check if line has comments
  25. # first is a comment line according to .snp documentation,
  26. # others detects comments in various languages
  27. def check_line_comments(line: str) -> Union[str, None]:
  28. if len(line) < 2 or line[0] == '!' or line[0] == '#' or line[
  29. 0] == '%' or line[0] == '/':
  30. return None
  31. else:
  32. # generally we expect these chars as separators
  33. line = line.replace(';', ' ').replace(',', ' ').replace('|', ' ')
  34. if '!' in line:
  35. line = line[:line.find('!')]
  36. return line
  37. # unpack a few first lines of the file to get number of ports
  38. def count_columns(data: List[str]) -> Tuple[int, str]:
  39. return_status = 'data parsed'
  40. column_count = 0
  41. for x in range(len(data)):
  42. line = check_line_comments(data[x])
  43. if line is None:
  44. continue
  45. line = line.split()
  46. # always at least 3 values for single data point
  47. if len(line) < 3:
  48. return_status = 'Can\'t parse line № ' + \
  49. str(x) + ',\n not enough arguments (less than 3)'
  50. break
  51. column_count = len(line)
  52. break
  53. return (column_count, return_status)
  54. # check comments and translate data matrixes into lines
  55. def prepare_snp(data: List[str], number: int) -> Tuple[List[str], str]:
  56. prepared_data = []
  57. return_status = 'data read, but not parsed'
  58. for x in range(len(data)):
  59. line = check_line_comments(data[x])
  60. if line is None:
  61. continue
  62. splitted_line = line.split()
  63. if number * 2 + 1 == len(splitted_line):
  64. prepared_data.append(line)
  65. elif number * 2 == len(splitted_line):
  66. prepared_data[-1] += line
  67. else:
  68. return_status = "Parsing error for .snp format on line №" + str(x)
  69. return prepared_data, return_status
  70. # for Touchstone .snp format
  71. def parse_snp_header(
  72. data: List[str],
  73. is_data_format_snp: bool) -> Tuple[int, int, float, float]:
  74. if is_data_format_snp:
  75. for x in range(len(data)):
  76. if data[x].lstrip()[0] == '#':
  77. line = data[x].split()
  78. if len(line) == 6:
  79. repr_map = {"ri": 0, "ma": 1, "db": 2}
  80. para_map = {"s": 0, "z": 1}
  81. hz_map = {
  82. "ghz": 10**9,
  83. "mhz": 10**6,
  84. "khz": 10**3,
  85. "hz": 1
  86. }
  87. hz, measurement_parameter, data_representation, _r, ref_resistance = (
  88. x.lower() for x in line[1:])
  89. try:
  90. return hz_map[hz], para_map[
  91. measurement_parameter], repr_map[
  92. data_representation], int(
  93. float(ref_resistance))
  94. except:
  95. break
  96. break
  97. return 1, 0, 0, 50
  98. def unpack_data(data: List[str], first_column:int, column_count:int, ref_resistance:float,
  99. measurement_parameter:int, data_representation:int):
  100. f, r, i = [], [], []
  101. return_status = 'data parsed'
  102. for x in range(len(data)):
  103. line = check_line_comments(data[x])
  104. if line is None:
  105. continue
  106. line = line.split()
  107. if column_count != len(line):
  108. return_status = "Wrong number of parameters on line № " + str(x)
  109. break
  110. # 1: process according to data_placement
  111. a, b, c = None, None, None
  112. try:
  113. a = line[0]
  114. b = line[first_column]
  115. c = line[first_column + 1]
  116. except:
  117. return_status = 'Can\'t parse line №: ' + \
  118. str(x) + ',\n not enough arguments'
  119. break
  120. if not ((is_float(a)) or (is_float(b)) or (is_float(c))):
  121. return_status = 'Wrong data type, expected number. Error on line: ' + \
  122. str(x)
  123. break
  124. # mark as processed?
  125. # for y in (a,b,c):
  126. # ace_preview_markers.append(
  127. # {"startRow": x,"startCol": 0,
  128. # "endRow": x,"endCol": data[x].find(y)+len(y),
  129. # "className": "ace_stack","type": "text"})
  130. a, b, c = (float(x) for x in (a, b, c))
  131. f.append(a) # frequency
  132. # 2: process according to data_representation
  133. if data_representation == 'Frequency, real, imaginary':
  134. # std format
  135. r.append(b) # Re
  136. i.append(c) # Im
  137. elif data_representation == 'Frequency, magnitude, angle':
  138. r.append(b * np.cos(np.deg2rad(c)))
  139. i.append(b * np.sin(np.deg2rad(c)))
  140. elif data_representation == 'Frequency, db, angle':
  141. b = 10**(b / 20)
  142. r.append(b * np.cos(np.deg2rad(c)))
  143. i.append(b * np.sin(np.deg2rad(c)))
  144. else:
  145. return_status = 'Wrong data format'
  146. break
  147. # 3: process according to measurement_parameter
  148. if measurement_parameter == 'Z':
  149. # normalization
  150. r[-1] = r[-1] / ref_resistance
  151. i[-1] = i[-1] / ref_resistance
  152. # translate to S
  153. try:
  154. r[-1], i[-1] = point_of_intersection(r[-1] / (1 + r[-1]), 0,
  155. 1 / (1 + r[-1]), 1,
  156. 1 / i[-1], 1 / i[-1])
  157. except:
  158. r.pop()
  159. i.pop()
  160. f.pop()
  161. if return_status == 'data parsed':
  162. if len(f) < 3 or len(f) != len(r) or len(f) != len(i):
  163. return_status = 'Choosen data range is too small, add more points'
  164. elif max(abs(np.array(r) + 1j * np.array(i))) > 2:
  165. return_status = 'Your data points have an abnormality:\
  166. they are too far outside the unit cirlce.\
  167. Make sure the format is correct'
  168. return f, r, i, return_status