diff --git a/.github/workflows/pythoncoverage.yml b/.github/workflows/pythoncoverage.yml index 2055238d..92e64fa1 100644 --- a/.github/workflows/pythoncoverage.yml +++ b/.github/workflows/pythoncoverage.yml @@ -15,11 +15,11 @@ jobs: # os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, ] # python-version: [3.7, 3.8] - python-version: [3.7, ] + python-version: [3.9, ] steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/release_linux.yml b/.github/workflows/release_linux.yml index 132f759b..b3665edb 100644 --- a/.github/workflows/release_linux.yml +++ b/.github/workflows/release_linux.yml @@ -4,6 +4,7 @@ on: push: tags: - v* + workflow_dispatch: jobs: release: @@ -14,12 +15,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies and pyinstall run: | python -m pip install --upgrade pip setuptools pip install -r requirements.txt - pip install PyInstaller==4.0 + pip install PyInstaller==4.5.1 - name: Build binary run: | pyinstaller --onefile -n nanovna-saver nanovna-saver.py diff --git a/.github/workflows/release_macos.yml b/.github/workflows/release_macos.yml index 6e8f6c68..83a59765 100644 --- a/.github/workflows/release_macos.yml +++ b/.github/workflows/release_macos.yml @@ -4,6 +4,7 @@ on: push: tags: - v* + workflow_dispatch: jobs: release: @@ -14,12 +15,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies and pyinstall run: | python -m pip install --upgrade pip setuptools pip install -r requirements.txt - pip install PyInstaller==4.0 + pip install PyInstaller==4.5.1 - name: Build binary run: | pyinstaller --onefile -n nanovna-saver nanovna-saver.py diff --git a/.github/workflows/release_win.yml b/.github/workflows/release_win.yml index a9e22d22..7dd7daaf 100644 --- a/.github/workflows/release_win.yml +++ b/.github/workflows/release_win.yml @@ -4,6 +4,7 @@ on: push: tags: - v* + workflow_dispatch: jobs: release: @@ -17,13 +18,13 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 architecture: ${{ matrix.arch }} - name: Install dependencies and pyinstall run: | python -m pip install --upgrade pip setuptools pip install -r requirements.txt - pip install PyInstaller==4.3 + pip install PyInstaller==4.7 - name: Build binary run: | pyinstaller --onefile -n nanovna-saver.exe nanovna-saver.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ccc1761..8a974da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ Changelog ========= +v0.3.10 +------ + +- Default Band ranges for 5 and 9cm +- Layout should fit on smaller screens +- Fixed fixed axis settings +- Show VNA type in port selector +- Recognise tinySA (screenshot only) +- Some more cables in TDR +- Reference plane applied after calibration +- Calibration fixes by DiSlord + v0.3.9 ------ diff --git a/NanoVNASaver/About.py b/NanoVNASaver/About.py index ec594301..39130f15 100644 --- a/NanoVNASaver/About.py +++ b/NanoVNASaver/About.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -VERSION = "0.3.9" +VERSION = "0.3.10" VERSION_URL = ( "https://raw.githubusercontent.com/" "NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py") @@ -26,7 +26,7 @@ INFO = f"""NanoVNASaver {VERSION} Copyright (C) 2019, 2020 Rune B. Broberg -Copyright (C) 2020 NanoVNA-Saver Authors +Copyright (C) 2020, 2021 NanoVNA-Saver Authors This program comes with ABSOLUTELY NO WARRANTY This program is licensed under the GNU General Public License version 3 diff --git a/NanoVNASaver/Analysis/Analysis.py b/NanoVNASaver/Analysis/Analysis.py index 241f280f..30601562 100644 --- a/NanoVNASaver/Analysis/Analysis.py +++ b/NanoVNASaver/Analysis/Analysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,7 +19,6 @@ import logging import math import numpy as np -from scipy.signal import argrelextrema from PyQt5 import QtWidgets from scipy import signal @@ -30,7 +29,7 @@ class Analysis: _widget = None @classmethod - def find_crossing_zero(cls, data, threshold=0): + def find_crossing_zero(cls, data): ''' Find values crossing zero @@ -45,7 +44,6 @@ def find_crossing_zero(cls, data, threshold=0): :param cls: :param data: list of values - :param threshold: unused, for future manage flipping around 0 ''' my_data = np.array(data) zeroes = np.where(my_data == 0)[0] @@ -118,8 +116,7 @@ def find_maximums(cls, data, threshold=None): # maximums = argrelextrema(my_data, np.greater)[0] if threshold is None: return peaks - else: - return [k for k in peaks if data[k] > threshold] + return [k for k in peaks if data[k] > threshold] def __init__(self, app: QtWidgets.QWidget): self.app = app @@ -136,10 +133,10 @@ def reset(self): def calculateRolloff(self, location1, location2): if location1 == location2: return 0, 0 - frequency1 = self.app.data21[location1].freq - frequency2 = self.app.data21[location2].freq - gain1 = self.app.data21[location1].gain - gain2 = self.app.data21[location2].gain + frequency1 = self.app.data.s21[location1].freq + frequency2 = self.app.data.s21[location2].freq + gain1 = self.app.data.s21[location1].gain + gain2 = self.app.data.s21[location2].gain frequency_factor = frequency2 / frequency1 if frequency_factor < 1: frequency_factor = 1 / frequency_factor diff --git a/NanoVNASaver/Analysis/AntennaAnalysis.py b/NanoVNASaver/Analysis/AntennaAnalysis.py index 84168fc2..c69078d3 100644 --- a/NanoVNASaver/Analysis/AntennaAnalysis.py +++ b/NanoVNASaver/Analysis/AntennaAnalysis.py @@ -1,7 +1,7 @@ # NanoVNASaver # # A python program to view and export Touchstone data from a NanoVNA -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,6 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + +from PyQt5.Qt import QTimer + ''' Created on May 30th 2020 @@ -37,28 +40,88 @@ class MagLoopAnalysis(VSWRAnalysis): ''' max_dips_shown = 1 - vswr_limit_value = 2.56 - bandwith = 250000 + + vswr_bandwith_value = 2.56 # -3 dB ?!? + bandwith = 25000 # 25 kHz + + def __init__(self, app): + # app.sweep_control.get_start() return -1 ?!? + # will populate first runAnalysis() + self.min_freq = None # app.sweep_control.get_start() + self.max_freq = None # app.sweep_control.get_end() + self.vswr_limit_value = self.vswr_bandwith_value + + super().__init__(app) def runAnalysis(self): super().runAnalysis() + new_start = self.app.sweep_control.get_start() + new_end = self.app.sweep_control.get_end() + if self.min_freq is None: + self.min_freq = new_start + self.max_freq = new_end + logger.debug("setting hard limits to %s - %s", + self.min_freq, self.max_freq) if len(self.minimums) > 1: self.layout.addRow("", QtWidgets.QLabel( - "Not magloop or try to lower VSWR limit")) - - for m in self.minimums[:1]: - # only one time + "Multiple minimums, not magloop or try to lower VSWR limit")) + return + if len(self.minimums) == 1: + m = self.minimums[0] start, lowest, end = m if start != end: - Q = self.app.data11[lowest].freq / \ - (self.app.data11[end].freq - self.app.data11[start].freq) - self.layout.addRow( - "Q", QtWidgets.QLabel("{}".format(int(Q)))) - self.app.sweep_control.set_start(self.app.data11[start].freq) - self.app.sweep_control.set_end(self.app.data11[end].freq) + if self.vswr_limit_value == self.vswr_bandwith_value: + Q = self.app.data.s11[lowest].freq / \ + (self.app.data.s11[end].freq - + self.app.data.s11[start].freq) + self.layout.addRow( + "Q", QtWidgets.QLabel("{}".format(int(Q)))) + new_start = self.app.data.s11[start].freq - self.bandwith + new_end = self.app.data.s11[end].freq + self.bandwith + logger.debug("Single Spot, new scan on %s-%s", + new_start, new_end) + else: - self.app.sweep_control.set_start( - self.app.data11[start].freq - self.bandwith) - self.app.sweep_control.set_end( - self.app.data11[end].freq + self.bandwith) + new_start = self.app.data.s11[start].freq - 2 * self.bandwith + new_end = self.app.data.s11[end].freq + 2 * self.bandwith + logger.debug(" Zoom to %s-%s", new_start, new_end) + + if self.vswr_limit_value > self.vswr_bandwith_value: + self.vswr_limit_value = max( + self.vswr_bandwith_value, self.vswr_limit_value - 1) + self.input_vswr_limit.setValue(self.vswr_limit_value) + logger.debug( + "found higher minimum, lowering vswr search to %s", self.vswr_limit_value) + else: + new_start = new_start - 5 * self.bandwith + new_end = new_end + 5 * self.bandwith + if all((new_start <= self.min_freq, + new_end >= self.max_freq)): + if self.vswr_limit_value < 10: + self.vswr_limit_value += 2 + self.input_vswr_limit.setValue(self.vswr_limit_value) + logger.debug( + "no minimum found, looking for higher value %s", self.vswr_limit_value) + new_start = max(self.min_freq, new_start) + new_end = min(self.max_freq, new_end) + logger.debug("next search will be %s - %s for vswr %s", + new_start, + new_end, + self.vswr_limit_value) + + self.app.sweep_control.set_start(new_start) + self.app.sweep_control.set_end(new_end) + # set timer to let finish all stuff before new sweep + QTimer.singleShot(2000, self._safe_sweep) + + def _safe_sweep(self): + ''' + sweep only if button enabled + to prevent multiple/concurrent sweep + ''' + + if self.app.sweep_control.btn_start.isEnabled(): + self.app.sweep_start() + else: + logger.error("sweep alredy running") diff --git a/NanoVNASaver/Analysis/BandPassAnalysis.py b/NanoVNASaver/Analysis/BandPassAnalysis.py index d8254b25..159a559f 100644 --- a/NanoVNASaver/Analysis/BandPassAnalysis.py +++ b/NanoVNASaver/Analysis/BandPassAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -108,7 +108,7 @@ def runAnalysis(self): pass_band_location = self.app.markers[0].location logger.debug("Pass band location: %d", pass_band_location) - if len(self.app.data21) == 0: + if len(self.app.data.s21) == 0: logger.debug("No data to analyse") self.result_label.setText("No data to analyse.") return @@ -119,13 +119,13 @@ def runAnalysis(self): f"Please place {self.app.markers[0].name} in the passband.") return - pass_band_db = self.app.data21[pass_band_location].gain + pass_band_db = self.app.data.s21[pass_band_location].gain logger.debug("Initial passband gain: %d", pass_band_db) initial_lower_cutoff_location = -1 for i in range(pass_band_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 3: + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found a cutoff location initial_lower_cutoff_location = i break @@ -134,13 +134,13 @@ def runAnalysis(self): self.result_label.setText("Lower cutoff location not found.") return - initial_lower_cutoff_frequency = self.app.data21[initial_lower_cutoff_location].freq + initial_lower_cutoff_frequency = self.app.data.s21[initial_lower_cutoff_location].freq logger.debug("Found initial lower cutoff frequency at %d", initial_lower_cutoff_frequency) initial_upper_cutoff_location = -1 - for i in range(pass_band_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 3: + for i in range(pass_band_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found a cutoff location initial_upper_cutoff_location = i break @@ -149,30 +149,30 @@ def runAnalysis(self): self.result_label.setText("Upper cutoff location not found.") return - initial_upper_cutoff_frequency = self.app.data21[initial_upper_cutoff_location].freq + initial_upper_cutoff_frequency = self.app.data.s21[initial_upper_cutoff_location].freq logger.debug("Found initial upper cutoff frequency at %d", initial_upper_cutoff_frequency) peak_location = -1 - peak_db = self.app.data21[initial_lower_cutoff_location].gain + peak_db = self.app.data.s21[initial_lower_cutoff_location].gain for i in range(initial_lower_cutoff_location, initial_upper_cutoff_location, 1): - db = self.app.data21[i].gain + db = self.app.data.s21[i].gain if db > peak_db: peak_db = db peak_location = i - logger.debug("Found peak of %f at %d", peak_db, self.app.data11[peak_location].freq) + logger.debug("Found peak of %f at %d", peak_db, self.app.data.s11[peak_location].freq) lower_cutoff_location = -1 pass_band_db = peak_db for i in range(peak_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 3: + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found the cutoff location lower_cutoff_location = i break - lower_cutoff_frequency = self.app.data21[lower_cutoff_location].freq - lower_cutoff_gain = self.app.data21[lower_cutoff_location].gain - pass_band_db + lower_cutoff_frequency = self.app.data.s21[lower_cutoff_location].freq + lower_cutoff_gain = self.app.data.s21[lower_cutoff_location].gain - pass_band_db if lower_cutoff_gain < -4: logger.debug("Lower cutoff frequency found at %f dB" @@ -189,14 +189,14 @@ def runAnalysis(self): upper_cutoff_location = -1 pass_band_db = peak_db - for i in range(peak_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 3: + for i in range(peak_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found the cutoff location upper_cutoff_location = i break - upper_cutoff_frequency = self.app.data21[upper_cutoff_location].freq - upper_cutoff_gain = self.app.data21[upper_cutoff_location].gain - pass_band_db + upper_cutoff_frequency = self.app.data.s21[upper_cutoff_location].freq + upper_cutoff_gain = self.app.data.s21[upper_cutoff_location].gain - pass_band_db if upper_cutoff_gain < -4: logger.debug("Upper cutoff frequency found at %f dB" " - insufficient data points for true -3 dB point.", @@ -227,7 +227,7 @@ def runAnalysis(self): lower_six_db_location = -1 for i in range(lower_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 6: + if (pass_band_db - self.app.data.s21[i].gain) > 6: # We found 6dB location lower_six_db_location = i break @@ -235,39 +235,39 @@ def runAnalysis(self): if lower_six_db_location < 0: self.result_label.setText("Lower 6 dB location not found.") return - lower_six_db_cutoff_frequency = self.app.data21[lower_six_db_location].freq + lower_six_db_cutoff_frequency = self.app.data.s21[lower_six_db_location].freq self.lower_six_db_label.setText( format_frequency(lower_six_db_cutoff_frequency)) ten_db_location = -1 for i in range(lower_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 10: + if (pass_band_db - self.app.data.s21[i].gain) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 for i in range(lower_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 20: + if (pass_band_db - self.app.data.s21[i].gain) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 for i in range(lower_six_db_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 60: + if (pass_band_db - self.app.data.s21[i].gain) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.lower_sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * \ 10 ** (5 * (math.log10(twenty) - math.log10(ten))) self.lower_sixty_db_label.setText( @@ -289,8 +289,8 @@ def runAnalysis(self): # Upper roll-off upper_six_db_location = -1 - for i in range(upper_cutoff_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 6: + for i in range(upper_cutoff_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 6: # We found 6dB location upper_six_db_location = i break @@ -298,7 +298,7 @@ def runAnalysis(self): if upper_six_db_location < 0: self.result_label.setText("Upper 6 dB location not found.") return - upper_six_db_cutoff_frequency = self.app.data21[upper_six_db_location].freq + upper_six_db_cutoff_frequency = self.app.data.s21[upper_six_db_location].freq self.upper_six_db_label.setText( format_frequency(upper_six_db_cutoff_frequency)) @@ -308,33 +308,33 @@ def runAnalysis(self): format_frequency(six_db_span)) ten_db_location = -1 - for i in range(upper_cutoff_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 10: + for i in range(upper_cutoff_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 - for i in range(upper_cutoff_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 20: + for i in range(upper_cutoff_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 - for i in range(upper_six_db_location, len(self.app.data21), 1): - if (pass_band_db - self.app.data21[i].gain) > 60: + for i in range(upper_six_db_location, len(self.app.data.s21), 1): + if (pass_band_db - self.app.data.s21[i].gain) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.upper_sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * \ 10 ** (5 * (math.log10(twenty) - math.log10(ten))) self.upper_sixty_db_label.setText( @@ -355,8 +355,8 @@ def runAnalysis(self): if upper_cutoff_gain < -4 or lower_cutoff_gain < -4: self.result_label.setText( - f"Analysis complete ({len(self.app.data11)} points)\n" + f"Analysis complete ({len(self.app.data.s11)} points)\n" f"Insufficient data for analysis. Increase segment count.") else: self.result_label.setText( - f"Analysis complete ({len(self.app.data11)} points)") + f"Analysis complete ({len(self.app.data.s11)} points)") diff --git a/NanoVNASaver/Analysis/BandStopAnalysis.py b/NanoVNASaver/Analysis/BandStopAnalysis.py index 7a656097..8c6af782 100644 --- a/NanoVNASaver/Analysis/BandStopAnalysis.py +++ b/NanoVNASaver/Analysis/BandStopAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -101,31 +101,31 @@ def reset(self): def runAnalysis(self): self.reset() - if len(self.app.data21) == 0: + if len(self.app.data.s21) == 0: logger.debug("No data to analyse") self.result_label.setText("No data to analyse.") return peak_location = -1 - peak_db = self.app.data21[0].gain - for i in range(len(self.app.data21)): - db = self.app.data21[i].gain + peak_db = self.app.data.s21[0].gain + for i in range(len(self.app.data.s21)): + db = self.app.data.s21[i].gain if db > peak_db: peak_db = db peak_location = i - logger.debug("Found peak of %f at %d", peak_db, self.app.data11[peak_location].freq) + logger.debug("Found peak of %f at %d", peak_db, self.app.data.s11[peak_location].freq) lower_cutoff_location = -1 pass_band_db = peak_db - for i in range(len(self.app.data21)): - if (pass_band_db - self.app.data21[i].gain) > 3: + for i in range(len(self.app.data.s21)): + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found the cutoff location lower_cutoff_location = i break - lower_cutoff_frequency = self.app.data21[lower_cutoff_location].freq - lower_cutoff_gain = self.app.data21[lower_cutoff_location].gain - pass_band_db + lower_cutoff_frequency = self.app.data.s21[lower_cutoff_location].freq + lower_cutoff_gain = self.app.data.s21[lower_cutoff_location].gain - pass_band_db if lower_cutoff_gain < -4: logger.debug("Lower cutoff frequency found at %f dB" @@ -142,14 +142,14 @@ def runAnalysis(self): self.app.markers[1].frequencyInput.setText(str(lower_cutoff_frequency)) upper_cutoff_location = -1 - for i in range(len(self.app.data21)-1, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 3: + for i in range(len(self.app.data.s21)-1, -1, -1): + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found the cutoff location upper_cutoff_location = i break - upper_cutoff_frequency = self.app.data21[upper_cutoff_location].freq - upper_cutoff_gain = self.app.data21[upper_cutoff_location].gain - pass_band_db + upper_cutoff_frequency = self.app.data.s21[upper_cutoff_location].freq + upper_cutoff_gain = self.app.data.s21[upper_cutoff_location].gain - pass_band_db if upper_cutoff_gain < -4: logger.debug("Upper cutoff frequency found at %f dB" " - insufficient data points for true -3 dB point.", @@ -178,8 +178,8 @@ def runAnalysis(self): # Lower roll-off lower_six_db_location = -1 - for i in range(lower_cutoff_location, len(self.app.data21)): - if (pass_band_db - self.app.data21[i].gain) > 6: + for i in range(lower_cutoff_location, len(self.app.data.s21)): + if (pass_band_db - self.app.data.s21[i].gain) > 6: # We found 6dB location lower_six_db_location = i break @@ -187,38 +187,38 @@ def runAnalysis(self): if lower_six_db_location < 0: self.result_label.setText("Lower 6 dB location not found.") return - lower_six_db_cutoff_frequency = self.app.data21[lower_six_db_location].freq + lower_six_db_cutoff_frequency = self.app.data.s21[lower_six_db_location].freq self.lower_six_db_label.setText( format_frequency(lower_six_db_cutoff_frequency)) ten_db_location = -1 - for i in range(lower_cutoff_location, len(self.app.data21)): - if (pass_band_db - self.app.data21[i].gain) > 10: + for i in range(lower_cutoff_location, len(self.app.data.s21)): + if (pass_band_db - self.app.data.s21[i].gain) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 - for i in range(lower_cutoff_location, len(self.app.data21)): - if (pass_band_db - self.app.data21[i].gain) > 20: + for i in range(lower_cutoff_location, len(self.app.data.s21)): + if (pass_band_db - self.app.data.s21[i].gain) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 - for i in range(lower_six_db_location, len(self.app.data21)): - if (pass_band_db - self.app.data21[i].gain) > 60: + for i in range(lower_six_db_location, len(self.app.data.s21)): + if (pass_band_db - self.app.data.s21[i].gain) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.lower_sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) self.lower_sixty_db_label.setText( f"{format_frequency(sixty_db_frequency)} (derived)") @@ -242,7 +242,7 @@ def runAnalysis(self): upper_six_db_location = -1 for i in range(upper_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 6: + if (pass_band_db - self.app.data.s21[i].gain) > 6: # We found 6dB location upper_six_db_location = i break @@ -250,7 +250,7 @@ def runAnalysis(self): if upper_six_db_location < 0: self.result_label.setText("Upper 6 dB location not found.") return - upper_six_db_cutoff_frequency = self.app.data21[upper_six_db_location].freq + upper_six_db_cutoff_frequency = self.app.data.s21[upper_six_db_location].freq self.upper_six_db_label.setText( format_frequency(upper_six_db_cutoff_frequency)) @@ -261,32 +261,32 @@ def runAnalysis(self): ten_db_location = -1 for i in range(upper_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 10: + if (pass_band_db - self.app.data.s21[i].gain) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 for i in range(upper_cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 20: + if (pass_band_db - self.app.data.s21[i].gain) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 for i in range(upper_six_db_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 60: + if (pass_band_db - self.app.data.s21[i].gain) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.upper_sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * 10 ** ( 5 * (math.log10(twenty) - math.log10(ten))) self.upper_sixty_db_label.setText( @@ -309,8 +309,8 @@ def runAnalysis(self): if upper_cutoff_gain < -4 or lower_cutoff_gain < -4: self.result_label.setText( - f"Analysis complete ({len(self.app.data11)} points)\n" + f"Analysis complete ({len(self.app.data.s11)} points)\n" f"Insufficient data for analysis. Increase segment count.") else: self.result_label.setText( - f"Analysis complete ({len(self.app.data11)} points)") + f"Analysis complete ({len(self.app.data.s11)} points)") diff --git a/NanoVNASaver/Analysis/HighPassAnalysis.py b/NanoVNASaver/Analysis/HighPassAnalysis.py index 26f7c5c0..ab585a1c 100644 --- a/NanoVNASaver/Analysis/HighPassAnalysis.py +++ b/NanoVNASaver/Analysis/HighPassAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -64,7 +64,7 @@ def runAnalysis(self): pass_band_location = self.app.markers[0].location logger.debug("Pass band location: %d", pass_band_location) - if len(self.app.data21) == 0: + if len(self.app.data.s21) == 0: logger.debug("No data to analyse") self.result_label.setText("No data to analyse.") return @@ -75,13 +75,13 @@ def runAnalysis(self): f"Please place {self.app.markers[0].name } in the passband.") return - pass_band_db = self.app.data21[pass_band_location].gain + pass_band_db = self.app.data.s21[pass_band_location].gain logger.debug("Initial passband gain: %d", pass_band_db) initial_cutoff_location = -1 for i in range(pass_band_location, -1, -1): - db = self.app.data21[i].gain + db = self.app.data.s21[i].gain if (pass_band_db - db) > 3: # We found a cutoff location initial_cutoff_location = i @@ -91,32 +91,32 @@ def runAnalysis(self): self.result_label.setText("Cutoff location not found.") return - initial_cutoff_frequency = self.app.data21[initial_cutoff_location].freq + initial_cutoff_frequency = self.app.data.s21[initial_cutoff_location].freq logger.debug("Found initial cutoff frequency at %d", initial_cutoff_frequency) peak_location = -1 - peak_db = self.app.data21[initial_cutoff_location].gain - for i in range(len(self.app.data21) - 1, initial_cutoff_location - 1, -1): - if self.app.data21[i].gain > peak_db: + peak_db = self.app.data.s21[initial_cutoff_location].gain + for i in range(len(self.app.data.s21) - 1, initial_cutoff_location - 1, -1): + if self.app.data.s21[i].gain > peak_db: peak_db = db peak_location = i - logger.debug("Found peak of %f at %d", peak_db, self.app.data11[peak_location].freq) + logger.debug("Found peak of %f at %d", peak_db, self.app.data.s11[peak_location].freq) - self.app.markers[0].setFrequency(str(self.app.data21[peak_location].freq)) - self.app.markers[0].frequencyInput.setText(str(self.app.data21[peak_location].freq)) + self.app.markers[0].setFrequency(str(self.app.data.s21[peak_location].freq)) + self.app.markers[0].frequencyInput.setText(str(self.app.data.s21[peak_location].freq)) cutoff_location = -1 pass_band_db = peak_db for i in range(peak_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 3: + if (pass_band_db - self.app.data.s21[i].gain) > 3: # We found the cutoff location cutoff_location = i break - cutoff_frequency = self.app.data21[cutoff_location].freq - cutoff_gain = self.app.data21[cutoff_location].gain - pass_band_db + cutoff_frequency = self.app.data.s21[cutoff_location].freq + cutoff_gain = self.app.data.s21[cutoff_location].gain - pass_band_db if cutoff_gain < -4: logger.debug("Cutoff frequency found at %f dB" " - insufficient data points for true -3 dB point.", @@ -131,7 +131,7 @@ def runAnalysis(self): six_db_location = -1 for i in range(cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 6: + if (pass_band_db - self.app.data.s21[i].gain) > 6: # We found 6dB location six_db_location = i break @@ -139,39 +139,39 @@ def runAnalysis(self): if six_db_location < 0: self.result_label.setText("6 dB location not found.") return - six_db_cutoff_frequency = self.app.data21[six_db_location].freq + six_db_cutoff_frequency = self.app.data.s21[six_db_location].freq self.six_db_label.setText( format_frequency(six_db_cutoff_frequency)) ten_db_location = -1 for i in range(cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 10: + if (pass_band_db - self.app.data.s21[i].gain) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 for i in range(cutoff_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 20: + if (pass_band_db - self.app.data.s21[i].gain) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 for i in range(six_db_location, -1, -1): - if (pass_band_db - self.app.data21[i].gain) > 60: + if (pass_band_db - self.app.data.s21[i].gain) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) self.sixty_db_label.setText( f"{format_frequency(sixty_db_frequency)} (derived)") @@ -187,4 +187,4 @@ def runAnalysis(self): self.db_per_octave_label.setText("Not calculated") self.db_per_decade_label.setText("Not calculated") - self.result_label.setText(f"Analysis complete ({len(self.app.data11)}) points)") + self.result_label.setText(f"Analysis complete ({len(self.app.data.s11)}) points)") diff --git a/NanoVNASaver/Analysis/LowPassAnalysis.py b/NanoVNASaver/Analysis/LowPassAnalysis.py index 7d523927..78f3d3e9 100644 --- a/NanoVNASaver/Analysis/LowPassAnalysis.py +++ b/NanoVNASaver/Analysis/LowPassAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -66,7 +66,7 @@ def runAnalysis(self): pass_band_location = self.app.markers[0].location logger.debug("Pass band location: %d", pass_band_location) - if len(self.app.data21) == 0: + if len(self.app.data.s21) == 0: logger.debug("No data to analyse") self.result_label.setText("No data to analyse.") return @@ -78,13 +78,13 @@ def runAnalysis(self): f"Please place {self.app.markers[0].name} in the passband.") return - pass_band_db = self.app.data21[pass_band_location].gain + pass_band_db = self.app.data.s21[pass_band_location].gain logger.debug("Initial passband gain: %d", pass_band_db) initial_cutoff_location = -1 - for i in range(pass_band_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(pass_band_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 3: # We found a cutoff location initial_cutoff_location = i @@ -94,34 +94,34 @@ def runAnalysis(self): self.result_label.setText("Cutoff location not found.") return - initial_cutoff_frequency = self.app.data21[initial_cutoff_location].freq + initial_cutoff_frequency = self.app.data.s21[initial_cutoff_location].freq logger.debug("Found initial cutoff frequency at %d", initial_cutoff_frequency) peak_location = -1 - peak_db = self.app.data21[initial_cutoff_location].gain + peak_db = self.app.data.s21[initial_cutoff_location].gain for i in range(0, initial_cutoff_location): - db = self.app.data21[i].gain + db = self.app.data.s21[i].gain if db > peak_db: peak_db = db peak_location = i - logger.debug("Found peak of %f at %d", peak_db, self.app.data11[peak_location].freq) + logger.debug("Found peak of %f at %d", peak_db, self.app.data.s11[peak_location].freq) - self.app.markers[0].setFrequency(str(self.app.data21[peak_location].freq)) - self.app.markers[0].frequencyInput.setText(str(self.app.data21[peak_location].freq)) + self.app.markers[0].setFrequency(str(self.app.data.s21[peak_location].freq)) + self.app.markers[0].frequencyInput.setText(str(self.app.data.s21[peak_location].freq)) cutoff_location = -1 pass_band_db = peak_db - for i in range(peak_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(peak_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 3: # We found the cutoff location cutoff_location = i break - cutoff_frequency = self.app.data21[cutoff_location].freq - cutoff_gain = self.app.data21[cutoff_location].gain - pass_band_db + cutoff_frequency = self.app.data.s21[cutoff_location].freq + cutoff_gain = self.app.data.s21[cutoff_location].gain - pass_band_db if cutoff_gain < -4: logger.debug( "Cutoff frequency found at %f dB" @@ -136,8 +136,8 @@ def runAnalysis(self): self.app.markers[1].frequencyInput.setText(str(cutoff_frequency)) six_db_location = -1 - for i in range(cutoff_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(cutoff_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 6: # We found 6dB location six_db_location = i @@ -146,41 +146,41 @@ def runAnalysis(self): if six_db_location < 0: self.result_label.setText("6 dB location not found.") return - six_db_cutoff_frequency = self.app.data21[six_db_location].freq + six_db_cutoff_frequency = self.app.data.s21[six_db_location].freq self.six_db_label.setText( format_frequency(six_db_cutoff_frequency)) ten_db_location = -1 - for i in range(cutoff_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(cutoff_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 10: # We found 6dB location ten_db_location = i break twenty_db_location = -1 - for i in range(cutoff_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(cutoff_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 20: # We found 6dB location twenty_db_location = i break sixty_db_location = -1 - for i in range(six_db_location, len(self.app.data21)): - db = self.app.data21[i].gain + for i in range(six_db_location, len(self.app.data.s21)): + db = self.app.data.s21[i].gain if (pass_band_db - db) > 60: # We found 60dB location! Wow. sixty_db_location = i break if sixty_db_location > 0: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + sixty_db_cutoff_frequency = self.app.data.s21[sixty_db_location].freq self.sixty_db_label.setText( format_frequency(sixty_db_cutoff_frequency)) elif ten_db_location != -1 and twenty_db_location != -1: - ten = self.app.data21[ten_db_location].freq - twenty = self.app.data21[twenty_db_location].freq + ten = self.app.data.s21[ten_db_location].freq + twenty = self.app.data.s21[twenty_db_location].freq sixty_db_frequency = ten * \ 10 ** (5 * (math.log10(twenty) - math.log10(ten))) self.sixty_db_label.setText( @@ -202,4 +202,4 @@ def runAnalysis(self): self.db_per_decade_label.setText("Not calculated") self.result_label.setText( - "Analysis complete (" + str(len(self.app.data11)) + " points)") + "Analysis complete (" + str(len(self.app.data.s11)) + " points)") diff --git a/NanoVNASaver/Analysis/PeakSearchAnalysis.py b/NanoVNASaver/Analysis/PeakSearchAnalysis.py index 0611f962..0a522df0 100644 --- a/NanoVNASaver/Analysis/PeakSearchAnalysis.py +++ b/NanoVNASaver/Analysis/PeakSearchAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -97,19 +97,19 @@ def runAnalysis(self): count = self.input_number_of_peaks.value() if self.rbtn_data_vswr.isChecked(): fn = format_vswr - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.vswr) elif self.rbtn_data_s21_gain.isChecked(): fn = format_gain - for d in self.app.data21: + for d in self.app.data.s21: data.append(d.gain) elif self.rbtn_data_resistance.isChecked(): fn = format_resistance - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.impedance().real) elif self.rbtn_data_reactance.isChecked(): fn = str - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.impedance().imag) else: @@ -150,10 +150,10 @@ def runAnalysis(self): logger.debug("Index %d", i) logger.debug("Prominence %f", prominences[i]) logger.debug("Index in sweep %d", peaks[i]) - logger.debug("Frequency %d", self.app.data11[peaks[i]].freq) + logger.debug("Frequency %d", self.app.data.s11[peaks[i]].freq) logger.debug("Value %f", sign * data[peaks[i]]) self.layout.addRow( - f"Freq {format_frequency_short(self.app.data11[peaks[i]].freq)}", + f"Freq {format_frequency_short(self.app.data.s11[peaks[i]].freq)}", QtWidgets.QLabel(f" value {fn(sign * data[peaks[i]])}" )) @@ -162,9 +162,9 @@ def runAnalysis(self): logger.warning("More peaks found than there are markers") for i in range(min(count, len(self.app.markers))): self.app.markers[i].setFrequency( - str(self.app.data11[peaks[indices[i]]].freq)) + str(self.app.data.s11[peaks[indices[i]]].freq)) self.app.markers[i].frequencyInput.setText( - str(self.app.data11[peaks[indices[i]]].freq)) + str(self.app.data.s11[peaks[indices[i]]].freq)) max_val = -10**10 max_idx = -1 @@ -180,6 +180,6 @@ def reset(self): logger.debug("Results start at %d, out of %d", self.results_header, self.layout.rowCount()) - for i in range(self.results_header, self.layout.rowCount()): + for _ in range(self.results_header, self.layout.rowCount()): logger.debug("deleting %s", self.layout.rowCount()) self.layout.removeRow(self.layout.rowCount() - 1) diff --git a/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py b/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py index a6fb449d..0629885c 100644 --- a/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py +++ b/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -81,22 +81,22 @@ def runAnalysis(self): if self.rbtn_data_vswr.isChecked(): suffix = "" data = [] - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.vswr) elif self.rbtn_data_resistance.isChecked(): suffix = " \N{OHM SIGN}" data = [] - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.impedance().real) elif self.rbtn_data_reactance.isChecked(): suffix = " \N{OHM SIGN}" data = [] - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.impedance().imag) elif self.rbtn_data_s21_gain.isChecked(): suffix = " dB" data = [] - for d in self.app.data21: + for d in self.app.data.s21: data.append(d.gain) else: logger.warning("Searching for peaks on unknown data") @@ -117,10 +117,10 @@ def runAnalysis(self): return self.peak_frequency.setText( - format_frequency(self.app.data11[idx_peak].freq)) + format_frequency(self.app.data.s11[idx_peak].freq)) self.peak_value.setText(str(round(data[idx_peak], 3)) + suffix) if self.checkbox_move_marker.isChecked() and len(self.app.markers) >= 1: - self.app.markers[0].setFrequency(str(self.app.data11[idx_peak].freq)) + self.app.markers[0].setFrequency(str(self.app.data.s11[idx_peak].freq)) self.app.markers[0].frequencyInput.setText( - format_frequency(self.app.data11[idx_peak].freq)) + format_frequency(self.app.data.s11[idx_peak].freq)) diff --git a/NanoVNASaver/Analysis/VSWRAnalysis.py b/NanoVNASaver/Analysis/VSWRAnalysis.py index 200dda46..24d2f1ea 100644 --- a/NanoVNASaver/Analysis/VSWRAnalysis.py +++ b/NanoVNASaver/Analysis/VSWRAnalysis.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,23 +16,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +import csv import logging +from collections import OrderedDict -from PyQt5 import QtWidgets import numpy as np +from PyQt5 import QtWidgets from NanoVNASaver.Analysis import Analysis, PeakSearchAnalysis -from NanoVNASaver.Formatting import format_frequency -from NanoVNASaver.Formatting import format_complex_imp +from NanoVNASaver.Formatting import ( + format_frequency, format_complex_imp, + format_frequency_short, format_resistance) from NanoVNASaver.RFTools import reflection_coefficient -import os -import csv -from NanoVNASaver.Marker.Values import Label -from NanoVNASaver.Marker.Widget import MarkerLabel -from NanoVNASaver.Marker.Widget import Marker -from collections import OrderedDict -from NanoVNASaver.Formatting import format_frequency_short -from NanoVNASaver.Formatting import format_resistance logger = logging.getLogger(__name__) @@ -78,19 +74,18 @@ def __init__(self, app): def runAnalysis(self): max_dips_shown = self.max_dips_shown - data = [] - for d in self.app.data11: - data.append(d.vswr) + data = [d.vswr for d in self.app.data.s11] + # min_idx = np.argmin(data) # # logger.debug("Minimum at %d", min_idx) # logger.debug("Value at minimum: %f", data[min_idx]) - # logger.debug("Frequency: %d", self.app.data11[min_idx].freq) + # logger.debug("Frequency: %d", self.app.data.s11[min_idx].freq) # # if self.checkbox_move_marker.isChecked(): - # self.app.markers[0].setFrequency(str(self.app.data11[min_idx].freq)) - # self.app.markers[0].frequencyInput.setText(str(self.app.data11[min_idx].freq)) + # self.app.markers[0].setFrequency(str(self.app.data.s11[min_idx].freq)) + # self.app.markers[0].frequencyInput.setText(str(self.app.data.s11[min_idx].freq)) threshold = self.input_vswr_limit.value() minimums = self.find_minimums(data, threshold) @@ -101,7 +96,7 @@ def runAnalysis(self): results_header = self.layout.indexOf(self.results_label) logger.debug("Results start at %d, out of %d", results_header, self.layout.rowCount()) - for i in range(results_header, self.layout.rowCount()): + for _ in range(results_header, self.layout.rowCount()): self.layout.removeRow(self.layout.rowCount() - 1) if len(minimums) > max_dips_shown: @@ -113,7 +108,7 @@ def runAnalysis(self): dips.append(data[lowest]) best_dips = [] - for i in range(max_dips_shown): + for _ in range(max_dips_shown): min_idx = np.argmin(dips) best_dips.append(minimums[min_idx]) dips.remove(dips[min_idx]) @@ -127,24 +122,23 @@ def runAnalysis(self): logger.debug( "Section from %d to %d, lowest at %d", start, end, lowest) self.layout.addRow("Start", QtWidgets.QLabel( - format_frequency(self.app.data11[start].freq))) + format_frequency(self.app.data.s11[start].freq))) self.layout.addRow( "Minimum", QtWidgets.QLabel( - f"{format_frequency(self.app.data11[lowest].freq)}" + f"{format_frequency(self.app.data.s11[lowest].freq)}" f" ({round(data[lowest], 2)})")) self.layout.addRow("End", QtWidgets.QLabel( - format_frequency(self.app.data11[end].freq))) + format_frequency(self.app.data.s11[end].freq))) self.layout.addRow( "Span", QtWidgets.QLabel( - format_frequency(self.app.data11[end].freq - - self.app.data11[start].freq))) - self.layout.addWidget(PeakSearchAnalysis.QHLine()) + format_frequency(self.app.data.s11[end].freq - + self.app.data.s11[start].freq))) else: self.layout.addRow("Low spot", QtWidgets.QLabel( - format_frequency(self.app.data11[lowest].freq))) - self.layout.addWidget(PeakSearchAnalysis.QHLine()) + format_frequency(self.app.data.s11[lowest].freq))) + self.layout.addWidget(PeakSearchAnalysis.QHLine()) # Remove the final separator line self.layout.removeRow(self.layout.rowCount() - 1) else: @@ -186,11 +180,11 @@ def __init__(self, app): self.layout.addRow(self.results_label) def _get_data(self, index): - my_data = {"freq": self.app.data11[index].freq, - "s11": self.app.data11[index].z, - "lambda": self.app.data11[index].wavelength, - "impedance": self.app.data11[index].impedance(), - "vswr": self.app.data11[index].vswr, + my_data = {"freq": self.app.data.s11[index].freq, + "s11": self.app.data.s11[index].z, + "lambda": self.app.data.s11[index].wavelength, + "impedance": self.app.data.s11[index].impedance(), + "vswr": self.app.data.s11[index].vswr, } my_data["vswr_49"] = self.vswr_transformed( my_data["impedance"], 49) @@ -202,13 +196,8 @@ def _get_data(self, index): return my_data def _get_crossing(self): - - data = [] - for d in self.app.data11: - data.append(d.phase) - - crossing = sorted(self.find_crossing_zero(data)) - return crossing + data = [d.phase for d in self.app.data.s11] + return sorted(self.find_crossing_zero(data)) def runAnalysis(self): self.reset() @@ -228,17 +217,15 @@ def runAnalysis(self): results_header = self.layout.indexOf(self.results_label) logger.debug("Results start at %d, out of %d", results_header, self.layout.rowCount()) - for i in range(results_header, self.layout.rowCount()): + for _ in range(results_header, self.layout.rowCount()): self.layout.removeRow(self.layout.rowCount() - 1) # if len(crossing) > max_dips_shown: # self.layout.addRow(QtWidgets.QLabel("More than " + str(max_dips_shown) + # " dips found. Lowest shown.")) - # self.crossing = crossing[:max_dips_shown] - extended_data = [] if len(crossing) > 0: - + extended_data = [] for m in crossing: start, lowest, end = m my_data = self._get_data(lowest) @@ -251,11 +238,11 @@ def runAnalysis(self): self.layout.addRow( "Resonance", QtWidgets.QLabel( - f"{format_frequency(self.app.data11[lowest].freq)}" - f" ({format_complex_imp(self.app.data11[lowest].impedance())})")) + f"{format_frequency(self.app.data.s11[lowest].freq)}" + f" ({format_complex_imp(self.app.data.s11[lowest].impedance())})")) else: self.layout.addRow("Resonance", QtWidgets.QLabel( - format_frequency(self.app.data11[lowest].freq))) + format_frequency(self.app.data.s11[lowest].freq))) self.layout.addWidget(PeakSearchAnalysis.QHLine()) # Remove the final separator line self.layout.removeRow(self.layout.rowCount() - 1) @@ -296,7 +283,7 @@ def runAnalysis(self): crossing = self._get_crossing() data = [] - for d in self.app.data11: + for d in self.app.data.s11: data.append(d.impedance().real) maximums = sorted(self.find_maximums(data, threshold=500)) @@ -342,13 +329,12 @@ def runAnalysis(self): # self.app.markers[i].label['returnloss'].setMinimumWidth(80) self.app.markers[i].setFrequency( - str(self.app.data11[both[i]].freq)) + str(self.app.data.s11[both[i]].freq)) self.app.markers[i].frequencyInput.setText( - str(self.app.data11[both[i]].freq)) + str(self.app.data.s11[both[i]].freq)) else: logger.info("TO DO: find near data") - for m in crossing: - start, lowest, end = m + for _, lowest, _ in crossing: my_data = self._get_data(lowest) if lowest in extended_data: @@ -382,9 +368,9 @@ def runAnalysis(self): for i, index in enumerate(sorted(extended_data.keys())): self.layout.addRow( - f"{format_frequency_short(self.app.data11[index].freq)}", + f"{format_frequency_short(self.app.data.s11[index].freq)}", QtWidgets.QLabel(f" ({diff[i]['freq']})" - f" {format_complex_imp(self.app.data11[index].impedance())}" + f" {format_complex_imp(self.app.data.s11[index].impedance())}" f" ({diff[i]['r']})" f" {diff[i]['lambda']} m")) @@ -402,16 +388,17 @@ def runAnalysis(self): row = extended_data[index] writer.writerow(row) - def compare(self, old, new, fields=[("freq", str), ]): + def compare(self, old, new, fields=None): ''' Compare data to help changes NB - must be same sweep + must be same sweep ( same index must be same frequence ) :param old: :param new: ''' + fields = fields or [("freq", str), ] def no_compare(): @@ -455,7 +442,7 @@ def no_compare(): if delta_f > 0: logger.debug("possible missing band, ") - if (len(old_idx) > (i + split + 1)): + if len(old_idx) > (i + split + 1): if abs(new[k]["freq"] - old[old_idx[i + split + 1]]["freq"]) < max_delta_f: logger.debug("new is missing band, compare next ") split += 1 diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index b5146982..c9e88ad8 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -97,8 +97,7 @@ def get(self, freq: int) -> CalData: return self.data[freq] def items(self): - for item in self.data.items(): - yield item + yield from self.data.items() def values(self): for freq in self.frequencies(): @@ -212,14 +211,14 @@ def calc_corrections(self): caldata["delta_e"] = - ((g1 * (gm2 - gm3) - g2 * gm2 + g3 * gm3) * gm1 + (g2 * gm3 - g3 * gm3) * gm2) / denominator - except ZeroDivisionError: + except ZeroDivisionError as exc: self.isCalculated = False logger.error( "Division error - did you use the same measurement" " for two of short, open and load?") raise ValueError( f"Two of short, open and load returned the same" - f" values at frequency {freq}Hz.") + f" values at frequency {freq}Hz.") from exc if self.isValid2Port(): caldata["e30"] = caldata["isolation"].z @@ -236,38 +235,36 @@ def gamma_short(self, freq: int) -> complex: g = Calibration.IDEAL_SHORT if not self.useIdealShort: logger.debug("Using short calibration set values.") - Zsp = complex(0, 1) * 2 * math.pi * freq * ( + Zsp = complex(0, 2 * math.pi * freq * ( self.shortL0 + self.shortL1 * freq + - self.shortL2 * freq**2 + self.shortL3 * freq**3) + self.shortL2 * freq**2 + self.shortL3 * freq**3)) # Referencing https://arxiv.org/pdf/1606.02446.pdf (18) - (21) g = (Zsp / 50 - 1) / (Zsp / 50 + 1) * cmath.exp( - complex(0, 1) * 2 * math.pi * 2 * freq * - self.shortLength * -1) + complex(0, 2 * math.pi * 2 * freq * self.shortLength * -1)) return g def gamma_open(self, freq: int) -> complex: g = Calibration.IDEAL_OPEN if not self.useIdealOpen: logger.debug("Using open calibration set values.") - divisor = (2 * math.pi * freq * ( + Zop = complex(0, 2 * math.pi * freq * ( self.openC0 + self.openC1 * freq + self.openC2 * freq**2 + self.openC3 * freq**3)) - if divisor != 0: - Zop = complex(0, -1) / divisor - g = ((Zop / 50 - 1) / (Zop / 50 + 1)) * cmath.exp( - complex(0, 1) * 2 * math.pi * - 2 * freq * self.openLength * -1) + g = ((1 - 50 * Zop) / (1 + 50 * Zop)) * cmath.exp( + complex(0, 2 * math.pi * 2 * freq * self.openLength * -1)) return g def gamma_load(self, freq: int) -> complex: g = Calibration.IDEAL_LOAD if not self.useIdealLoad: logger.debug("Using load calibration set values.") - Zl = self.loadR + (complex(0, 1) * 2 * - math.pi * freq * self.loadL) + Zl = complex(self.loadR, 0) + if self.loadC > 0: + Zl = self.loadR / complex(1, 2 * self.loadR * math.pi * freq * self.loadC) + if self.loadL > 0: + Zl = Zl + complex(0, 2 * math.pi * freq * self.loadL) g = (Zl / 50 - 1) / (Zl / 50 + 1) * cmath.exp( - complex(0, 1) * 2 * math.pi * - 2 * freq * self.loadLength * -1) + complex(0, 2 * math.pi * 2 * freq * self.loadLength * -1)) return g def gamma_through(self, freq: int) -> complex: @@ -354,12 +351,10 @@ def load(self, filename): self.notes.append(note) continue if line.startswith("#"): - if not parsed_header: - # Check that this is a valid header - if line == ( - "# Hz ShortR ShortI OpenR OpenI LoadR LoadI" - " ThroughR ThroughI IsolationR IsolationI"): - parsed_header = True + if not parsed_header and line == ( + "# Hz ShortR ShortI OpenR OpenI LoadR LoadI" + " ThroughR ThroughI IsolationR IsolationI"): + parsed_header = True continue if not parsed_header: logger.warning( @@ -372,11 +367,7 @@ def load(self, filename): logger.warning("Illegal data in cal file. Line %i", i) cal = m.groupdict() - if cal["throughr"]: - nr_cals = 5 - else: - nr_cals = 3 - + nr_cals = 5 if cal["throughr"] else 3 for name in Calibration.CAL_NAMES[:nr_cals]: self.dataset.insert( name, diff --git a/NanoVNASaver/Charts/CLogMag.py b/NanoVNASaver/Charts/CLogMag.py index a4776d0c..fa2f8fe4 100644 --- a/NanoVNASaver/Charts/CLogMag.py +++ b/NanoVNASaver/Charts/CLogMag.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,11 +20,12 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart -from .LogMag import LogMagChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart +from NanoVNASaver.Charts.LogMag import LogMagChart logger = logging.getLogger(__name__) @@ -32,9 +33,7 @@ class CombinedLogMagChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.minDisplayValue = -80 self.maxDisplayValue = 10 @@ -50,15 +49,6 @@ def __init__(self, name=""): self.isInverted = False - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def setCombinedData(self, data11, data21): self.data11 = data11 self.data21 = data21 @@ -80,23 +70,23 @@ def resetDisplayLimits(self): self.update() def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) - qp.drawText(int(round(self.chartWidth / 2)) - 20, 15, self.name + " (dB)") + qp.setPen(QtGui.QPen(Chart.color.text)) + qp.drawText(int(round(self.dim.width / 2)) - 20, 15, self.name + " (dB)") qp.drawText(10, 15, "S11") - qp.drawText(self.leftMargin + self.chartWidth - 8, 15, "S21") - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.drawText(self.leftMargin + self.dim.width - 8, 15, "S21") + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin, self.topMargin - 5, - self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) def drawValues(self, qp: QtGui.QPainter): if len(self.data11) == 0 and len(self.reference11) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if not self.fixedSpan: @@ -218,26 +208,26 @@ def drawValues(self, qp: QtGui.QPainter): for i in range(tick_count): db = first_tick + i * tick_step - y = self.topMargin + round((maxValue - db)/span*self.chartHeight) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) + y = self.topMargin + round((maxValue - db)/span*self.dim.height) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.dim.width, y) if db > minValue and db != maxValue: - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) if tick_step < 1: dbstr = str(round(db, 1)) else: dbstr = str(db) qp.drawText(3, y + 4, dbstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(maxValue)) - qp.drawText(3, self.chartHeight+self.topMargin, str(minValue)) + qp.drawText(3, self.dim.height+self.topMargin, str(minValue)) self.drawFrequencyTicks(qp) - qp.setPen(self.swrColor) + qp.setPen(Chart.color.swr) for vswr in self.swrMarkers: if vswr <= 1: continue @@ -245,45 +235,45 @@ def drawValues(self, qp: QtGui.QPainter): if self.isInverted: logMag = logMag * -1 y = self.topMargin + round((self.maxValue - logMag) / - self.span * self.chartHeight) + self.span * self.dim.height) qp.drawLine(self.leftMargin, y, - self.leftMargin + self.chartWidth, y) + self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr)) if len(self.data11) > 0: - c = QtGui.QColor(self.sweepColor) + c = QtGui.QColor(Chart.color.sweep) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(33, 9, 38, 9) - c = QtGui.QColor(self.secondarySweepColor) + c = QtGui.QColor(Chart.color.sweep_secondary) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.chartWidth - 20, 9, - self.leftMargin + self.chartWidth - 15, 9) + qp.drawLine(self.leftMargin + self.dim.width - 20, 9, + self.leftMargin + self.dim.width - 15, 9) if len(self.reference11) > 0: - c = QtGui.QColor(self.referenceColor) + c = QtGui.QColor(Chart.color.reference) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(33, 14, 38, 14) - c = QtGui.QColor(self.secondaryReferenceColor) + c = QtGui.QColor(Chart.color.reference_secondary) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.chartWidth - 20, 14, - self.leftMargin + self.chartWidth - 15, 14) + qp.drawLine(self.leftMargin + self.dim.width - 20, 14, + self.leftMargin + self.dim.width - 15, 14) - self.drawData(qp, self.data11, self.sweepColor) - self.drawData(qp, self.data21, self.secondarySweepColor) - self.drawData(qp, self.reference11, self.referenceColor) - self.drawData(qp, self.reference21, self.secondaryReferenceColor) + self.drawData(qp, self.data11, Chart.color.sweep) + self.drawData(qp, self.data21, Chart.color.sweep_secondary) + self.drawData(qp, self.reference11, Chart.color.reference) + self.drawData(qp, self.reference21, Chart.color.reference_secondary) self.drawMarkers(qp, data=self.data11) self.drawMarkers(qp, data=self.data21) @@ -291,11 +281,11 @@ def getYPosition(self, d: Datapoint) -> int: logMag = self.logMag(d) if math.isinf(logMag): return None - return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - logMag) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val] def logMag(self, p: Datapoint) -> float: diff --git a/NanoVNASaver/Charts/Capacitance.py b/NanoVNASaver/Charts/Capacitance.py index 3db11418..b0647b05 100644 --- a/NanoVNASaver/Charts/Capacitance.py +++ b/NanoVNASaver/Charts/Capacitance.py @@ -18,13 +18,15 @@ # along with this program. If not, see . import math import logging +from decimal import InvalidOperation from typing import List from PyQt5 import QtWidgets, QtGui from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -32,9 +34,9 @@ class CapacitanceChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.leftMargin = 45 + self.dim.width = 250 + self.dim.height = 250 self.minDisplayValue = 0 self.maxDisplayValue = 100 @@ -42,31 +44,31 @@ def __init__(self, name=""): self.maxValue = 1 self.span = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) + self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin, + self.dim.height + self.topMargin + self.bottomMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name + " (F)") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if not self.fixedSpan: @@ -118,39 +120,46 @@ def drawValues(self, qp: QtGui.QPainter): span = 1e-15 self.span = span - target_ticks = math.floor(self.chartHeight / 60) - fmt = Format(max_nr_digits=1) + target_ticks = math.floor(self.dim.height / 60) + fmt = Format(max_nr_digits=3) for i in range(target_ticks): val = minValue + (i / target_ticks) * span - y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) - qp.setPen(self.textColor) - if val != minValue: - valstr = str(Value(val, fmt=fmt)) - qp.drawText(3, y + 3, valstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) - - qp.setPen(QtGui.QPen(self.foregroundColor)) + try: + y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height) + qp.setPen(Chart.color.text) + if val != minValue: + valstr = str(Value(val, fmt=fmt)) + qp.drawText(3, y + 3, valstr) + except (ValueError, InvalidOperation) as exc: + logger.debug("Drawing wrent wrong: %s", exc) + y = self.topMargin + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) + + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt))) - qp.drawText(3, self.chartHeight+self.topMargin, str(Value(minValue, fmt=fmt))) + qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt))) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: - return ( - self.topMargin + - round((self.maxValue - d.capacitiveEquivalent()) / - self.span * self.chartHeight)) + try: + return ( + self.topMargin + + round((self.maxValue - d.capacitiveEquivalent()) / + self.span * self.dim.height)) + except ValueError: + return self.topMargin def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val * 10e11] def copy(self): @@ -163,8 +172,8 @@ class InductanceChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.dim.width = 250 + self.dim.height = 250 self.minDisplayValue = 0 self.maxDisplayValue = 100 @@ -172,31 +181,31 @@ def __init__(self, name=""): self.maxValue = 1 self.span = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) + self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin, + self.dim.height + self.topMargin + self.bottomMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name + " (H)") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if not self.fixedSpan: @@ -248,38 +257,38 @@ def drawValues(self, qp: QtGui.QPainter): span = 1e-15 self.span = span - target_ticks = math.floor(self.chartHeight / 60) - fmt = Format(max_nr_digits=1) + target_ticks = math.floor(self.dim.height / 60) + fmt = Format(max_nr_digits=3) for i in range(target_ticks): val = minValue + (i / target_ticks) * span - y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) - qp.setPen(self.textColor) + y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height) + qp.setPen(Chart.color.text) if val != minValue: valstr = str(Value(val, fmt=fmt)) qp.drawText(3, y + 3, valstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt))) - qp.drawText(3, self.chartHeight+self.topMargin, str(Value(minValue, fmt=fmt))) + qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt))) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: return (self.topMargin + round((self.maxValue - d.inductiveEquivalent()) / - self.span * self.chartHeight)) + self.span * self.dim.height)) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val * 10e11] def copy(self): diff --git a/NanoVNASaver/Charts/Chart.py b/NanoVNASaver/Charts/Chart.py index a53321c2..abb57582 100644 --- a/NanoVNASaver/Charts/Chart.py +++ b/NanoVNASaver/Charts/Chart.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,102 +16,122 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import math -from typing import List, Set import logging +from dataclasses import dataclass, replace +from typing import List, Set, Tuple, ClassVar, Any + from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtCore import pyqtSignal from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.Marker import Marker + logger = logging.getLogger(__name__) +@dataclass +class ChartColors: # pylint: disable=too-many-instance-attributes + background: QtGui.QColor = QtGui.QColor(QtCore.Qt.white) + foreground: QtGui.QColor = QtGui.QColor(QtCore.Qt.lightGray) + reference: QtGui.QColor = QtGui.QColor(0, 0, 255, 64) + reference_secondary: QtGui.QColor = QtGui.QColor(0, 0, 192, 48) + sweep: QtGui.QColor = QtGui.QColor(QtCore.Qt.darkYellow) + sweep_secondary: QtGui.QColor = QtGui.QColor(QtCore.Qt.darkMagenta) + swr: QtGui.QColor = QtGui.QColor(255, 0, 0, 128) + text: QtGui.QColor = QtGui.QColor(QtCore.Qt.black) + bands: QtGui.QColor = QtGui.QColor(128, 128, 128, 48) + +@dataclass +class ChartDimensions: + height: int = 200 + height_min: int = 200 + width: int = 200 + width_min: int = 200 + line: int = 1 + point: int = 2 + +@dataclass +class ChartDragBox: + pos: Tuple[int] = (-1, -1) + pos_start: Tuple[int] = (0, 0) + state: bool = False + move_x: int = -1 + move_y: int = -1 + +@dataclass +class ChartFlags: + draw_lines: bool = False + is_popout: bool = False + +@dataclass +class ChartMarkerConfig: + draw_label: bool = False + fill: bool = False + at_tip: bool = False + size: int = 3 + +class ChartMarker(QtWidgets.QWidget): + cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig() + + def __init__(self, qp: QtGui.QPaintDevice): + super().__init__() + self.qp = qp + + def draw(self, x: int, y: int, color: QtGui.QColor, text: str = ""): + offset = self.cfg.size // 2 + if self.cfg.at_tip: + y -= offset + pen = QtGui.QPen(color) + self.qp.setPen(pen) + qpp = QtGui.QPainterPath() + qpp.moveTo(x, y + offset) + qpp.lineTo(x - offset, y - offset) + qpp.lineTo(x + offset, y - offset) + qpp.lineTo(x, y + offset) + + if self.cfg.fill: + self.qp.fillPath(qpp, color) + else: + self.qp.drawPath(qpp) + + if text and self.cfg.draw_label: + text_width = self.qp.fontMetrics().horizontalAdvance(text) + self.qp.drawText(x - text_width // 2, y - 3 - offset, text) + + class Chart(QtWidgets.QWidget): - sweepColor = QtCore.Qt.darkYellow - secondarySweepColor = QtCore.Qt.darkMagenta - referenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue) - referenceColor.setAlpha(64) - secondaryReferenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue) - secondaryReferenceColor.setAlpha(64) - backgroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.white) - foregroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.lightGray) - textColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.black) - swrColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.red) - swrColor.setAlpha(128) - data: List[Datapoint] = [] - reference: List[Datapoint] = [] - markers: List[Marker] = [] - swrMarkers: Set[float] = set() - bands = None - draggedMarker: Marker = None - name = "" - sweepTitle = "" - drawLines = False - minChartHeight = 200 - minChartWidth = 200 - chartWidth = minChartWidth - chartHeight = minChartHeight - lineThickness = 1 - pointSize = 2 - markerSize = 3 - drawMarkerNumbers = False - markerAtTip = False - filledMarkers = False - draggedBox = False - draggedBoxStart = (0, 0) - draggedBoxCurrent = (-1, -1) - moveStartX = -1 - moveStartY = -1 - - isPopout = False - popoutRequested = pyqtSignal(object) + bands: ClassVar[Any] = None + popoutRequested: ClassVar[Any] = pyqtSignal(object) + color: ClassVar[ChartColors] = ChartColors() + marker_cfg: ClassVar[ChartMarkerConfig] = ChartMarkerConfig() def __init__(self, name): super().__init__() self.name = name + self.sweepTitle = "" - self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - self.action_save_screenshot = QtWidgets.QAction("Save image") - self.action_save_screenshot.triggered.connect(self.saveScreenshot) - self.addAction(self.action_save_screenshot) - self.action_popout = QtWidgets.QAction("Popout chart") - self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self)) - self.addAction(self.action_popout) + self.dim = ChartDimensions() + self.dragbox = ChartDragBox() + self.flag = ChartFlags() - self.swrMarkers = set() - - def setSweepColor(self, color: QtGui.QColor): - self.sweepColor = color - self.update() + self.draggedMarker = None - def setSecondarySweepColor(self, color: QtGui.QColor): - self.secondarySweepColor = color - self.update() + self.data: List[Datapoint] = [] + self.reference: List[Datapoint] = [] - def setReferenceColor(self, color: QtGui.QColor): - self.referenceColor = color - self.update() + self.markers: List[Marker] = [] + self.swrMarkers: Set[float] = set() - def setSecondaryReferenceColor(self, color: QtGui.QColor): - self.secondaryReferenceColor = color - self.update() - - def setBackgroundColor(self, color: QtGui.QColor): - self.backgroundColor = color - pal = self.palette() - pal.setColor(QtGui.QPalette.Background, color) - self.setPalette(pal) - self.update() + self.action_popout = QtWidgets.QAction("Popout chart") + self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self)) + self.addAction(self.action_popout) - def setForegroundColor(self, color: QtGui.QColor): - self.foregroundColor = color - self.update() + self.action_save_screenshot = QtWidgets.QAction("Save image") + self.action_save_screenshot.triggered.connect(self.saveScreenshot) + self.addAction(self.action_save_screenshot) - def setTextColor(self, color: QtGui.QColor): - self.textColor = color - self.update() + self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) def setReference(self, data): self.reference = data @@ -132,15 +152,15 @@ def setBands(self, bands): self.bands = bands def setLineThickness(self, thickness): - self.lineThickness = thickness + self.dim.line = thickness self.update() def setPointSize(self, size): - self.pointSize = size + self.dim.point = size self.update() def setMarkerSize(self, size): - self.markerSize = size + ChartMarker.cfg.size = size self.update() def setSweepTitle(self, title): @@ -162,49 +182,19 @@ def getNearestMarker(self, x, y) -> Marker: nearest = None for m in self.markers: mx, my = self.getPosition(self.data[m.location]) - dx = abs(x - mx) - dy = abs(y - my) - distance = math.sqrt(dx**2 + dy**2) + distance = abs(complex(x - mx, y - my)) if distance < shortest: shortest = distance nearest = m return nearest - def getYPosition(self, d: Datapoint) -> int: - return 0 - - def getXPosition(self, d: Datapoint) -> int: - return 0 - - def getPosition(self, d: Datapoint) -> (int, int): + def getPosition(self, d: Datapoint) -> Tuple[int, int]: return self.getXPosition(d), self.getYPosition(d) def setDrawLines(self, draw_lines): - self.drawLines = draw_lines + self.flag.draw_lines = draw_lines self.update() - def setDrawMarkerNumbers(self, draw_marker_numbers): - self.drawMarkerNumbers = draw_marker_numbers - self.update() - - def setMarkerAtTip(self, marker_at_tip): - self.markerAtTip = marker_at_tip - self.update() - - def setFilledMarkers(self, filled_markers): - self.filledMarkers = filled_markers - self.update() - - @staticmethod - def shortenFrequency(frequency: int) -> str: - if frequency < 50000: - return str(frequency) - if frequency < 5000000: - return str(round(frequency / 1000)) + "k" - if frequency < 50000000: - return str(round(frequency / 1000000, 2)) + "M" - return str(round(frequency / 1000000, 1)) + "M" - def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: if event.buttons() == QtCore.Qt.RightButton: event.ignore() @@ -212,64 +202,57 @@ def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: if event.buttons() == QtCore.Qt.MiddleButton: # Drag event event.accept() - self.moveStartX = event.x() - self.moveStartY = event.y() + self.dragbox.move_x = event.x() + self.dragbox.move_y = event.y() return - if event.modifiers() == QtCore.Qt.ShiftModifier: - self.draggedMarker = self.getNearestMarker(event.x(), event.y()) - elif event.modifiers() == QtCore.Qt.ControlModifier: + if event.modifiers() == QtCore.Qt.ControlModifier: event.accept() - self.draggedBox = True - self.draggedBoxStart = (event.x(), event.y()) + self.dragbox.state = True + self.dragbox.pos_start = (event.x(), event.y()) return + if event.modifiers() == QtCore.Qt.ShiftModifier: + self.draggedMarker = self.getNearestMarker(event.x(), event.y()) self.mouseMoveEvent(event) - def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None: + def mouseReleaseEvent(self, a0: QtGui.QMouseEvent): self.draggedMarker = None - if self.draggedBox: - self.zoomTo(self.draggedBoxStart[0], self.draggedBoxStart[1], a0.x(), a0.y()) - self.draggedBox = False - self.draggedBoxCurrent = (-1, -1) - self.draggedBoxStart = (0, 0) + if self.dragbox.state: + self.zoomTo(self.dragbox.pos_start[0], self.dragbox.pos_start[1], a0.x(), a0.y()) + self.dragbox.state = False + self.dragbox.pos = (-1, -1) + self.dragbox.pos_start = (0, 0) self.update() def zoomTo(self, x1, y1, x2, y2): - pass + raise NotImplementedError() def saveScreenshot(self): logger.info("Saving %s to file...", self.name) - filename, _ = QtWidgets.QFileDialog.getSaveFileName(parent=self, caption="Save image", - filter="PNG (*.png);;All files (*.*)") + filename, _ = QtWidgets.QFileDialog.getSaveFileName( + parent=self, caption="Save image", + filter="PNG (*.png);;All files (*.*)") logger.debug("Filename: %s", filename) - if filename != "": - if not QtCore.QFileInfo(filename).suffix(): - filename += ".png" - self.grab().save(filename) + if not filename: + return + if not QtCore.QFileInfo(filename).suffix(): + filename += ".png" + self.grab().save(filename) def copy(self): new_chart = self.__class__(self.name) new_chart.data = self.data new_chart.reference = self.reference - new_chart.sweepColor = self.sweepColor - new_chart.secondarySweepColor = self.secondarySweepColor - new_chart.referenceColor = self.referenceColor - new_chart.secondaryReferenceColor = self.secondaryReferenceColor - new_chart.setBackgroundColor(self.backgroundColor) - new_chart.textColor = self.textColor - new_chart.foregroundColor = self.foregroundColor - new_chart.swrColor = self.swrColor + new_chart.dim = replace(self.dim) + new_chart.flag = replace(self.flag) + new_chart.marker_cfg = replace(self.marker_cfg) new_chart.markers = self.markers new_chart.swrMarkers = self.swrMarkers new_chart.bands = self.bands - new_chart.drawLines = self.drawLines - new_chart.markerSize = self.markerSize - new_chart.drawMarkerNumbers = self.drawMarkerNumbers - new_chart.filledMarkers = self.filledMarkers - new_chart.markerAtTip = self.markerAtTip + new_chart.resize(self.width(), self.height()) - new_chart.setPointSize(self.pointSize) - new_chart.setLineThickness(self.lineThickness) + new_chart.setPointSize(self.dim.point) + new_chart.setLineThickness(self.dim.line) return new_chart def addSWRMarker(self, swr: float): @@ -289,36 +272,22 @@ def clearSWRMarkers(self): self.swrMarkers.clear() self.update() - def setSWRColor(self, color: QtGui.QColor): - self.swrColor = color - self.update() - def drawMarker(self, x, y, qp: QtGui.QPainter, color: QtGui.QColor, number=0): - if self.markerAtTip: - y -= self.markerSize - pen = QtGui.QPen(color) - qp.setPen(pen) - qpp = QtGui.QPainterPath() - qpp.moveTo(x, y + self.markerSize) - qpp.lineTo(x - self.markerSize, y - self.markerSize) - qpp.lineTo(x + self.markerSize, y - self.markerSize) - qpp.lineTo(x, y + self.markerSize) - - if self.filledMarkers: - qp.fillPath(qpp, color) - else: - qp.drawPath(qpp) - - if self.drawMarkerNumbers: - number_x = x - 3 - number_y = y - self.markerSize - 3 - qp.drawText(number_x, number_y, str(number)) + cmarker = ChartMarker(qp) + cmarker.draw(x, y, color, str(number)) def drawTitle(self, qp: QtGui.QPainter, position: QtCore.QPoint = None): - if self.sweepTitle != "": - qp.setPen(self.textColor) - if position is None: - qf = QtGui.QFontMetricsF(self.font()) - width = qf.boundingRect(self.sweepTitle).width() - position = QtCore.QPointF(self.width()/2 - width/2, 15) - qp.drawText(position, self.sweepTitle) + if not self.sweepTitle: + return + qp.setPen(Chart.color.text) + if position is None: + qf = QtGui.QFontMetricsF(self.font()) + width = qf.boundingRect(self.sweepTitle).width() + position = QtCore.QPointF(self.width()/2 - width/2, 15) + qp.drawText(position, self.sweepTitle) + + def update(self): + pal = self.palette() + pal.setColor(QtGui.QPalette.Background, Chart.color.background) + self.setPalette(pal) + super().update() diff --git a/NanoVNASaver/Charts/Frequency.py b/NanoVNASaver/Charts/Frequency.py index 66e9fd68..87674d5e 100644 --- a/NanoVNASaver/Charts/Frequency.py +++ b/NanoVNASaver/Charts/Frequency.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,14 +18,17 @@ # along with this program. If not, see . import math import logging -from typing import List +from typing import List, Tuple import numpy as np from PyQt5 import QtWidgets, QtGui, QtCore -from NanoVNASaver.Formatting import parse_frequency +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Formatting import ( + parse_frequency, parse_value, + format_frequency_chart, format_y_axis) from NanoVNASaver.RFTools import Datapoint -from .Chart import Chart +from NanoVNASaver.SITools import Format, Value logger = logging.getLogger(__name__) @@ -37,6 +40,7 @@ class FrequencyChart(Chart): maxFrequency = 100000000 minFrequency = 1000000 + # TODO: use unscaled values instead of unit dependend ones minDisplayValue = -1 maxDisplayValue = 1 @@ -53,6 +57,18 @@ class FrequencyChart(Chart): def __init__(self, name): super().__init__(name) + self.leftMargin = 30 + self.dim.width = 250 + self.dim.height = 250 + self.fstart = 0 + self.fstop = 0 + + self.name_unit = "" + self.value_function = lambda x: 0.0 + + self.minValue = -1 + self.maxValue = 1 + self.span = 1 self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) mode_group = QtWidgets.QActionGroup(self) @@ -79,11 +95,11 @@ def __init__(self, name): self.x_menu.addSeparator() self.action_set_fixed_start = QtWidgets.QAction( - "Start (" + Chart.shortenFrequency(self.minFrequency) + ")") + "Start (" + format_frequency_chart(self.minFrequency) + ")") self.action_set_fixed_start.triggered.connect(self.setMinimumFrequency) self.action_set_fixed_stop = QtWidgets.QAction( - "Stop (" + Chart.shortenFrequency(self.maxFrequency) + ")") + "Stop (" + format_frequency_chart(self.maxFrequency) + ")") self.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency) self.x_menu.addAction(self.action_set_fixed_start) @@ -122,14 +138,14 @@ def __init__(self, name): self.y_menu.addAction(self.y_action_fixed_span) self.y_menu.addSeparator() - self.action_set_fixed_maximum = QtWidgets.QAction( - f"Maximum ({self.maxDisplayValue})") - self.action_set_fixed_maximum.triggered.connect(self.setMaximumValue) - self.action_set_fixed_minimum = QtWidgets.QAction( f"Minimum ({self.minDisplayValue})") self.action_set_fixed_minimum.triggered.connect(self.setMinimumValue) + self.action_set_fixed_maximum = QtWidgets.QAction( + f"Maximum ({self.maxDisplayValue})") + self.action_set_fixed_maximum.triggered.connect(self.setMaximumValue) + self.y_menu.addAction(self.action_set_fixed_maximum) self.y_menu.addAction(self.action_set_fixed_minimum) @@ -160,11 +176,35 @@ def __init__(self, name): self.menu.addAction(self.action_popout) self.setFocusPolicy(QtCore.Qt.ClickFocus) + self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin, + self.dim.height + self.topMargin + self.bottomMargin) + self.setSizePolicy( + QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding)) + pal = QtGui.QPalette() + pal.setColor(QtGui.QPalette.Background, Chart.color.background) + self.setPalette(pal) + self.setAutoFillBackground(True) + + def _set_start_stop(self): + if self.fixedSpan: + fstart = self.minFrequency + fstop = self.maxFrequency + else: + if len(self.data) > 0: + fstart = self.data[0].freq + fstop = self.data[len(self.data)-1].freq + else: + fstart = self.reference[0].freq + fstop = self.reference[len(self.reference) - 1].freq + self.fstart = fstart + self.fstop = fstop + def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({Chart.shortenFrequency(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})") self.action_set_fixed_stop.setText( - f"Stop ({Chart.shortenFrequency(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})") self.action_set_fixed_minimum.setText( f"Minimum ({self.minDisplayValue})") self.action_set_fixed_maximum.setText( @@ -192,12 +232,6 @@ def setFixedSpan(self, fixed_span: bool): def setFixedValues(self, fixed_values: bool): self.fixedValues = fixed_values - if fixed_values and self.minDisplayValue >= self.maxDisplayValue: - self.fixedValues = False - self.y_action_automatic.setChecked(True) - self.y_action_fixed_span.setChecked(False) - if fixed_values and self.minDisplayValue <= 0: - self.minDisplayValue = 0.01 self.update() def setLogarithmicX(self, logarithmic: bool): @@ -217,11 +251,15 @@ def setMinimumFrequency(self): "Set start frequency", text=str(self.minFrequency)) if not selected: return + span = abs(self.maxFrequency - self.minFrequency) min_freq = parse_frequency(min_freq_str) - if min_freq > 0 and not (self.fixedSpan and min_freq >= self.maxFrequency): - self.minFrequency = min_freq - if self.fixedSpan: - self.update() + if min_freq < 0: + return + self.minFrequency = min_freq + if self.minFrequency >= self.maxFrequency: + self.maxFrequency = self.minFrequency + span + self.fixedSpan = True + self.update() def setMaximumFrequency(self): max_freq_str, selected = QtWidgets.QInputDialog.getText( @@ -229,37 +267,48 @@ def setMaximumFrequency(self): "Set stop frequency", text=str(self.maxFrequency)) if not selected: return + span = abs(self.maxFrequency - self.minFrequency) max_freq = parse_frequency(max_freq_str) - if max_freq > 0 and not (self.fixedSpan and max_freq <= self.minFrequency): - self.maxFrequency = max_freq - if self.fixedSpan: - self.update() + if max_freq < 0: + return + self.maxFrequency = max_freq + if self.maxFrequency <= self.minFrequency: + self.minFrequency = max(self.maxFrequency - span, 0) + self.fixedSpan = True + self.update() def setMinimumValue(self): - min_val, selected = QtWidgets.QInputDialog.getDouble( + text, selected = QtWidgets.QInputDialog.getText( self, "Minimum value", - "Set minimum value", value=self.minDisplayValue, - decimals=3) + "Set minimum value", + text=format_y_axis(self.minDisplayValue, self.name_unit)) if not selected: return - if not (self.fixedValues and min_val >= self.maxDisplayValue): - self.minDisplayValue = min_val + min_val = parse_value(text) + yspan = abs(self.maxDisplayValue - self.minDisplayValue) + self.minDisplayValue = min_val + if self.minDisplayValue >= self.maxDisplayValue: + self.maxDisplayValue = self.minDisplayValue + yspan + # TODO: negativ logarythmical scale if self.logarithmicY and min_val <= 0: self.minDisplayValue = 0.01 - if self.fixedValues: - self.update() + self.fixedValues = True + self.update() def setMaximumValue(self): - max_val, selected = QtWidgets.QInputDialog.getDouble( + text, selected = QtWidgets.QInputDialog.getText( self, "Maximum value", - "Set maximum value", value=self.maxDisplayValue, - decimals=3) + "Set maximum value", + text=format_y_axis(self.maxDisplayValue, self.name_unit)) if not selected: return - if not (self.fixedValues and max_val <= self.minDisplayValue): - self.maxDisplayValue = max_val - if self.fixedValues: - self.update() + max_val = parse_value(text) + yspan = abs(self.maxDisplayValue - self.minDisplayValue) + self.maxDisplayValue = max_val + if self.maxDisplayValue <= self.minDisplayValue: + self.minDisplayValue = self.maxDisplayValue - yspan + self.fixedValues = True + self.update() def resetDisplayLimits(self): self.fixedValues = False @@ -279,12 +328,18 @@ def getXPosition(self, d: Datapoint) -> int: if self.logarithmicX: span = math.log(self.fstop) - math.log(self.fstart) return self.leftMargin + round( - self.chartWidth * (math.log(d.freq) - + self.dim.width * (math.log(d.freq) - math.log(self.fstart)) / span) return self.leftMargin + round( - self.chartWidth * (d.freq - self.fstart) / span) + self.dim.width * (d.freq - self.fstart) / span) return math.floor(self.width()/2) + def getYPosition(self, d: Datapoint) -> int: + return ( + self.topMargin + + round((self.maxValue - d.capacitiveEquivalent()) / + self.span * self.dim.height)) + def frequencyAtPosition(self, x, limit=True) -> int: """ Calculates the frequency at a given X-position @@ -300,14 +355,14 @@ def frequencyAtPosition(self, x, limit=True) -> int: absx = x - self.leftMargin if limit and absx < 0: return self.fstart - if limit and absx > self.chartWidth: + if limit and absx > self.dim.width: return self.fstop if self.logarithmicX: span = math.log(self.fstop) - math.log(self.fstart) - step = span/self.chartWidth + step = span/self.dim.width return round(math.exp(math.log(self.fstart) + absx * step)) span = self.fstop - self.fstart - step = span/self.chartWidth + step = span/self.dim.width return round(self.fstart + absx * step) return -1 @@ -322,10 +377,13 @@ def valueAtPosition(self, _) -> List[float]: is above or below the chart, returns maximum or minimum values. """ - return [] + absy = y - self.topMargin + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) + return [val * 10e11] def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: - if len(self.data) == 0 and len(self.reference) == 0: + if ((len(self.data) == 0 and len(self.reference) == 0) or + a0.angleDelta().y() == 0): a0.ignore() return do_zoom_x = do_zoom_y = True @@ -333,54 +391,28 @@ def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: do_zoom_x = False if a0.modifiers() == QtCore.Qt.ControlModifier: do_zoom_y = False - if a0.angleDelta().y() > 0: - # Zoom in - a0.accept() - # Center of zoom = a0.x(), a0.y() - # We zoom in by 1/10 of the width/height. - rate = a0.angleDelta().y() / 120 - if do_zoom_x: - zoomx = rate * self.chartWidth / 10 - else: - zoomx = 0 - if do_zoom_y: - zoomy = rate * self.chartHeight / 10 - else: - zoomy = 0 - absx = max(0, a0.x() - self.leftMargin) - absy = max(0, a0.y() - self.topMargin) - ratiox = absx/self.chartWidth - ratioy = absy/self.chartHeight - p1x = int(self.leftMargin + ratiox * zoomx) - p1y = int(self.topMargin + ratioy * zoomy) - p2x = int(self.leftMargin + self.chartWidth - (1 - ratiox) * zoomx) - p2y = int(self.topMargin + self.chartHeight - (1 - ratioy) * zoomy) - self.zoomTo(p1x, p1y, p2x, p2y) - elif a0.angleDelta().y() < 0: - # Zoom out - a0.accept() - # Center of zoom = a0.x(), a0.y() - # We zoom out by 1/9 of the width/height, to match zoom in. - rate = -a0.angleDelta().y() / 120 - if do_zoom_x: - zoomx = rate * self.chartWidth / 9 - else: - zoomx = 0 - if do_zoom_y: - zoomy = rate * self.chartHeight / 9 - else: - zoomy = 0 - absx = max(0, a0.x() - self.leftMargin) - absy = max(0, a0.y() - self.topMargin) - ratiox = absx/self.chartWidth - ratioy = absy/self.chartHeight - p1x = int(self.leftMargin - ratiox * zoomx) - p1y = int(self.topMargin - ratioy * zoomy) - p2x = int(self.leftMargin + self.chartWidth + (1 - ratiox) * zoomx) - p2y = int(self.topMargin + self.chartHeight + (1 - ratioy) * zoomy) - self.zoomTo(p1x, p1y, p2x, p2y) - else: - a0.ignore() + self._wheel_zomm( + a0, do_zoom_x, do_zoom_y, + math.copysign(1, a0.angleDelta().y())) + + + def _wheel_zomm(self, a0, do_zoom_x, do_zoom_y, sign: int=1): + # Zoom in + a0.accept() + # Center of zoom = a0.x(), a0.y() + # We zoom in by 1/10 of the width/height. + rate = sign * a0.angleDelta().y() / 120 + zoomx = rate * self.dim.width / 10 if do_zoom_x else 0 + zoomy = rate * self.dim.height / 10 if do_zoom_y else 0 + absx = max(0, a0.x() - self.leftMargin) + absy = max(0, a0.y() - self.topMargin) + ratiox = absx/self.dim.width + ratioy = absy/self.dim.height + p1x = int(self.leftMargin + ratiox * zoomx) + p1y = int(self.topMargin + ratioy * zoomy) + p2x = int(self.leftMargin + self.dim.width - (1 - ratiox) * zoomx) + p2y = int(self.topMargin + self.dim.height - (1 - ratioy) * zoomy) + self.zoomTo(p1x, p1y, p2x, p2y) def zoomTo(self, x1, y1, x2, y2): val1 = self.valueAtPosition(y1) @@ -408,21 +440,21 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent): if a0.buttons() == QtCore.Qt.MiddleButton: # Drag the display a0.accept() - if self.moveStartX != -1 and self.moveStartY != -1: - dx = self.moveStartX - a0.x() - dy = self.moveStartY - a0.y() + if self.dragbox.move_x != -1 and self.dragbox.move_y != -1: + dx = self.dragbox.move_x - a0.x() + dy = self.dragbox.move_y - a0.y() self.zoomTo(self.leftMargin + dx, self.topMargin + dy, - self.leftMargin + self.chartWidth + dx, - self.topMargin + self.chartHeight + dy) + self.leftMargin + self.dim.width + dx, + self.topMargin + self.dim.height + dy) - self.moveStartX = a0.x() - self.moveStartY = a0.y() + self.dragbox.move_x = a0.x() + self.dragbox.move_y = a0.y() return if a0.modifiers() == QtCore.Qt.ControlModifier: # Dragging a box - if not self.draggedBox: - self.draggedBoxStart = (a0.x(), a0.y()) - self.draggedBoxCurrent = (a0.x(), a0.y()) + if not self.dragbox.state: + self.dragbox.pos_start = (a0.x(), a0.y()) + self.dragbox.pos = (a0.x(), a0.y()) self.update() a0.accept() return @@ -438,14 +470,20 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent): m.frequencyInput.setText(str(f)) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - self.chartWidth = a0.size().width()-self.rightMargin-self.leftMargin - self.chartHeight = a0.size().height() - self.bottomMargin - self.topMargin + self.dim.width = a0.size().width()-self.rightMargin-self.leftMargin + self.dim.height = a0.size().height() - self.bottomMargin - self.topMargin self.update() def paintEvent(self, _: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) self.drawChart(qp) self.drawValues(qp) + self._check_frequency_boundaries(qp) + if self.dragbox.state and self.dragbox.pos[0] != -1: + self.drawDragbog(qp) + qp.end() + + def _check_frequency_boundaries(self, qp: QtGui.QPainter): if (len(self.data) > 0 and (self.data[0].freq > self.fstop or self.data[len(self.data)-1].freq < self.fstart) @@ -455,50 +493,124 @@ def paintEvent(self, _: QtGui.QPaintEvent) -> None: self.reference[len(self.reference)-1].freq < self.fstart)): # Data outside frequency range qp.setBackgroundMode(QtCore.Qt.OpaqueMode) - qp.setBackground(self.backgroundColor) - qp.setPen(self.textColor) - qp.drawText(self.leftMargin + self.chartWidth/2 - 70, - self.topMargin + self.chartHeight/2 - 20, + qp.setBackground(Chart.color.background) + qp.setPen(Chart.color.text) + qp.drawText(self.leftMargin + self.dim.width/2 - 70, + self.topMargin + self.dim.height/2 - 20, "Data outside frequency span") - if self.draggedBox and self.draggedBoxCurrent[0] != -1: - dashed_pen = QtGui.QPen(self.foregroundColor, 1, QtCore.Qt.DashLine) - qp.setPen(dashed_pen) - top_left = QtCore.QPoint(self.draggedBoxStart[0], self.draggedBoxStart[1]) - bottom_right = QtCore.QPoint(self.draggedBoxCurrent[0], self.draggedBoxCurrent[1]) - rect = QtCore.QRect(top_left, bottom_right) - qp.drawRect(rect) - qp.end() + + def drawDragbog(self, qp: QtGui.QPainter): + dashed_pen = QtGui.QPen(Chart.color.foreground, 1, QtCore.Qt.DashLine) + qp.setPen(dashed_pen) + top_left = QtCore.QPoint(self.dragbox.pos_start[0], self.dragbox.pos_start[1]) + bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.pos[1]) + rect = QtCore.QRect(top_left, bottom_right) + qp.drawRect(rect) def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) - qp.drawText(3, 15, self.name) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, self.topMargin - 5, - self.leftMargin, self.topMargin + self.chartHeight + 5) - qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + qp.setPen(QtGui.QPen(Chart.color.text)) + headline = self.name + if self.name_unit: + headline += f" ({self.name_unit})" + qp.drawText(3, 15, headline) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin, 20, + self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) self.drawTitle(qp) + def drawValues(self, qp: QtGui.QPainter): + if len(self.data) == 0 and len(self.reference) == 0: + return + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) + highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) + highlighter.setWidth(1) + + self._set_start_stop() + + # Draw bands if required + if self.bands.enabled: + self.drawBands(qp, self.fstart, self.fstop) + + min_value, max_value = self._find_scaling() + self.maxValue = max_value + self.minValue = min_value + span = max_value - min_value + if span == 0: + logger.info("Span is zero for %s-Chart, setting to a small value.", self.name) + span = 1e-15 + self.span = span + + target_ticks = math.floor(self.dim.height / 60) + fmt = Format(max_nr_digits=1) + for i in range(target_ticks): + val = min_value + (i / target_ticks) * span + y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height) + qp.setPen(Chart.color.text) + if val != min_value: + valstr = str(Value(val, fmt=fmt)) + qp.drawText(3, y + 3, valstr) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) + + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, self.topMargin, + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) + qp.drawText(3, self.topMargin + 4, str(Value(max_value, fmt=fmt))) + qp.drawText(3, self.dim.height+self.topMargin, str(Value(min_value, fmt=fmt))) + self.drawFrequencyTicks(qp) + + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) + self.drawMarkers(qp) + + def _find_scaling(self) -> Tuple[int, int]: + if self.fixedValues: + return (self.minDisplayValue / 10e11, + self.maxDisplayValue / 10e11) + min_value = 1 + max_value = -1 + for d in self.data: + val = self.value_function(d) + if val > max_value: + max_value = val + if val < min_value: + min_value = val + for d in self.reference: # Also check min/max for the reference sweep + if d.freq < self.fstart or d.freq > self.fstop: + continue + val = self.value_function(d) + if val > max_value: + max_value = val + if val < min_value: + min_value = val + return (min_value, max_value) + def drawFrequencyTicks(self, qp): fspan = self.fstop - self.fstart - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawText(self.leftMargin - 20, - self.topMargin + self.chartHeight + 15, - Chart.shortenFrequency(self.fstart)) - ticks = math.floor(self.chartWidth / 100) # Number of ticks does not include the origin + self.topMargin + self.dim.height + 15, + format_frequency_chart(self.fstart)) + ticks = math.floor(self.dim.width / 100) # Number of ticks does not include the origin for i in range(ticks): - x = self.leftMargin + round((i + 1) * self.chartWidth / ticks) + x = self.leftMargin + round((i + 1) * self.dim.width / ticks) if self.logarithmicX: fspan = math.log(self.fstop) - math.log(self.fstart) freq = round(math.exp(((i + 1) * fspan / ticks) + math.log(self.fstart))) else: freq = round(fspan / ticks * (i + 1) + self.fstart) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(x, self.topMargin, x, self.topMargin + self.chartHeight + 5) - qp.setPen(self.textColor) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(x, self.topMargin, x, self.topMargin + self.dim.height + 5) + qp.setPen(Chart.color.text) qp.drawText(x - 20, - self.topMargin + self.chartHeight + 15, - Chart.shortenFrequency(freq)) + self.topMargin + self.dim.height + 15, + format_frequency_chart(freq)) def drawBands(self, qp, fstart, fstop): qp.setBrush(self.bands.color) @@ -514,21 +626,21 @@ def drawBands(self, qp, fstart, fstop): continue x_start = max(self.leftMargin + 1, self.getXPosition(Datapoint(start, 0, 0))) - x_stop = min(self.leftMargin + self.chartWidth, + x_stop = min(self.leftMargin + self.dim.width, self.getXPosition(Datapoint(end, 0, 0))) qp.drawRect(x_start, self.topMargin, x_stop - x_start, - self.chartHeight) + self.dim.height) def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], color: QtGui.QColor, y_function=None): if y_function is None: y_function = self.getYPosition pen = QtGui.QPen(color) - pen.setWidth(self.pointSize) + pen.setWidth(self.dim.point) line_pen = QtGui.QPen(color) - line_pen.setWidth(self.lineThickness) + line_pen.setWidth(self.dim.line) qp.setPen(pen) for i, d in enumerate(data): x = self.getXPosition(d) @@ -537,7 +649,7 @@ def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], continue if self.isPlotable(x, y): qp.drawPoint(int(x), int(y)) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(data[i - 1]) prevy = y_function(data[i - 1]) if prevy is None: @@ -569,8 +681,8 @@ def drawMarkers(self, qp, data=None, y_function=None): def isPlotable(self, x, y): return y is not None and x is not None and \ - self.leftMargin <= x <= self.leftMargin + self.chartWidth and \ - self.topMargin <= y <= self.topMargin + self.chartHeight + self.leftMargin <= x <= self.leftMargin + self.dim.width and \ + self.topMargin <= y <= self.topMargin + self.dim.height def getPlotable(self, x, y, distantx, distanty): p1 = np.array([x, y]) @@ -578,10 +690,10 @@ def getPlotable(self, x, y, distantx, distanty): # First check the top line if distanty < self.topMargin: p3 = np.array([self.leftMargin, self.topMargin]) - p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin]) - elif distanty > self.topMargin + self.chartHeight: - p3 = np.array([self.leftMargin, self.topMargin + self.chartHeight]) - p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight]) + p4 = np.array([self.leftMargin + self.dim.width, self.topMargin]) + elif distanty > self.topMargin + self.dim.height: + p3 = np.array([self.leftMargin, self.topMargin + self.dim.height]) + p4 = np.array([self.leftMargin + self.dim.width, self.topMargin + self.dim.height]) else: return x, y da = p2 - p1 @@ -596,15 +708,16 @@ def getPlotable(self, x, y, distantx, distanty): return x, y def copy(self): - new_chart: FrequencyChart = super().copy() + new_chart = super().copy() new_chart.fstart = self.fstart new_chart.fstop = self.fstop new_chart.maxFrequency = self.maxFrequency new_chart.minFrequency = self.minFrequency + new_chart.span = self.span new_chart.minDisplayValue = self.minDisplayValue new_chart.maxDisplayValue = self.maxDisplayValue - new_chart.pointSize = self.pointSize - new_chart.lineThickness = self.lineThickness + new_chart.pointSize = self.dim.point + new_chart.lineThickness = self.dim.line new_chart.setFixedSpan(self.fixedSpan) new_chart.action_automatic.setChecked(not self.fixedSpan) @@ -627,10 +740,10 @@ def copy(self): def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None: m = self.getActiveMarker() if m is not None and a0.modifiers() == QtCore.Qt.NoModifier: - if a0.key() == QtCore.Qt.Key_Down or a0.key() == QtCore.Qt.Key_Left: + if a0.key() in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Left]: m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( a0.type(), QtCore.Qt.Key_Down, a0.modifiers())) - elif a0.key() == QtCore.Qt.Key_Up or a0.key() == QtCore.Qt.Key_Right: + elif a0.key() in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Right]: m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( a0.type(), QtCore.Qt.Key_Up, a0.modifiers())) else: diff --git a/NanoVNASaver/Charts/GroupDelay.py b/NanoVNASaver/Charts/GroupDelay.py index f0ca4aaa..ac44a794 100644 --- a/NanoVNASaver/Charts/GroupDelay.py +++ b/NanoVNASaver/Charts/GroupDelay.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,8 +22,9 @@ import numpy as np -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui +from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.RFTools import Datapoint from .Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -32,9 +33,12 @@ class GroupDelayChart(FrequencyChart): def __init__(self, name="", reflective=True): super().__init__(name) + + self.name_unit = "ns" + self.leftMargin = 40 - self.chartWidth = 250 - self.chartHeight = 250 + self.dim.width = 250 + self.dim.height = 250 self.fstart = 0 self.fstop = 0 self.minDelay = 0 @@ -49,15 +53,6 @@ def __init__(self, name="", reflective=True): self.minDisplayValue = -180 self.maxDisplayValue = 180 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def copy(self): new_chart: GroupDelayChart = super().copy() new_chart.reflective = self.reflective @@ -125,22 +120,13 @@ def calculateGroupDelay(self): self.update() - def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) - qp.drawText(3, 15, self.name + " (ns)") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) - self.drawTitle(qp) - def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) if self.fixedValues: min_delay = self.minDisplayValue @@ -159,13 +145,13 @@ def drawValues(self, qp: QtGui.QPainter): self.maxDelay = max_delay self.span = span - tickcount = math.floor(self.chartHeight / 60) + tickcount = math.floor(self.dim.height / 60) for i in range(tickcount): delay = min_delay + span * i / tickcount - y = self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight) + y = self.topMargin + round((self.maxDelay - delay) / self.span * self.dim.height) if delay != min_delay and delay != max_delay: - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) if delay != 0: digits = max(0, min(2, math.floor(3 - math.log10(abs(delay))))) if digits == 0: @@ -175,47 +161,36 @@ def drawValues(self, qp: QtGui.QPainter): else: delaystr = "0" qp.drawText(3, y + 3, delaystr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, + self.leftMargin + self.dim.width, self.topMargin) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 5, str(max_delay)) - qp.drawText(3, self.chartHeight + self.topMargin, str(min_delay)) + qp.drawText(3, self.dim.height + self.topMargin, str(min_delay)) - if self.fixedSpan: - fstart = self.minFrequency - fstop = self.maxFrequency - else: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) self.drawFrequencyTicks(qp) - color = self.sweepColor + color = Chart.color.sweep pen = QtGui.QPen(color) - pen.setWidth(self.pointSize) + pen.setWidth(self.dim.point) line_pen = QtGui.QPen(color) - line_pen.setWidth(self.lineThickness) + line_pen.setWidth(self.dim.line) qp.setPen(pen) for i in range(len(self.data)): x = self.getXPosition(self.data[i]) y = self.getYPositionFromDelay(self.groupDelay[i]) if self.isPlotable(x, y): qp.drawPoint(int(x), int(y)) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.data[i - 1]) prevy = self.getYPositionFromDelay(self.groupDelay[i - 1]) qp.setPen(line_pen) @@ -229,18 +204,18 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawLine(prevx, prevy, new_x, new_y) qp.setPen(pen) - color = self.referenceColor + color = Chart.color.reference pen = QtGui.QPen(color) - pen.setWidth(self.pointSize) + pen.setWidth(self.dim.point) line_pen = QtGui.QPen(color) - line_pen.setWidth(self.lineThickness) + line_pen.setWidth(self.dim.line) qp.setPen(pen) for i in range(len(self.reference)): x = self.getXPosition(self.reference[i]) y = self.getYPositionFromDelay(self.groupDelayReference[i]) if self.isPlotable(x, y): qp.drawPoint(int(x), int(y)) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.reference[i - 1]) prevy = self.getYPositionFromDelay(self.groupDelayReference[i - 1]) qp.setPen(line_pen) @@ -267,9 +242,9 @@ def getYPosition(self, d: Datapoint) -> int: return self.getYPositionFromDelay(delay) def getYPositionFromDelay(self, delay: float): - return self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight) + return self.topMargin + round((self.maxDelay - delay) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxDelay) + val = -1 * ((absy / self.dim.height * self.span) - self.maxDelay) return [val] diff --git a/NanoVNASaver/Charts/Inductance.py b/NanoVNASaver/Charts/Inductance.py index 7e912514..4b56c450 100644 --- a/NanoVNASaver/Charts/Inductance.py +++ b/NanoVNASaver/Charts/Inductance.py @@ -24,7 +24,8 @@ from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -32,9 +33,9 @@ class InductanceChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.leftMargin = 45 + self.dim.width = 250 + self.dim.height = 250 self.minDisplayValue = 0 self.maxDisplayValue = 100 @@ -42,31 +43,31 @@ def __init__(self, name=""): self.maxValue = 1 self.span = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) + self.setMinimumSize(self.dim.width + self.rightMargin + self.leftMargin, + self.dim.height + self.topMargin + self.bottomMargin) self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name + " (H)") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) if not self.fixedSpan: @@ -118,38 +119,38 @@ def drawValues(self, qp: QtGui.QPainter): span = 1e-15 self.span = span - target_ticks = math.floor(self.chartHeight / 60) - fmt = Format(max_nr_digits=1) + target_ticks = math.floor(self.dim.height / 60) + fmt = Format(max_nr_digits=3) for i in range(target_ticks): val = minValue + (i / target_ticks) * span - y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) - qp.setPen(self.textColor) + y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height) + qp.setPen(Chart.color.text) if val != minValue: valstr = str(Value(val, fmt=fmt)) qp.drawText(3, y + 3, valstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt))) - qp.drawText(3, self.chartHeight+self.topMargin, str(Value(minValue, fmt=fmt))) + qp.drawText(3, self.dim.height+self.topMargin, str(Value(minValue, fmt=fmt))) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: return (self.topMargin + round((self.maxValue - d.inductiveEquivalent()) / - self.span * self.chartHeight)) + self.span * self.dim.height)) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val * 10e11] def copy(self): diff --git a/NanoVNASaver/Charts/LogMag.py b/NanoVNASaver/Charts/LogMag.py index 32fce9d6..51f27e0c 100644 --- a/NanoVNASaver/Charts/LogMag.py +++ b/NanoVNASaver/Charts/LogMag.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,10 +20,11 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -31,9 +32,9 @@ class LogMagChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + + self.name_unit = "dB" + self.minDisplayValue = -80 self.maxDisplayValue = 10 @@ -43,50 +44,15 @@ def __init__(self, name=""): self.isInverted = False - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - - def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) - qp.drawText(3, 15, self.name + " (dB)") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin, self.topMargin - 5, - self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) - self.drawTitle(qp) - def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) - if not self.fixedSpan: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop - else: - fstart = self.fstart = self.minFrequency - fstop = self.fstop = self.maxFrequency + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) if self.fixedValues: maxValue = self.maxDisplayValue @@ -171,49 +137,49 @@ def drawValues(self, qp: QtGui.QPainter): for i in range(tick_count): db = first_tick + i * tick_step - y = self.topMargin + round((maxValue - db)/span*self.chartHeight) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) + y = self.topMargin + round((maxValue - db)/span*self.dim.height) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.dim.width, y) if db > minValue and db != maxValue: - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) if tick_step < 1: dbstr = str(round(db, 1)) else: dbstr = str(db) qp.drawText(3, y + 4, dbstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(maxValue)) - qp.drawText(3, self.chartHeight+self.topMargin, str(minValue)) + qp.drawText(3, self.dim.height+self.topMargin, str(minValue)) self.drawFrequencyTicks(qp) - qp.setPen(self.swrColor) + qp.setPen(Chart.color.swr) for vswr in self.swrMarkers: if vswr <= 1: continue logMag = 20 * math.log10((vswr-1)/(vswr+1)) if self.isInverted: logMag = logMag * -1 - y = self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight) - qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y) + y = self.topMargin + round((self.maxValue - logMag) / self.span * self.dim.height) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr)) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: logMag = self.logMag(d) if math.isinf(logMag): return None - return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - logMag) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val] def logMag(self, p: Datapoint) -> float: diff --git a/NanoVNASaver/Charts/Magnitude.py b/NanoVNASaver/Charts/Magnitude.py index 489fd8da..18be21c6 100644 --- a/NanoVNASaver/Charts/Magnitude.py +++ b/NanoVNASaver/Charts/Magnitude.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,19 +20,18 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) class MagnitudeChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.minDisplayValue = 0 self.maxDisplayValue = 1 @@ -44,40 +43,15 @@ def __init__(self, name=""): self.maxValue = 1 self.span = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) - if not self.fixedSpan: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop - else: - fstart = self.fstart = self.minFrequency - fstop = self.fstop = self.maxFrequency + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) if self.fixedValues: maxValue = self.maxDisplayValue @@ -113,12 +87,12 @@ def drawValues(self, qp: QtGui.QPainter): span = 0.01 self.span = span - target_ticks = math.floor(self.chartHeight / 60) + target_ticks = math.floor(self.dim.height / 60) for i in range(target_ticks): val = minValue + i / target_ticks * span - y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) - qp.setPen(self.textColor) + y = self.topMargin + round((self.maxValue - val) / self.span * self.dim.height) + qp.setPen(Chart.color.text) if val != minValue: digits = max(0, min(2, math.floor(3 - math.log10(abs(val))))) if digits == 0: @@ -126,37 +100,37 @@ def drawValues(self, qp: QtGui.QPainter): else: vswrstr = str(round(val, digits)) qp.drawText(3, y + 3, vswrstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(maxValue)) - qp.drawText(3, self.chartHeight+self.topMargin, str(minValue)) + qp.drawText(3, self.dim.height+self.topMargin, str(minValue)) self.drawFrequencyTicks(qp) - qp.setPen(self.swrColor) + qp.setPen(Chart.color.swr) for vswr in self.swrMarkers: if vswr <= 1: continue mag = (vswr-1)/(vswr+1) - y = self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight) - qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y) + y = self.topMargin + round((self.maxValue - mag) / self.span * self.dim.height) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr)) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: mag = self.magnitude(d) - return self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - mag) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val] @staticmethod diff --git a/NanoVNASaver/Charts/MagnitudeZ.py b/NanoVNASaver/Charts/MagnitudeZ.py index 40390bb5..90a073a6 100644 --- a/NanoVNASaver/Charts/MagnitudeZ.py +++ b/NanoVNASaver/Charts/MagnitudeZ.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,12 +20,13 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value -from .Frequency import FrequencyChart -from .LogMag import LogMagChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart +from NanoVNASaver.Charts.LogMag import LogMagChart logger = logging.getLogger(__name__) @@ -34,9 +35,7 @@ class MagnitudeZChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.minDisplayValue = 0 self.maxDisplayValue = 100 @@ -44,40 +43,15 @@ def __init__(self, name=""): self.maxValue = 1 self.span = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) - if not self.fixedSpan: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop - else: - fstart = self.fstart = self.minFrequency - fstop = self.fstop = self.maxFrequency + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) if self.fixedValues: maxValue = self.maxDisplayValue @@ -124,46 +98,46 @@ def drawValues(self, qp: QtGui.QPainter): self.span = span # We want one horizontal tick per 50 pixels, at most - horizontal_ticks = math.floor(self.chartHeight/50) + horizontal_ticks = math.floor(self.dim.height/50) fmt = Format(max_nr_digits=4) for i in range(horizontal_ticks): - y = self.topMargin + round(i * self.chartHeight / horizontal_ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) + y = self.topMargin + round(i * self.dim.height / horizontal_ticks) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.chartWidth + 5, y) - qp.setPen(QtGui.QPen(self.textColor)) + self.leftMargin + self.dim.width + 5, y) + qp.setPen(QtGui.QPen(Chart.color.text)) val = Value(self.valueAtPosition(y)[0], fmt=fmt) qp.drawText(3, y + 4, str(val)) qp.drawText(3, - self.chartHeight + self.topMargin, + self.dim.height + self.topMargin, str(Value(self.minValue, fmt=fmt))) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: mag = self.magnitude(d) if self.logarithmicY and mag == 0: - return self.topMargin - self.chartHeight + return self.topMargin - self.dim.height if math.isfinite(mag): if self.logarithmicY: span = math.log(self.maxValue) - math.log(self.minValue) - return self.topMargin + round((math.log(self.maxValue) - math.log(mag)) / span * self.chartHeight) - return self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight) - else: - return self.topMargin + return self.topMargin + round( + (math.log(self.maxValue) - math.log(mag)) / span * self.dim.height) + return self.topMargin + round((self.maxValue - mag) / self.span * self.dim.height) + return self.topMargin def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin if self.logarithmicY: span = math.log(self.maxValue) - math.log(self.minValue) - val = math.exp(math.log(self.maxValue) - absy * span / self.chartHeight) + val = math.exp(math.log(self.maxValue) - absy * span / self.dim.height) else: - val = self.maxValue - (absy / self.chartHeight * self.span) + val = self.maxValue - (absy / self.dim.height * self.span) return [val] @staticmethod @@ -171,7 +145,7 @@ def magnitude(p: Datapoint) -> float: return abs(p.impedance()) def logarithmicYAllowed(self) -> bool: - return True; + return True def copy(self): new_chart: LogMagChart = super().copy() diff --git a/NanoVNASaver/Charts/MagnitudeZSeries.py b/NanoVNASaver/Charts/MagnitudeZSeries.py index 1d276caf..816ca839 100644 --- a/NanoVNASaver/Charts/MagnitudeZSeries.py +++ b/NanoVNASaver/Charts/MagnitudeZSeries.py @@ -3,7 +3,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,24 +17,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import math import logging -from typing import List - -from PyQt5 import QtWidgets, QtGui from NanoVNASaver.RFTools import Datapoint -from .MagnitudeZ import MagnitudeZChart +from NanoVNASaver.Charts.MagnitudeZ import MagnitudeZChart logger = logging.getLogger(__name__) class MagnitudeZSeriesChart(MagnitudeZChart): - def __init__(self, name=""): - super().__init__(name) @staticmethod def magnitude(p: Datapoint) -> float: return abs(p.seriesImpedance()) - diff --git a/NanoVNASaver/Charts/MagnitudeZShunt.py b/NanoVNASaver/Charts/MagnitudeZShunt.py index 0bd4d057..3736e3db 100644 --- a/NanoVNASaver/Charts/MagnitudeZShunt.py +++ b/NanoVNASaver/Charts/MagnitudeZShunt.py @@ -3,7 +3,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,24 +17,16 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import math import logging -from typing import List - -from PyQt5 import QtWidgets, QtGui from NanoVNASaver.RFTools import Datapoint from .MagnitudeZ import MagnitudeZChart - logger = logging.getLogger(__name__) class MagnitudeZShuntChart(MagnitudeZChart): - def __init__(self, name=""): - super().__init__(name) @staticmethod def magnitude(p: Datapoint) -> float: return abs(p.shuntImpedance()) - diff --git a/NanoVNASaver/Charts/Permeability.py b/NanoVNASaver/Charts/Permeability.py index 76607f28..d0b9d39c 100644 --- a/NanoVNASaver/Charts/Permeability.py +++ b/NanoVNASaver/Charts/Permeability.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,12 +20,13 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.Marker import Marker from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -34,8 +35,8 @@ def __init__(self, name=""): super().__init__(name) self.leftMargin = 40 self.rightMargin = 30 - self.chartWidth = 230 - self.chartHeight = 250 + self.dim.width = 230 + self.dim.height = 250 self.fstart = 0 self.fstop = 0 self.span = 0.01 @@ -44,20 +45,6 @@ def __init__(self, name=""): self.maxDisplayValue = 100 self.minDisplayValue = -100 - # - # Set up size policy and palette - # - - self.setMinimumSize(self.chartWidth + self.leftMargin + - self.rightMargin, self.chartHeight + 40) - self.setSizePolicy(QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def logarithmicYAllowed(self) -> bool: return True; @@ -66,42 +53,30 @@ def copy(self): return new_chart def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)") qp.drawText(10, 15, "R") - qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X") - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin, self.topMargin - 5, - self.leftMargin, self.topMargin + self.chartHeight + 5) - qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight, - self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight) + self.leftMargin, self.topMargin + self.dim.height + 5) + qp.drawLine(self.leftMargin-5, self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, self.topMargin + self.dim.height) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) - if self.fixedSpan: - fstart = self.minFrequency - fstop = self.maxFrequency - else: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) # Find scaling if self.fixedValues: @@ -124,7 +99,7 @@ def drawValues(self, qp: QtGui.QPainter): if im < min_val: min_val = im for d in self.reference: # Also check min/max for the reference sweep - if d.freq < fstart or d.freq > fstop: + if d.freq < self.fstart or d.freq > self.fstop: continue imp = d.impedance() re, im = imp.real, imp.imag @@ -150,43 +125,43 @@ def drawValues(self, qp: QtGui.QPainter): self.span = span # We want one horizontal tick per 50 pixels, at most - horizontal_ticks = math.floor(self.chartHeight/50) + horizontal_ticks = math.floor(self.dim.height/50) fmt = Format(max_nr_digits=4) for i in range(horizontal_ticks): - y = self.topMargin + round(i * self.chartHeight / horizontal_ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) + y = self.topMargin + round(i * self.dim.height / horizontal_ticks) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.chartWidth + 5, y) - qp.setPen(QtGui.QPen(self.textColor)) + self.leftMargin + self.dim.width + 5, y) + qp.setPen(QtGui.QPen(Chart.color.text)) val = Value(self.valueAtPosition(y)[0], fmt=fmt) qp.drawText(3, y + 4, str(val)) qp.drawText(3, - self.chartHeight + self.topMargin, + self.dim.height + self.topMargin, str(Value(min_val, fmt=fmt))) self.drawFrequencyTicks(qp) primary_pen = pen - secondary_pen = QtGui.QPen(self.secondarySweepColor) + secondary_pen = QtGui.QPen(Chart.color.sweep_secondary) if len(self.data) > 0: - c = QtGui.QColor(self.sweepColor) + c = QtGui.QColor(Chart.color.sweep) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(20, 9, 25, 9) - c = QtGui.QColor(self.secondarySweepColor) + c = QtGui.QColor(Chart.color.sweep_secondary) c.setAlpha(255) pen.setColor(c) qp.setPen(pen) qp.drawLine( - self.leftMargin + self.chartWidth, 9, - self.leftMargin + self.chartWidth + 5, 9) + self.leftMargin + self.dim.width, 9, + self.leftMargin + self.dim.width + 5, 9) - primary_pen.setWidth(self.pointSize) - secondary_pen.setWidth(self.pointSize) - line_pen.setWidth(self.lineThickness) + primary_pen.setWidth(self.dim.point) + secondary_pen.setWidth(self.dim.point) + line_pen.setWidth(self.dim.line) for i in range(len(self.data)): x = self.getXPosition(self.data[i]) @@ -198,13 +173,13 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(secondary_pen) if self.isPlotable(x, y_im): qp.drawPoint(x, y_im) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prev_x = self.getXPosition(self.data[i - 1]) prev_y_re = self.getReYPosition(self.data[i-1]) prev_y_im = self.getImYPosition(self.data[i-1]) # Real part first - line_pen.setColor(self.sweepColor) + line_pen.setColor(Chart.color.sweep) qp.setPen(line_pen) if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): qp.drawLine(x, y_re, prev_x, prev_y_re) @@ -216,7 +191,7 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawLine(prev_x, prev_y_re, new_x, new_y) # Imag part second - line_pen.setColor(self.secondarySweepColor) + line_pen.setColor(Chart.color.sweep_secondary) qp.setPen(line_pen) if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): qp.drawLine(x, y_im, prev_x, prev_y_im) @@ -227,27 +202,27 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) qp.drawLine(prev_x, prev_y_im, new_x, new_y) - primary_pen.setColor(self.referenceColor) - line_pen.setColor(self.referenceColor) - secondary_pen.setColor(self.secondaryReferenceColor) + primary_pen.setColor(Chart.color.reference) + line_pen.setColor(Chart.color.reference) + secondary_pen.setColor(Chart.color.reference_secondary) qp.setPen(primary_pen) if len(self.reference) > 0: - c = QtGui.QColor(self.referenceColor) + c = QtGui.QColor(Chart.color.reference) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(20, 14, 25, 14) - c = QtGui.QColor(self.secondaryReferenceColor) + c = QtGui.QColor(Chart.color.reference_secondary) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.chartWidth, 14, - self.leftMargin + self.chartWidth + 5, 14) + qp.drawLine(self.leftMargin + self.dim.width, 14, + self.leftMargin + self.dim.width + 5, 14) for i in range(len(self.reference)): - if self.reference[i].freq < fstart or self.reference[i].freq > fstop: + if self.reference[i].freq < self.fstart or self.reference[i].freq > self.fstop: continue x = self.getXPosition(self.reference[i]) y_re = self.getReYPosition(self.reference[i]) @@ -258,12 +233,12 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(secondary_pen) if self.isPlotable(x, y_im): qp.drawPoint(x, y_im) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prev_x = self.getXPosition(self.reference[i - 1]) prev_y_re = self.getReYPosition(self.reference[i-1]) prev_y_im = self.getImYPosition(self.reference[i-1]) - line_pen.setColor(self.referenceColor) + line_pen.setColor(Chart.color.reference) qp.setPen(line_pen) # Real part first if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): @@ -275,7 +250,7 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) qp.drawLine(prev_x, prev_y_re, new_x, new_y) - line_pen.setColor(self.secondaryReferenceColor) + line_pen.setColor(Chart.color.reference_secondary) qp.setPen(line_pen) # Imag part second if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): @@ -308,9 +283,9 @@ def getImYPosition(self, d: Datapoint) -> int: return -1 return self.topMargin + round( (math.log(self.max) - math.log(im)) / - span * self.chartHeight) + span * self.dim.height) return self.topMargin + round( - (self.max - im) / self.span * self.chartHeight) + (self.max - im) / self.span * self.dim.height) def getReYPosition(self, d: Datapoint) -> int: re = d.impedance().real @@ -323,9 +298,9 @@ def getReYPosition(self, d: Datapoint) -> int: return -1 return self.topMargin + round( (math.log(self.max) - math.log(re)) / - span * self.chartHeight) + span * self.dim.height) return self.topMargin + round( - (self.max - re) / self.span * self.chartHeight) + (self.max - re) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin @@ -333,12 +308,12 @@ def valueAtPosition(self, y) -> List[float]: min_val = self.max - self.span if self.max > 0 and min_val > 0: span = math.log(self.max) - math.log(min_val) - step = span / self.chartHeight + step = span / self.dim.height val = math.exp(math.log(self.max) - absy * step) else: val = -1 else: - val = -1 * ((absy / self.chartHeight * self.span) - self.max) + val = -1 * ((absy / self.dim.height * self.span) - self.max) return [val] def getNearestMarker(self, x, y) -> Marker: diff --git a/NanoVNASaver/Charts/Phase.py b/NanoVNASaver/Charts/Phase.py index 6fc39dfd..2f740b6e 100644 --- a/NanoVNASaver/Charts/Phase.py +++ b/NanoVNASaver/Charts/Phase.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,7 +25,8 @@ from PyQt5 import QtWidgets, QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -33,11 +34,7 @@ class PhaseChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 40 - self.chartWidth = 250 - self.chartHeight = 250 - self.fstart = 0 - self.fstop = 0 + self.minAngle = 0 self.maxAngle = 0 self.span = 0 @@ -49,15 +46,6 @@ def __init__(self, name=""): self.minDisplayValue = -180 self.maxDisplayValue = 180 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - self.y_menu.addSeparator() self.action_unwrap = QtWidgets.QAction("Unwrap") self.action_unwrap.setCheckable(True) @@ -65,7 +53,7 @@ def __init__(self, name=""): self.y_menu.addAction(self.action_unwrap) def copy(self): - new_chart: PhaseChart = super().copy() + new_chart = super().copy() new_chart.setUnwrap(self.unwrap) new_chart.action_unwrap.setChecked(self.unwrap) return new_chart @@ -77,10 +65,6 @@ def setUnwrap(self, unwrap: bool): def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) if self.unwrap: rawData = [] @@ -114,13 +98,13 @@ def drawValues(self, qp: QtGui.QPainter): self.maxAngle = maxAngle self.span = span - tickcount = math.floor(self.chartHeight / 60) + tickcount = math.floor(self.dim.height / 60) for i in range(tickcount): angle = minAngle + span * i / tickcount - y = self.topMargin + round((self.maxAngle - angle) / self.span * self.chartHeight) + y = self.topMargin + round((self.maxAngle - angle) / self.span * self.dim.height) if angle != minAngle and angle != maxAngle: - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) if angle != 0: digits = max(0, min(2, math.floor(3 - math.log10(abs(angle))))) if digits == 0: @@ -130,37 +114,25 @@ def drawValues(self, qp: QtGui.QPainter): else: anglestr = "0" qp.drawText(3, y + 3, anglestr + "°") - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width, y) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, + self.leftMargin + self.dim.width, self.topMargin) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 5, str(maxAngle) + "°") - qp.drawText(3, self.chartHeight + self.topMargin, str(minAngle) + "°") + qp.drawText(3, self.dim.height + self.topMargin, str(minAngle) + "°") - if self.fixedSpan: - fstart = self.minFrequency - fstop = self.maxFrequency - else: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) self.drawFrequencyTicks(qp) - - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: @@ -173,9 +145,9 @@ def getYPosition(self, d: Datapoint) -> int: angle = math.degrees(d.phase) else: angle = math.degrees(d.phase) - return self.topMargin + round((self.maxAngle - angle) / self.span * self.chartHeight) + return self.topMargin + round((self.maxAngle - angle) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxAngle) + val = -1 * ((absy / self.dim.height * self.span) - self.maxAngle) return [val] diff --git a/NanoVNASaver/Charts/Polar.py b/NanoVNASaver/Charts/Polar.py index 06fa33df..8db1319c 100644 --- a/NanoVNASaver/Charts/Polar.py +++ b/NanoVNASaver/Charts/Polar.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,8 @@ from PyQt5 import QtGui, QtCore from NanoVNASaver.RFTools import Datapoint -from .Square import SquareChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Square import SquareChart logger = logging.getLogger(__name__) @@ -30,16 +31,16 @@ class PolarChart(SquareChart): def __init__(self, name=""): super().__init__(name) - self.chartWidth = 250 - self.chartHeight = 250 + self.dim.width = 250 + self.dim.height = 250 - self.setMinimumSize(self.chartWidth + 40, self.chartHeight + 40) + self.setMinimumSize(self.dim.width + 40, self.dim.height + 40) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + def paintEvent(self, _: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) self.drawChart(qp) self.drawValues(qp) @@ -48,51 +49,49 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: def drawChart(self, qp: QtGui.QPainter): centerX = int(self.width()/2) centerY = int(self.height()/2) - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawEllipse(QtCore.QPoint(centerX, centerY), - int(self.chartWidth / 2), - int(self.chartHeight / 2)) + int(self.dim.width / 2), + int(self.dim.height / 2)) qp.drawEllipse(QtCore.QPoint(centerX, centerY), - int(self.chartWidth / 4), - int(self.chartHeight / 4)) - qp.drawLine(centerX - int(self.chartWidth / 2), centerY, - centerX + int(self.chartWidth / 2), centerY) - qp.drawLine(centerX, centerY - int(self.chartHeight / 2), - centerX, centerY + int(self.chartHeight / 2)) - qp.drawLine(centerX + int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerY + int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerX - int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerY - int(self.chartHeight / 2 * math.sin(math.pi / 4))) - qp.drawLine(centerX + int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerY - int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerX - int(self.chartHeight / 2 * math.sin(math.pi / 4)), - centerY + int(self.chartHeight / 2 * math.sin(math.pi / 4))) + int(self.dim.width / 4), + int(self.dim.height / 4)) + qp.drawLine(centerX - int(self.dim.width / 2), centerY, + centerX + int(self.dim.width / 2), centerY) + qp.drawLine(centerX, centerY - int(self.dim.height / 2), + centerX, centerY + int(self.dim.height / 2)) + qp.drawLine(centerX + int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerY + int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerX - int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerY - int(self.dim.height / 2 * math.sin(math.pi / 4))) + qp.drawLine(centerX + int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerY - int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerX - int(self.dim.height / 2 * math.sin(math.pi / 4)), + centerY + int(self.dim.height / 2 * math.sin(math.pi / 4))) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) qp.setPen(pen) for i in range(len(self.data)): x = self.getXPosition(self.data[i]) - y = self.height()/2 + self.data[i].im * -1 * self.chartHeight/2 + y = self.height()/2 + self.data[i].im * -1 * self.dim.height/2 qp.drawPoint(int(x), int(y)) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.data[i-1]) - prevy = self.height() / 2 + self.data[i-1].im * -1 * self.chartHeight / 2 + prevy = self.height() / 2 + self.data[i-1].im * -1 * self.dim.height / 2 qp.setPen(line_pen) qp.drawLine(x, y, prevx, prevy) qp.setPen(pen) - pen.setColor(self.referenceColor) - line_pen.setColor(self.referenceColor) + pen.setColor(Chart.color.reference) + line_pen.setColor(Chart.color.reference) qp.setPen(pen) if len(self.data) > 0: fstart = self.data[0].freq @@ -105,11 +104,11 @@ def drawValues(self, qp: QtGui.QPainter): if data.freq < fstart or data.freq > fstop: continue x = self.getXPosition(self.reference[i]) - y = self.height()/2 + data.im * -1 * self.chartHeight/2 + y = self.height()/2 + data.im * -1 * self.dim.height/2 qp.drawPoint(int(x), int(y)) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.reference[i-1]) - prevy = self.height() / 2 + self.reference[i-1].im * -1 * self.chartHeight / 2 + prevy = self.height() / 2 + self.reference[i-1].im * -1 * self.dim.height / 2 qp.setPen(line_pen) qp.drawLine(x, y, prevx, prevy) qp.setPen(pen) @@ -117,14 +116,14 @@ def drawValues(self, qp: QtGui.QPainter): for m in self.markers: if m.location != -1 and m.location < len(self.data): x = self.getXPosition(self.data[m.location]) - y = self.height() / 2 + self.data[m.location].im * -1 * self.chartHeight / 2 + y = self.height() / 2 + self.data[m.location].im * -1 * self.dim.height / 2 self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1) def getXPosition(self, d: Datapoint) -> int: - return self.width()/2 + d.re * self.chartWidth/2 + return self.width()/2 + d.re * self.dim.width/2 def getYPosition(self, d: Datapoint) -> int: - return self.height()/2 + d.im * -1 * self.chartHeight/2 + return self.height()/2 + d.im * -1 * self.dim.height/2 def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: if a0.buttons() == QtCore.Qt.RightButton: @@ -132,9 +131,9 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: return x = a0.x() y = a0.y() - absx = x - (self.width() - self.chartWidth) / 2 - absy = y - (self.height() - self.chartHeight) / 2 - if absx < 0 or absx > self.chartWidth or absy < 0 or absy > self.chartHeight \ + absx = x - (self.width() - self.dim.width) / 2 + absy = y - (self.height() - self.dim.height) / 2 + if absx < 0 or absx > self.dim.width or absy < 0 or absy > self.dim.height \ or len(self.data) == len(self.reference) == 0: a0.ignore() return @@ -146,8 +145,8 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: target = self.reference positions = [] for d in target: - thisx = self.width() / 2 + d.re * self.chartWidth / 2 - thisy = self.height() / 2 + d.im * -1 * self.chartHeight / 2 + thisx = self.width() / 2 + d.re * self.dim.width / 2 + thisy = self.height() / 2 + d.im * -1 * self.dim.height / 2 positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2)) minimum_position = positions.index(min(positions)) diff --git a/NanoVNASaver/Charts/QFactor.py b/NanoVNASaver/Charts/QFactor.py index 2a1fd436..ed955464 100644 --- a/NanoVNASaver/Charts/QFactor.py +++ b/NanoVNASaver/Charts/QFactor.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,10 +20,11 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -32,8 +33,8 @@ class QualityFactorChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) self.leftMargin = 35 - self.chartWidth = 250 - self.chartHeight = 250 + self.dim.width = 250 + self.dim.height = 250 self.fstart = 0 self.fstop = 0 self.minQ = 0 @@ -42,15 +43,6 @@ def __init__(self, name=""): self.minDisplayValue = 0 self.maxDisplayValue = 100 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def drawChart(self, qp: QtGui.QPainter): super().drawChart(qp) @@ -75,25 +67,25 @@ def drawChart(self, qp: QtGui.QPainter): if self.span == 0: return # No data to draw the graph from - tickcount = math.floor(self.chartHeight / 60) + tickcount = math.floor(self.dim.height / 60) for i in range(tickcount): q = self.minQ + i * self.span / tickcount - y = self.topMargin + round((self.maxQ - q) / self.span * self.chartHeight) + y = self.topMargin + round((self.maxQ - q) / self.span * self.dim.height) if q < 10: q = round(q, 2) elif q < 20: q = round(q, 1) else: q = round(q) - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, y+3, str(q)) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin + self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin + self.dim.width, y) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) if maxQ < 10: qstr = str(round(maxQ, 2)) elif maxQ < 20: @@ -107,39 +99,29 @@ def drawValues(self, qp: QtGui.QPainter): return if self.span == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) - if self.fixedSpan: - fstart = self.minFrequency - fstop = self.maxFrequency - else: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPosition(self, d: Datapoint) -> int: Q = d.qFactor() - return self.topMargin + round((self.maxQ - Q) / self.span * self.chartHeight) + return self.topMargin + round((self.maxQ - Q) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxQ) + val = -1 * ((absy / self.dim.height * self.span) - self.maxQ) return [val] diff --git a/NanoVNASaver/Charts/RI.py b/NanoVNASaver/Charts/RI.py index 9d5d2d4b..ebe479b2 100644 --- a/NanoVNASaver/Charts/RI.py +++ b/NanoVNASaver/Charts/RI.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,12 +22,13 @@ from PyQt5 import QtWidgets, QtGui +from NanoVNASaver.Formatting import format_frequency_chart from NanoVNASaver.Marker import Marker from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value -from .Chart import Chart -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) @@ -37,8 +38,8 @@ def __init__(self, name=""): super().__init__(name) self.leftMargin = 45 self.rightMargin = 45 - self.chartWidth = 230 - self.chartHeight = 250 + self.dim.width = 230 + self.dim.height = 250 self.fstart = 0 self.fstop = 0 self.span_real = 0.01 @@ -99,22 +100,6 @@ def __init__(self, name=""): self.y_menu.addAction(self.action_set_fixed_maximum_imag) self.y_menu.addAction(self.action_set_fixed_minimum_imag) - # - # Set up size policy and palette - # - - self.setMinimumSize( - self.chartWidth + self.leftMargin + self.rightMargin, - self.chartHeight + 40) - self.setSizePolicy( - QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def copy(self): new_chart: RealImaginaryChart = super().copy() @@ -125,47 +110,37 @@ def copy(self): return new_chart def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(self.leftMargin + 5, 15, f"{self.name} (\N{OHM SIGN})") qp.drawText(10, 15, "R") - qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X") - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, - self.topMargin + self.chartHeight + 5) + self.topMargin + self.dim.height + 5) qp.drawLine(self.leftMargin-5, - self.topMargin + self.chartHeight, - self.leftMargin + self.chartWidth + 5, - self.topMargin + self.chartHeight) + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, + self.topMargin + self.dim.height) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) - if self.fixedSpan: - fstart = self.minFrequency - fstop = self.maxFrequency - else: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) # Find scaling if self.fixedValues: @@ -192,7 +167,7 @@ def drawValues(self, qp: QtGui.QPainter): if im < min_imag: min_imag = im for d in self.reference: # Also check min/max for the reference sweep - if d.freq < fstart or d.freq > fstop: + if d.freq < self.fstart or d.freq > self.fstop: continue imp = self.impedance(d) re, im = imp.real, imp.imag @@ -253,45 +228,45 @@ def drawValues(self, qp: QtGui.QPainter): self.span_imag = span_imag # We want one horizontal tick per 50 pixels, at most - horizontal_ticks = math.floor(self.chartHeight/50) + horizontal_ticks = math.floor(self.dim.height/50) - fmt = Format(max_nr_digits=4) + fmt = Format(max_nr_digits=3) for i in range(horizontal_ticks): - y = self.topMargin + round(i * self.chartHeight / horizontal_ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y) - qp.setPen(QtGui.QPen(self.textColor)) + y = self.topMargin + round(i * self.dim.height / horizontal_ticks) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y) + qp.setPen(QtGui.QPen(Chart.color.text)) re = max_real - i * span_real / horizontal_ticks im = max_imag - i * span_imag / horizontal_ticks qp.drawText(3, y + 4, str(Value(re, fmt=fmt))) - qp.drawText(self.leftMargin + self.chartWidth + 8, y + 4, str(Value(im, fmt=fmt))) + qp.drawText(self.leftMargin + self.dim.width + 8, y + 4, str(Value(im, fmt=fmt))) - qp.drawText(3, self.chartHeight + self.topMargin, str(Value(min_real, fmt=fmt))) - qp.drawText(self.leftMargin + self.chartWidth + 8, - self.chartHeight + self.topMargin, + qp.drawText(3, self.dim.height + self.topMargin, str(Value(min_real, fmt=fmt))) + qp.drawText(self.leftMargin + self.dim.width + 8, + self.dim.height + self.topMargin, str(Value(min_imag, fmt=fmt))) self.drawFrequencyTicks(qp) primary_pen = pen - secondary_pen = QtGui.QPen(self.secondarySweepColor) + secondary_pen = QtGui.QPen(Chart.color.sweep_secondary) if len(self.data) > 0: - c = QtGui.QColor(self.sweepColor) + c = QtGui.QColor(Chart.color.sweep) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(20, 9, 25, 9) - c = QtGui.QColor(self.secondarySweepColor) + c = QtGui.QColor(Chart.color.sweep_secondary) c.setAlpha(255) pen.setColor(c) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.chartWidth, 9, - self.leftMargin + self.chartWidth + 5, 9) + qp.drawLine(self.leftMargin + self.dim.width, 9, + self.leftMargin + self.dim.width + 5, 9) - primary_pen.setWidth(self.pointSize) - secondary_pen.setWidth(self.pointSize) - line_pen.setWidth(self.lineThickness) + primary_pen.setWidth(self.dim.point) + secondary_pen.setWidth(self.dim.point) + line_pen.setWidth(self.dim.line) for i in range(len(self.data)): x = self.getXPosition(self.data[i]) @@ -303,13 +278,13 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(secondary_pen) if self.isPlotable(x, y_im): qp.drawPoint(x, y_im) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prev_x = self.getXPosition(self.data[i - 1]) prev_y_re = self.getReYPosition(self.data[i-1]) prev_y_im = self.getImYPosition(self.data[i-1]) # Real part first - line_pen.setColor(self.sweepColor) + line_pen.setColor(Chart.color.sweep) qp.setPen(line_pen) if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): qp.drawLine(x, y_re, prev_x, prev_y_re) @@ -321,7 +296,7 @@ def drawValues(self, qp: QtGui.QPainter): qp.drawLine(prev_x, prev_y_re, new_x, new_y) # Imag part second - line_pen.setColor(self.secondarySweepColor) + line_pen.setColor(Chart.color.sweep_secondary) qp.setPen(line_pen) if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): qp.drawLine(x, y_im, prev_x, prev_y_im) @@ -332,27 +307,27 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) qp.drawLine(prev_x, prev_y_im, new_x, new_y) - primary_pen.setColor(self.referenceColor) - line_pen.setColor(self.referenceColor) - secondary_pen.setColor(self.secondaryReferenceColor) + primary_pen.setColor(Chart.color.reference) + line_pen.setColor(Chart.color.reference) + secondary_pen.setColor(Chart.color.reference_secondary) qp.setPen(primary_pen) if len(self.reference) > 0: - c = QtGui.QColor(self.referenceColor) + c = QtGui.QColor(Chart.color.reference) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) qp.drawLine(20, 14, 25, 14) - c = QtGui.QColor(self.secondaryReferenceColor) + c = QtGui.QColor(Chart.color.reference_secondary) c.setAlpha(255) pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.chartWidth, 14, - self.leftMargin + self.chartWidth + 5, 14) + qp.drawLine(self.leftMargin + self.dim.width, 14, + self.leftMargin + self.dim.width + 5, 14) for i in range(len(self.reference)): - if self.reference[i].freq < fstart or self.reference[i].freq > fstop: + if self.reference[i].freq < self.fstart or self.reference[i].freq > self.fstop: continue x = self.getXPosition(self.reference[i]) y_re = self.getReYPosition(self.reference[i]) @@ -363,12 +338,12 @@ def drawValues(self, qp: QtGui.QPainter): qp.setPen(secondary_pen) if self.isPlotable(x, y_im): qp.drawPoint(x, y_im) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prev_x = self.getXPosition(self.reference[i - 1]) prev_y_re = self.getReYPosition(self.reference[i-1]) prev_y_im = self.getImYPosition(self.reference[i-1]) - line_pen.setColor(self.referenceColor) + line_pen.setColor(Chart.color.reference) qp.setPen(line_pen) # Real part first if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): @@ -380,7 +355,7 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) qp.drawLine(prev_x, prev_y_re, new_x, new_y) - line_pen.setColor(self.secondaryReferenceColor) + line_pen.setColor(Chart.color.reference_secondary) qp.setPen(line_pen) # Imag part second if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): @@ -404,19 +379,18 @@ def drawValues(self, qp: QtGui.QPainter): def getImYPosition(self, d: Datapoint) -> int: im = self.impedance(d).imag - return self.topMargin + round((self.max_imag - im) / self.span_imag * self.chartHeight) + return self.topMargin + round((self.max_imag - im) / self.span_imag * self.dim.height) def getReYPosition(self, d: Datapoint) -> int: re = self.impedance(d).real if math.isfinite(re): - return self.topMargin + round((self.max_real - re) / self.span_real * self.chartHeight) - else: - return self.topMargin + return self.topMargin + round((self.max_real - re) / self.span_real * self.dim.height) + return self.topMargin def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - valRe = -1 * ((absy / self.chartHeight * self.span_real) - self.max_real) - valIm = -1 * ((absy / self.chartHeight * self.span_imag) - self.max_imag) + valRe = -1 * ((absy / self.dim.height * self.span_real) - self.max_real) + valIm = -1 * ((absy / self.dim.height * self.span_imag) - self.max_imag) return [valRe, valIm] def zoomTo(self, x1, y1, x2, y2): @@ -517,9 +491,9 @@ def setFixedValues(self, fixed_values: bool): def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({Chart.shortenFrequency(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})") self.action_set_fixed_stop.setText( - f"Stop ({Chart.shortenFrequency(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})") self.action_set_fixed_minimum_real.setText( f"Minimum R ({self.minDisplayReal})") self.action_set_fixed_maximum_real.setText( diff --git a/NanoVNASaver/Charts/RISeries.py b/NanoVNASaver/Charts/RISeries.py index b2ca4eff..245b9372 100644 --- a/NanoVNASaver/Charts/RISeries.py +++ b/NanoVNASaver/Charts/RISeries.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,20 +16,15 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import math import logging -from typing import List from NanoVNASaver.RFTools import Datapoint - from .RI import RealImaginaryChart logger = logging.getLogger(__name__) class RealImaginarySeriesChart(RealImaginaryChart): - def __init__(self, name=""): - super().__init__(name) def impedance(self, p: Datapoint) -> complex: return p.seriesImpedance() diff --git a/NanoVNASaver/Charts/RIShunt.py b/NanoVNASaver/Charts/RIShunt.py index 82e602f8..29874253 100644 --- a/NanoVNASaver/Charts/RIShunt.py +++ b/NanoVNASaver/Charts/RIShunt.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,20 +16,15 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import math import logging -from typing import List from NanoVNASaver.RFTools import Datapoint - from .RI import RealImaginaryChart logger = logging.getLogger(__name__) class RealImaginaryShuntChart(RealImaginaryChart): - def __init__(self, name=""): - super().__init__(name) def impedance(self, p: Datapoint) -> complex: return p.shuntImpedance() diff --git a/NanoVNASaver/Charts/SParam.py b/NanoVNASaver/Charts/SParam.py index 5874d29f..6699bc3d 100644 --- a/NanoVNASaver/Charts/SParam.py +++ b/NanoVNASaver/Charts/SParam.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,11 +20,12 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart -from .LogMag import LogMagChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart +from NanoVNASaver.Charts.LogMag import LogMagChart logger = logging.getLogger(__name__) @@ -32,9 +33,7 @@ class SParameterChart(FrequencyChart): def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 + self.minDisplayValue = -1 self.maxDisplayValue = 1 self.fixedValues = True @@ -48,51 +47,26 @@ def __init__(self, name=""): self.isInverted = False - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) - def drawChart(self, qp: QtGui.QPainter): - qp.setPen(QtGui.QPen(self.textColor)) - qp.drawText(int(round(self.chartWidth / 2)) - 20, 15, self.name + "") + qp.setPen(QtGui.QPen(Chart.color.text)) + qp.drawText(int(round(self.dim.width / 2)) - 20, 15, self.name + "") qp.drawText(10, 15, "Real") - qp.drawText(self.leftMargin + self.chartWidth - 15, 15, "Imag") - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.drawText(self.leftMargin + self.dim.width - 15, 15, "Imag") + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin, self.topMargin - 5, - self.leftMargin, self.topMargin+self.chartHeight+5) - qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, - self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight) + self.leftMargin, self.topMargin+self.dim.height+5) + qp.drawLine(self.leftMargin-5, self.topMargin+self.dim.height, + self.leftMargin+self.dim.width, self.topMargin + self.dim.height) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) - if not self.fixedSpan: - if len(self.data) > 0: - fstart = self.data[0].freq - fstop = self.data[len(self.data)-1].freq - else: - fstart = self.reference[0].freq - fstop = self.reference[len(self.reference) - 1].freq - self.fstart = fstart - self.fstop = fstop - else: - fstart = self.fstart = self.minFrequency - fstop = self.fstop = self.maxFrequency + + self._set_start_stop() # Draw bands if required if self.bands.enabled: - self.drawBands(qp, fstart, fstop) + self.drawBands(qp, self.fstart, self.fstop) if self.fixedValues: maxValue = self.maxDisplayValue @@ -130,45 +104,45 @@ def drawValues(self, qp: QtGui.QPainter): span = 0.01 self.span = span - tick_count = math.floor(self.chartHeight / 60) + tick_count = math.floor(self.dim.height / 60) tick_step = self.span / tick_count for i in range(tick_count): val = minValue + i * tick_step - y = self.topMargin + round((maxValue - val)/span*self.chartHeight) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) + y = self.topMargin + round((maxValue - val)/span*self.dim.height) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.dim.width, y) if val > minValue and val != maxValue: - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, y + 4, str(round(val, 2))) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, self.topMargin) - qp.setPen(self.textColor) + self.leftMargin + self.dim.width, self.topMargin) + qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(maxValue)) - qp.drawText(3, self.chartHeight+self.topMargin, str(minValue)) + qp.drawText(3, self.dim.height+self.topMargin, str(minValue)) self.drawFrequencyTicks(qp) - self.drawData(qp, self.data, self.sweepColor, self.getReYPosition) - self.drawData(qp, self.reference, self.referenceColor, self.getReYPosition) - self.drawData(qp, self.data, self.secondarySweepColor, self.getImYPosition) - self.drawData(qp, self.reference, self.secondaryReferenceColor, self.getImYPosition) + self.drawData(qp, self.data, Chart.color.sweep, self.getReYPosition) + self.drawData(qp, self.reference, Chart.color.reference, self.getReYPosition) + self.drawData(qp, self.data, Chart.color.sweep_secondary, self.getImYPosition) + self.drawData(qp, self.reference, Chart.color.reference_secondary, self.getImYPosition) self.drawMarkers(qp, y_function=self.getReYPosition) self.drawMarkers(qp, y_function=self.getImYPosition) def getYPosition(self, d: Datapoint) -> int: - return self.topMargin + round((self.maxValue - d.re) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - d.re) / self.span * self.dim.height) def getReYPosition(self, d: Datapoint) -> int: - return self.topMargin + round((self.maxValue - d.re) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - d.re) / self.span * self.dim.height) def getImYPosition(self, d: Datapoint) -> int: - return self.topMargin + round((self.maxValue - d.im) / self.span * self.chartHeight) + return self.topMargin + round((self.maxValue - d.im) / self.span * self.dim.height) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue) + val = -1 * ((absy / self.dim.height * self.span) - self.maxValue) return [val] def logMag(self, p: Datapoint) -> float: diff --git a/NanoVNASaver/Charts/Smith.py b/NanoVNASaver/Charts/Smith.py index 2668e8fb..23099633 100644 --- a/NanoVNASaver/Charts/Smith.py +++ b/NanoVNASaver/Charts/Smith.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,8 @@ from PyQt5 import QtGui, QtCore from NanoVNASaver.RFTools import Datapoint -from .Square import SquareChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Square import SquareChart logger = logging.getLogger(__name__) @@ -30,16 +31,16 @@ class SmithChart(SquareChart): def __init__(self, name=""): super().__init__(name) - self.chartWidth = 250 - self.chartHeight = 250 + self.dim.width = 250 + self.dim.height = 250 - self.setMinimumSize(self.chartWidth + 40, self.chartHeight + 40) + self.setMinimumSize(self.dim.width + 40, self.dim.height + 40) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + def paintEvent(self, _: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) # qp.begin(self) # Apparently not needed? self.drawSmithChart(qp) @@ -49,67 +50,67 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: def drawSmithChart(self, qp: QtGui.QPainter): centerX = int(self.width()/2) centerY = int(self.height()/2) - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawEllipse(QtCore.QPoint(centerX, centerY), - int(self.chartWidth / 2), - int(self.chartHeight / 2)) + int(self.dim.width / 2), + int(self.dim.height / 2)) qp.drawLine( - centerX - int(self.chartWidth / 2), + centerX - int(self.dim.width / 2), centerY, - centerX + int(self.chartWidth / 2), + centerX + int(self.dim.width / 2), centerY) - qp.drawEllipse(QtCore.QPoint(centerX + int(self.chartWidth/4), centerY), - int(self.chartWidth/4), int(self.chartHeight/4)) # Re(Z) = 1 - qp.drawEllipse(QtCore.QPoint(centerX + int(2/3*self.chartWidth/2), centerY), - int(self.chartWidth/6), int(self.chartHeight/6)) # Re(Z) = 2 - qp.drawEllipse(QtCore.QPoint(centerX + int(3 / 4 * self.chartWidth / 2), centerY), - int(self.chartWidth / 8), int(self.chartHeight / 8)) # Re(Z) = 3 - qp.drawEllipse(QtCore.QPoint(centerX + int(5 / 6 * self.chartWidth / 2), centerY), - int(self.chartWidth / 12), int(self.chartHeight / 12)) # Re(Z) = 5 - - qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 3 * self.chartWidth / 2), centerY), - int(self.chartWidth / 3), int(self.chartHeight / 3)) # Re(Z) = 0.5 - qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 6 * self.chartWidth / 2), centerY), - int(self.chartWidth / 2.4), int(self.chartHeight / 2.4)) # Re(Z) = 0.2 - - qp.drawArc(centerX + int(3/8*self.chartWidth), centerY, int(self.chartWidth/4), - int(self.chartWidth/4), 90*16, 152*16) # Im(Z) = -5 - qp.drawArc(centerX + int(3/8*self.chartWidth), centerY, int(self.chartWidth/4), - -int(self.chartWidth/4), -90 * 16, -152 * 16) # Im(Z) = 5 - qp.drawArc(centerX + int(self.chartWidth/4), centerY, int(self.chartWidth/2), - int(self.chartHeight/2), 90*16, 127*16) # Im(Z) = -2 - qp.drawArc(centerX + int(self.chartWidth/4), centerY, int(self.chartWidth/2), - -int(self.chartHeight/2), -90*16, -127*16) # Im(Z) = 2 + qp.drawEllipse(QtCore.QPoint(centerX + int(self.dim.width/4), centerY), + int(self.dim.width/4), int(self.dim.height/4)) # Re(Z) = 1 + qp.drawEllipse(QtCore.QPoint(centerX + int(2/3*self.dim.width/2), centerY), + int(self.dim.width/6), int(self.dim.height/6)) # Re(Z) = 2 + qp.drawEllipse(QtCore.QPoint(centerX + int(3 / 4 * self.dim.width / 2), centerY), + int(self.dim.width / 8), int(self.dim.height / 8)) # Re(Z) = 3 + qp.drawEllipse(QtCore.QPoint(centerX + int(5 / 6 * self.dim.width / 2), centerY), + int(self.dim.width / 12), int(self.dim.height / 12)) # Re(Z) = 5 + + qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 3 * self.dim.width / 2), centerY), + int(self.dim.width / 3), int(self.dim.height / 3)) # Re(Z) = 0.5 + qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 6 * self.dim.width / 2), centerY), + int(self.dim.width / 2.4), int(self.dim.height / 2.4)) # Re(Z) = 0.2 + + qp.drawArc(centerX + int(3/8*self.dim.width), centerY, int(self.dim.width/4), + int(self.dim.width/4), 90*16, 152*16) # Im(Z) = -5 + qp.drawArc(centerX + int(3/8*self.dim.width), centerY, int(self.dim.width/4), + -int(self.dim.width/4), -90 * 16, -152 * 16) # Im(Z) = 5 + qp.drawArc(centerX + int(self.dim.width/4), centerY, int(self.dim.width/2), + int(self.dim.height/2), 90*16, 127*16) # Im(Z) = -2 + qp.drawArc(centerX + int(self.dim.width/4), centerY, int(self.dim.width/2), + -int(self.dim.height/2), -90*16, -127*16) # Im(Z) = 2 qp.drawArc(centerX, centerY, - self.chartWidth, self.chartHeight, + self.dim.width, self.dim.height, 90*16, 90*16) # Im(Z) = -1 qp.drawArc(centerX, centerY, - self.chartWidth, -self.chartHeight, + self.dim.width, -self.dim.height, -90 * 16, -90 * 16) # Im(Z) = 1 - qp.drawArc(centerX - int(self.chartWidth / 2), centerY, - self.chartWidth * 2, self.chartHeight * 2, + qp.drawArc(centerX - int(self.dim.width / 2), centerY, + self.dim.width * 2, self.dim.height * 2, int(99.5*16), int(43.5*16)) # Im(Z) = -0.5 - qp.drawArc(centerX - int(self.chartWidth / 2), centerY, - self.chartWidth * 2, -self.chartHeight * 2, + qp.drawArc(centerX - int(self.dim.width / 2), centerY, + self.dim.width * 2, -self.dim.height * 2, int(-99.5 * 16), int(-43.5 * 16)) # Im(Z) = 0.5 - qp.drawArc(centerX - self.chartWidth * 2, centerY, - self.chartWidth * 5, self.chartHeight * 5, + qp.drawArc(centerX - self.dim.width * 2, centerY, + self.dim.width * 5, self.dim.height * 5, int(93.85 * 16), int(18.85 * 16)) # Im(Z) = -0.2 - qp.drawArc(centerX - self.chartWidth*2, centerY, - self.chartWidth*5, -self.chartHeight*5, + qp.drawArc(centerX - self.dim.width*2, centerY, + self.dim.width*5, -self.dim.height*5, int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.2 self.drawTitle(qp) - qp.setPen(self.swrColor) + qp.setPen(Chart.color.swr) for swr in self.swrMarkers: if swr <= 1: continue gamma = (swr - 1)/(swr + 1) - r = round(gamma * self.chartWidth/2) + r = round(gamma * self.dim.width/2) qp.drawEllipse(QtCore.QPoint(centerX, centerY), r, r) qp.drawText( QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20), @@ -118,25 +119,25 @@ def drawSmithChart(self, qp: QtGui.QPainter): def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) + line_pen = QtGui.QPen(Chart.color.sweep) + line_pen.setWidth(self.dim.line) highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) highlighter.setWidth(1) qp.setPen(pen) for i in range(len(self.data)): x = self.getXPosition(self.data[i]) - y = int(self.height()/2 + self.data[i].im * -1 * self.chartHeight/2) + y = int(self.height()/2 + self.data[i].im * -1 * self.dim.height/2) qp.drawPoint(x, y) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.data[i-1]) - prevy = int(self.height() / 2 + self.data[i-1].im * -1 * self.chartHeight / 2) + prevy = int(self.height() / 2 + self.data[i-1].im * -1 * self.dim.height / 2) qp.setPen(line_pen) qp.drawLine(x, y, prevx, prevy) qp.setPen(pen) - pen.setColor(self.referenceColor) - line_pen.setColor(self.referenceColor) + pen.setColor(Chart.color.reference) + line_pen.setColor(Chart.color.reference) qp.setPen(pen) if len(self.data) > 0: fstart = self.data[0].freq @@ -149,11 +150,11 @@ def drawValues(self, qp: QtGui.QPainter): if data.freq < fstart or data.freq > fstop: continue x = self.getXPosition(data) - y = int(self.height()/2 + data.im * -1 * self.chartHeight/2) + y = int(self.height()/2 + data.im * -1 * self.dim.height/2) qp.drawPoint(x, y) - if self.drawLines and i > 0: + if self.flag.draw_lines and i > 0: prevx = self.getXPosition(self.reference[i-1]) - prevy = int(self.height() / 2 + self.reference[i-1].im * -1 * self.chartHeight / 2) + prevy = int(self.height() / 2 + self.reference[i-1].im * -1 * self.dim.height / 2) qp.setPen(line_pen) qp.drawLine(x, y, prevx, prevy) qp.setPen(pen) @@ -161,14 +162,14 @@ def drawValues(self, qp: QtGui.QPainter): for m in self.markers: if m.location != -1: x = self.getXPosition(self.data[m.location]) - y = self.height() / 2 + self.data[m.location].im * -1 * self.chartHeight / 2 + y = self.height() / 2 + self.data[m.location].im * -1 * self.dim.height / 2 self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1) def getXPosition(self, d: Datapoint) -> int: - return int(self.width()/2 + d.re * self.chartWidth/2) + return int(self.width()/2 + d.re * self.dim.width/2) def getYPosition(self, d: Datapoint) -> int: - return int(self.height()/2 + d.im * -1 * self.chartHeight/2) + return int(self.height()/2 + d.im * -1 * self.dim.height/2) def heightForWidth(self, a0: int) -> int: return a0 @@ -179,9 +180,9 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: return x = a0.x() y = a0.y() - absx = x - (self.width() - self.chartWidth) / 2 - absy = y - (self.height() - self.chartHeight) / 2 - if absx < 0 or absx > self.chartWidth or absy < 0 or absy > self.chartHeight \ + absx = x - (self.width() - self.dim.width) / 2 + absy = y - (self.height() - self.dim.height) / 2 + if absx < 0 or absx > self.dim.width or absy < 0 or absy > self.dim.height \ or len(self.data) == len(self.reference) == 0: a0.ignore() return @@ -193,8 +194,8 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: target = self.reference positions = [] for d in target: - thisx = self.width() / 2 + d.re * self.chartWidth / 2 - thisy = self.height() / 2 + d.im * -1 * self.chartHeight / 2 + thisx = self.width() / 2 + d.re * self.dim.width / 2 + thisy = self.height() / 2 + d.im * -1 * self.dim.height / 2 positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2)) minimum_position = positions.index(min(positions)) diff --git a/NanoVNASaver/Charts/Square.py b/NanoVNASaver/Charts/Square.py index 9cd8fd61..83b96d18 100644 --- a/NanoVNASaver/Charts/Square.py +++ b/NanoVNASaver/Charts/Square.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,15 +32,15 @@ def __init__(self, name): QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.MinimumExpanding) self.setSizePolicy(sizepolicy) - self.chartWidth = self.width()-40 - self.chartHeight = self.height()-40 + self.dim.width = self.width()-40 + self.dim.height = self.height()-40 def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - if not self.isPopout: + if not self.flag.is_popout: self.setFixedWidth(a0.size().height()) - self.chartWidth = a0.size().height()-40 - self.chartHeight = a0.size().height()-40 + self.dim.width = a0.size().height()-40 + self.dim.height = a0.size().height()-40 else: min_dimension = min(a0.size().height(), a0.size().width()) - self.chartWidth = self.chartHeight = min_dimension - 40 + self.dim.width = self.dim.height = min_dimension - 40 self.update() diff --git a/NanoVNASaver/Charts/TDR.py b/NanoVNASaver/Charts/TDR.py index 2d8b829c..bf2a90cd 100644 --- a/NanoVNASaver/Charts/TDR.py +++ b/NanoVNASaver/Charts/TDR.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ import numpy as np from PyQt5 import QtWidgets, QtGui, QtCore -from .Chart import Chart +from NanoVNASaver.Charts.Chart import Chart logger = logging.getLogger(__name__) @@ -51,7 +51,7 @@ def __init__(self, name): QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) + pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) self.setAutoFillBackground(True) @@ -127,8 +127,8 @@ def __init__(self, name): lambda: self.popoutRequested.emit(self)) self.menu.addAction(self.action_popout) - self.chartWidth = self.width() - self.leftMargin - self.rightMargin - self.chartHeight = self.height() - self.bottomMargin - self.topMargin + self.dim.width = self.width() - self.leftMargin - self.rightMargin + self.dim.height = self.height() - self.bottomMargin - self.topMargin def contextMenuEvent(self, event): self.action_set_fixed_start.setText( @@ -231,20 +231,20 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: if a0.buttons() == QtCore.Qt.MiddleButton: # Drag the display a0.accept() - if self.moveStartX != -1 and self.moveStartY != -1: - dx = self.moveStartX - a0.x() - dy = self.moveStartY - a0.y() + if self.dragbox.move_x != -1 and self.dragbox.move_y != -1: + dx = self.dragbox.move_x - a0.x() + dy = self.dragbox.move_y - a0.y() self.zoomTo(self.leftMargin + dx, self.topMargin + dy, - self.leftMargin + self.chartWidth + dx, - self.topMargin + self.chartHeight + dy) - self.moveStartX = a0.x() - self.moveStartY = a0.y() + self.leftMargin + self.dim.width + dx, + self.topMargin + self.dim.height + dy) + self.dragbox.move_x = a0.x() + self.dragbox.move_y = a0.y() return if a0.modifiers() == QtCore.Qt.ControlModifier: # Dragging a box - if not self.draggedBox: - self.draggedBoxStart = (a0.x(), a0.y()) - self.draggedBoxCurrent = (a0.x(), a0.y()) + if not self.dragbox.state: + self.dragbox.pos_start = (a0.x(), a0.y()) + self.dragbox.pos = (a0.x(), a0.y()) self.update() a0.accept() return @@ -269,15 +269,15 @@ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: self.update() return - def paintEvent(self, a0: QtGui.QPaintEvent) -> None: + def paintEvent(self, _: QtGui.QPaintEvent) -> None: qp = QtGui.QPainter(self) - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, 15, self.name) width = self.width() - self.leftMargin - self.rightMargin height = self.height() - self.bottomMargin - self.topMargin - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(self.leftMargin - 5, self.height() - self.bottomMargin, self.width() - self.rightMargin, @@ -323,9 +323,9 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: for i in range(ticks): x = self.leftMargin + round((i + 1) * width / ticks) - qp.setPen(QtGui.QPen(self.foregroundColor)) + qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(x, self.topMargin, x, self.topMargin + height) - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText( x - 15, self.topMargin + height + 15, @@ -335,7 +335,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: int((x - self.leftMargin) * x_step) - 1] / 2, 1)) + "m") - qp.setPen(QtGui.QPen(self.textColor)) + qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText( self.leftMargin - 10, self.topMargin + height + 15, @@ -347,16 +347,16 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: for i in range(y_ticks): y = self.bottomMargin + int(i * y_tick_step) - qp.setPen(self.foregroundColor) + qp.setPen(Chart.color.foreground) qp.drawLine(self.leftMargin, y, self.leftMargin + width, y) y_val = max_impedance - y_impedance_step * i * y_tick_step - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawText(3, y + 3, str(round(y_val, 1))) qp.drawText(3, self.topMargin + height + 3, str(round(min_impedance, 1))) - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) + pen = QtGui.QPen(Chart.color.sweep) + pen.setWidth(self.dim.point) qp.setPen(pen) for i in range(min_index, max_index): if i < min_index or i > max_index: @@ -365,7 +365,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: x = self.leftMargin + int((i - min_index) / x_step) y = (self.topMargin + height) - int(self.tdrWindow.td[i] / y_step) if self.isPlotable(x, y): - pen.setColor(self.sweepColor) + pen.setColor(Chart.color.sweep) qp.setPen(pen) qp.drawPoint(x, y) @@ -373,7 +373,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: y = (self.topMargin + height) -\ int((self.tdrWindow.step_response_Z[i]-min_impedance) / y_impedance_step) if self.isPlotable(x, y): - pen.setColor(self.secondarySweepColor) + pen.setColor(Chart.color.sweep_secondary) qp.setPen(pen) qp.drawPoint(x, y) @@ -383,7 +383,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: (self.topMargin + height) - int(self.tdrWindow.td[id_max] / y_step)) qp.setPen(self.markers[0].color) qp.drawEllipse(max_point, 2, 2) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawText(max_point.x() - 10, max_point.y() - 5, str(round(self.tdrWindow.distance_axis[id_max] / 2, 2)) + "m") @@ -394,7 +394,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: int((self.markerLocation - min_index) / x_step), (self.topMargin + height) - int(self.tdrWindow.td[self.markerLocation] / y_step)) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) qp.drawEllipse(marker_point, 2, 2) qp.drawText( marker_point.x() - 10, @@ -402,11 +402,11 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: str(round(self.tdrWindow.distance_axis[self.markerLocation] / 2, 2)) + "m") - if self.draggedBox and self.draggedBoxCurrent[0] != -1: - dashed_pen = QtGui.QPen(self.foregroundColor, 1, QtCore.Qt.DashLine) + if self.dragbox.state and self.dragbox.pos[0] != -1: + dashed_pen = QtGui.QPen(Chart.color.foreground, 1, QtCore.Qt.DashLine) qp.setPen(dashed_pen) - top_left = QtCore.QPoint(self.draggedBoxStart[0], self.draggedBoxStart[1]) - bottom_right = QtCore.QPoint(self.draggedBoxCurrent[0], self.draggedBoxCurrent[1]) + top_left = QtCore.QPoint(self.dragbox.pos_start[0], self.dragbox.stateStart[1]) + bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.stateCurrent[1]) rect = QtCore.QRect(top_left, bottom_right) qp.drawRect(rect) @@ -474,8 +474,8 @@ def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: if len(self.tdrWindow.td) == 0: a0.ignore() return - chart_height = self.chartHeight - chart_width = self.chartWidth + chart_height = self.dim.height + chart_width = self.dim.width do_zoom_x = do_zoom_y = True if a0.modifiers() == QtCore.Qt.ShiftModifier: do_zoom_x = False @@ -534,5 +534,5 @@ def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: super().resizeEvent(a0) - self.chartWidth = self.width() - self.leftMargin - self.rightMargin - self.chartHeight = self.height() - self.bottomMargin - self.topMargin + self.dim.width = self.width() - self.leftMargin - self.rightMargin + self.dim.height = self.height() - self.bottomMargin - self.topMargin diff --git a/NanoVNASaver/Charts/VSWR.py b/NanoVNASaver/Charts/VSWR.py index 7ce8dd47..de394630 100644 --- a/NanoVNASaver/Charts/VSWR.py +++ b/NanoVNASaver/Charts/VSWR.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,53 +20,32 @@ import logging from typing import List -from PyQt5 import QtWidgets, QtGui +from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from .Frequency import FrequencyChart +from NanoVNASaver.Charts.Chart import Chart +from NanoVNASaver.Charts.Frequency import FrequencyChart logger = logging.getLogger(__name__) class VSWRChart(FrequencyChart): - maxVSWR = 3 - span = 2 def __init__(self, name=""): super().__init__(name) - self.leftMargin = 30 - self.chartWidth = 250 - self.chartHeight = 250 - self.fstart = 0 - self.fstop = 0 + self.maxDisplayValue = 25 self.minDisplayValue = 1 - self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, - self.chartHeight + self.topMargin + self.bottomMargin) - self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) - pal = QtGui.QPalette() - pal.setColor(QtGui.QPalette.Background, self.backgroundColor) - self.setPalette(pal) - self.setAutoFillBackground(True) + self.maxVSWR = 3 + self.span = 2 def logarithmicYAllowed(self) -> bool: return True - def copy(self): - new_chart: VSWRChart = super().copy() - return new_chart - def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: return - pen = QtGui.QPen(self.sweepColor) - pen.setWidth(self.pointSize) - line_pen = QtGui.QPen(self.sweepColor) - line_pen.setWidth(self.lineThickness) - highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255)) - highlighter.setWidth(1) if self.fixedSpan: fstart = self.minFrequency fstop = self.maxFrequency @@ -101,13 +80,13 @@ def drawValues(self, qp: QtGui.QPainter): span = 0.01 self.span = span - target_ticks = math.floor(self.chartHeight / 60) + target_ticks = math.floor(self.dim.height / 60) if self.logarithmicY: for i in range(target_ticks): - y = int(self.topMargin + (i / target_ticks) * self.chartHeight) + y = int(self.topMargin + (i / target_ticks) * self.dim.height) vswr = self.valueAtPosition(y)[0] - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) if vswr != 0: digits = max(0, min(2, math.floor(3 - math.log10(abs(vswr))))) if digits == 0: @@ -115,22 +94,22 @@ def drawValues(self, qp: QtGui.QPainter): else: vswrstr = str(round(vswr, digits)) qp.drawText(3, y+3, vswrstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) - qp.drawLine(self.leftMargin - 5, self.topMargin + self.chartHeight, - self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight) - qp.setPen(self.textColor) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.dim.width, y) + qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height, + self.leftMargin + self.dim.width, self.topMargin + self.dim.height) + qp.setPen(Chart.color.text) digits = max(0, min(2, math.floor(3 - math.log10(abs(minVSWR))))) if digits == 0: vswrstr = str(round(minVSWR)) else: vswrstr = str(round(minVSWR, digits)) - qp.drawText(3, self.topMargin + self.chartHeight, vswrstr) + qp.drawText(3, self.topMargin + self.dim.height, vswrstr) else: for i in range(target_ticks): vswr = minVSWR + i * self.span/target_ticks y = self.getYPositionFromValue(vswr) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) if vswr != 0: digits = max(0, min(2, math.floor(3 - math.log10(abs(vswr))))) if digits == 0: @@ -138,13 +117,13 @@ def drawValues(self, qp: QtGui.QPainter): else: vswrstr = str(round(vswr, digits)) qp.drawText(3, y+3, vswrstr) - qp.setPen(QtGui.QPen(self.foregroundColor)) - qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y) + qp.setPen(QtGui.QPen(Chart.color.foreground)) + qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.dim.width, y) qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.chartWidth, + self.leftMargin + self.dim.width, self.topMargin) - qp.setPen(self.textColor) + qp.setPen(Chart.color.text) digits = max(0, min(2, math.floor(3 - math.log10(abs(maxVSWR))))) if digits == 0: vswrstr = str(round(maxVSWR)) @@ -152,16 +131,15 @@ def drawValues(self, qp: QtGui.QPainter): vswrstr = str(round(maxVSWR, digits)) qp.drawText(3, 35, vswrstr) - self.drawFrequencyTicks(qp) - - qp.setPen(self.swrColor) + qp.setPen(Chart.color.swr) for vswr in self.swrMarkers: y = self.getYPositionFromValue(vswr) - qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, str(vswr)) - self.drawData(qp, self.data, self.sweepColor) - self.drawData(qp, self.reference, self.referenceColor) + self.drawFrequencyTicks(qp) + self.drawData(qp, self.data, Chart.color.sweep) + self.drawData(qp, self.reference, Chart.color.reference) self.drawMarkers(qp) def getYPositionFromValue(self, vswr) -> int: @@ -173,8 +151,8 @@ def getYPositionFromValue(self, vswr) -> int: return -1 return ( self.topMargin + - round((math.log(self.maxVSWR) - math.log(vswr)) / span * self.chartHeight)) - return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.chartHeight) + round((math.log(self.maxVSWR) - math.log(vswr)) / span * self.dim.height)) + return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.dim.height) def getYPosition(self, d: Datapoint) -> int: return self.getYPositionFromValue(d.vswr) @@ -185,12 +163,12 @@ def valueAtPosition(self, y) -> List[float]: min_val = self.maxVSWR - self.span if self.maxVSWR > 0 and min_val > 0: span = math.log(self.maxVSWR) - math.log(min_val) - step = span / self.chartHeight + step = span / self.dim.height val = math.exp(math.log(self.maxVSWR) - absy * step) else: val = -1 else: - val = -1 * ((absy / self.chartHeight * self.span) - self.maxVSWR) + val = -1 * ((absy / self.dim.height * self.span) - self.maxVSWR) return [val] def resetDisplayLimits(self): diff --git a/NanoVNASaver/Charts/__init__.py b/NanoVNASaver/Charts/__init__.py index fb0022be..d8ebd651 100644 --- a/NanoVNASaver/Charts/__init__.py +++ b/NanoVNASaver/Charts/__init__.py @@ -1,4 +1,4 @@ -from .Chart import Chart +from .Chart import Chart from .Frequency import FrequencyChart from .Polar import PolarChart from .Square import SquareChart diff --git a/NanoVNASaver/Controls/Control.py b/NanoVNASaver/Controls/Control.py new file mode 100644 index 00000000..ad882950 --- /dev/null +++ b/NanoVNASaver/Controls/Control.py @@ -0,0 +1,33 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import logging + +from PyQt5 import QtWidgets, QtCore + +logger = logging.getLogger(__name__) + +class Control(QtWidgets.QGroupBox): + updated = QtCore.pyqtSignal(object) + + def __init__(self, app: QtWidgets.QWidget, title: str = ""): + super().__init__() + self.app = app + self.setMaximumWidth(240) + self.setTitle(title) + self.layout = QtWidgets.QFormLayout(self) diff --git a/NanoVNASaver/Controls/MarkerControl.py b/NanoVNASaver/Controls/MarkerControl.py index f255a91e..72b8cc67 100644 --- a/NanoVNASaver/Controls/MarkerControl.py +++ b/NanoVNASaver/Controls/MarkerControl.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,26 +19,23 @@ import logging from PyQt5 import QtWidgets, QtCore -from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QCheckBox from NanoVNASaver.Marker import Marker +from NanoVNASaver.Controls.Control import Control + logger = logging.getLogger(__name__) -class MarkerControl(QtWidgets.QGroupBox): - updated = pyqtSignal(object) +class MarkerControl(Control): - def __init__(self, app: QtWidgets.QWidget, title: str = "Markers"): - super().__init__() - self.app = app - self.setMaximumWidth(250) - self.setTitle(title) - self.layout = QtWidgets.QFormLayout(self) + def __init__(self, app: QtWidgets.QWidget): + super().__init__(app, "Markers") marker_count = max(self.app.settings.value("MarkerCount", 3, int), 1) for i in range(marker_count): marker = Marker("", self.app.settings) + #marker.setFixedHeight(20) marker.updated.connect(self.app.markerUpdated) label, layout = marker.getRow() self.layout.addRow(label, layout) @@ -51,6 +48,7 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Markers"): self.layout.addRow(self.check_delta) self.showMarkerButton = QtWidgets.QPushButton() + self.showMarkerButton.setFixedHeight(20) if self.app.marker_frame.isHidden(): self.showMarkerButton.setText("Show data") else: @@ -68,16 +66,13 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Markers"): self.layout.addRow(hbox) def toggle_frame(self): - if self.app.marker_frame.isHidden(): - self.app.marker_frame.setHidden(False) - self.app.settings.setValue("MarkersVisible", True) - self.showMarkerButton.setText("Hide data") - self.showMarkerButton.repaint() - else: - self.app.marker_frame.setHidden(True) - self.app.settings.setValue("MarkersVisible", False) - self.showMarkerButton.setText("Show data") + def settings(hidden: bool): + self.app.marker_frame.setHidden(not hidden) + self.app.settings.setValue("MarkersVisible", hidden) + self.showMarkerButton.setText( + "Hide data" if hidden else "Show data") self.showMarkerButton.repaint() + settings(self.app.marker_frame.isHidden()) def toggle_delta(self): self.app.delta_marker_layout.setVisible(self.check_delta.isChecked()) diff --git a/NanoVNASaver/Controls/SerialControl.py b/NanoVNASaver/Controls/SerialControl.py new file mode 100644 index 00000000..cf0096ff --- /dev/null +++ b/NanoVNASaver/Controls/SerialControl.py @@ -0,0 +1,132 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import logging +from time import sleep + +from PyQt5 import QtWidgets + +from NanoVNASaver.Hardware.Hardware import Interface, get_interfaces, get_VNA +from NanoVNASaver.Controls.Control import Control + +logger = logging.getLogger(__name__) + +class SerialControl(Control): + + def __init__(self, app: QtWidgets.QWidget): + super().__init__(app, "Serial port control") + + self.interface = Interface("serial", "none") + self.inp_port = QtWidgets.QComboBox() + self.inp_port.setMinimumHeight(20) + self.rescanSerialPort() + self.inp_port.setEditable(True) + self.btn_rescan = QtWidgets.QPushButton("Rescan") + self.btn_rescan.setMinimumHeight(20) + self.btn_rescan.setFixedWidth(60) + self.btn_rescan.clicked.connect(self.rescanSerialPort) + intput_layout = QtWidgets.QHBoxLayout() + intput_layout.addWidget(QtWidgets.QLabel("Port"), stretch=0) + intput_layout.addWidget(self.inp_port, stretch=1) + intput_layout.addWidget(self.btn_rescan, stretch=0) + self.layout.addRow(intput_layout) + + button_layout = QtWidgets.QHBoxLayout() + + self.btn_toggle = QtWidgets.QPushButton("Connect to device") + self.btn_toggle.setMinimumHeight(20) + self.btn_toggle.clicked.connect(self.serialButtonClick) + button_layout.addWidget(self.btn_toggle, stretch=1) + + self.btn_settings = QtWidgets.QPushButton("Manage") + self.btn_settings.setMinimumHeight(20) + self.btn_settings.setFixedWidth(60) + self.btn_settings.clicked.connect( + lambda: self.app.display_window("device_settings")) + + button_layout.addWidget(self.btn_settings, stretch=0) + self.layout.addRow(button_layout) + + def rescanSerialPort(self): + self.inp_port.clear() + for iface in get_interfaces(): + self.inp_port.insertItem(1, f"{iface}", iface) + self.inp_port.repaint() + + def serialButtonClick(self): + if not self.app.vna.connected(): + self.connect_device() + else: + self.disconnect_device() + + def connect_device(self): + with self.interface.lock: + self.interface = self.inp_port.currentData() + logger.info("Connection %s", self.interface) + try: + self.interface.open() + except (IOError, AttributeError) as exc: + logger.error("Tried to open %s and failed: %s", + self.interface, exc) + return + if not self.interface.isOpen(): + logger.error("Unable to open port %s", self.interface) + return + self.interface.timeout = 0.05 + sleep(0.1) + try: + self.app.vna = get_VNA(self.interface) + except IOError as exc: + logger.error("Unable to connect to VNA: %s", exc) + + self.app.vna.validateInput = self.app.settings.value( + "SerialInputValidation", True, bool) + + # connected + self.btn_toggle.setText("Disconnect") + self.btn_toggle.repaint() + + frequencies = self.app.vna.readFrequencies() + if not frequencies: + logger.warning("No frequencies read") + return + logger.info("Read starting frequency %s and end frequency %s", + frequencies[0], frequencies[-1]) + self.app.sweep_control.set_start(frequencies[0]) + if frequencies[0] < frequencies[-1]: + self.app.sweep_control.set_end(frequencies[-1]) + else: + self.app.sweep_control.set_end( + frequencies[0] + + self.app.vna.datapoints * self.app.sweep_control.get_segments()) + + self.app.sweep_control.set_segments(1) # speed up things + self.app.sweep_control.update_center_span() + self.app.sweep_control.update_step_size() + + self.app.windows["sweep_settings"].vna_connected() + + logger.debug("Starting initial sweep") + self.app.sweep_start() + + def disconnect_device(self): + with self.interface.lock: + logger.info("Closing connection to %s", self.interface) + self.interface.close() + self.btn_toggle.setText("Connect to device") + self.btn_toggle.repaint() diff --git a/NanoVNASaver/Controls/SweepControl.py b/NanoVNASaver/Controls/SweepControl.py index daa82ebc..d2b32421 100644 --- a/NanoVNASaver/Controls/SweepControl.py +++ b/NanoVNASaver/Controls/SweepControl.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,26 +19,20 @@ import logging from PyQt5 import QtWidgets, QtCore -from PyQt5.QtCore import pyqtSignal - from NanoVNASaver.Formatting import ( format_frequency_sweep, format_frequency_short, parse_frequency) from NanoVNASaver.Inputs import FrequencyInputWidget +from NanoVNASaver.Controls.Control import Control logger = logging.getLogger(__name__) -class SweepControl(QtWidgets.QGroupBox): - updated = pyqtSignal(object) +class SweepControl(Control): - def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): - super().__init__() - self.app = app - self.setMaximumWidth(250) - self.setTitle(title) - control_layout = QtWidgets.QFormLayout(self) + def __init__(self, app: QtWidgets.QWidget): + super().__init__(app, "Sweep control") line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.VLine) @@ -49,9 +43,10 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): input_layout.addLayout(input_left_layout) input_layout.addWidget(line) input_layout.addLayout(input_right_layout) - control_layout.addRow(input_layout) + self.layout.addRow(input_layout) self.input_start = FrequencyInputWidget() + self.input_start.setFixedHeight(20) self.input_start.setMinimumWidth(60) self.input_start.setAlignment(QtCore.Qt.AlignRight) self.input_start.textEdited.connect(self.update_center_span) @@ -59,12 +54,14 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): input_left_layout.addRow(QtWidgets.QLabel("Start"), self.input_start) self.input_end = FrequencyInputWidget() + self.input_end.setFixedHeight(20) self.input_end.setAlignment(QtCore.Qt.AlignRight) self.input_end.textEdited.connect(self.update_center_span) self.input_end.textChanged.connect(self.update_step_size) input_left_layout.addRow(QtWidgets.QLabel("Stop"), self.input_end) self.input_center = FrequencyInputWidget() + self.input_center.setFixedHeight(20) self.input_center.setMinimumWidth(60) self.input_center.setAlignment(QtCore.Qt.AlignRight) self.input_center.textEdited.connect(self.update_start_end) @@ -72,6 +69,7 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): input_right_layout.addRow(QtWidgets.QLabel("Center"), self.input_center) self.input_span = FrequencyInputWidget() + self.input_span.setFixedHeight(20) self.input_span.setAlignment(QtCore.Qt.AlignRight) self.input_span.textEdited.connect(self.update_start_end) @@ -79,6 +77,7 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): self.input_segments = QtWidgets.QLineEdit(self.app.settings.value("Segments", "1")) self.input_segments.setAlignment(QtCore.Qt.AlignRight) + self.input_segments.setFixedHeight(20) self.input_segments.setFixedWidth(60) self.input_segments.textEdited.connect(self.update_step_size) @@ -88,23 +87,26 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): segment_layout = QtWidgets.QHBoxLayout() segment_layout.addWidget(self.input_segments) segment_layout.addWidget(self.label_step) - control_layout.addRow(QtWidgets.QLabel("Segments"), segment_layout) + self.layout.addRow(QtWidgets.QLabel("Segments"), segment_layout) btn_settings_window = QtWidgets.QPushButton("Sweep settings ...") + btn_settings_window.setFixedHeight(20) btn_settings_window.clicked.connect( lambda: self.app.display_window("sweep_settings")) - control_layout.addRow(btn_settings_window) + self.layout.addRow(btn_settings_window) self.progress_bar = QtWidgets.QProgressBar() self.progress_bar.setMaximum(100) self.progress_bar.setValue(0) - control_layout.addRow(self.progress_bar) + self.layout.addRow(self.progress_bar) self.btn_start = QtWidgets.QPushButton("Sweep") + self.btn_start.setFixedHeight(20) self.btn_start.clicked.connect(self.app.sweep_start) self.btn_start.setShortcut(QtCore.Qt.Key_W | QtCore.Qt.CTRL) self.btn_stop = QtWidgets.QPushButton("Stop") + self.btn_stop.setFixedHeight(20) self.btn_stop.clicked.connect(self.app.sweep_stop) self.btn_stop.setShortcut(QtCore.Qt.Key_Escape) self.btn_stop.setDisabled(True) @@ -114,7 +116,7 @@ def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"): btn_layout.setContentsMargins(0, 0, 0, 0) btn_layout_widget = QtWidgets.QWidget() btn_layout_widget.setLayout(btn_layout) - control_layout.addRow(btn_layout_widget) + self.layout.addRow(btn_layout_widget) self.input_start.textEdited.emit(self.input_start.text()) self.input_start.textChanged.emit(self.input_start.text()) diff --git a/NanoVNASaver/Controls/__init__.py b/NanoVNASaver/Controls/__init__.py index ab0c77d2..c67faef2 100644 --- a/NanoVNASaver/Controls/__init__.py +++ b/NanoVNASaver/Controls/__init__.py @@ -1,2 +1,3 @@ from .MarkerControl import MarkerControl from .SweepControl import SweepControl +from .SerialControl import SerialControl diff --git a/NanoVNASaver/Formatting.py b/NanoVNASaver/Formatting.py index 7c406f88..17b4a9cc 100644 --- a/NanoVNASaver/Formatting.py +++ b/NanoVNASaver/Formatting.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -36,10 +36,11 @@ FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True, printable_min=0, unprintable_under="- ") FMT_COMPLEX_NEG = SITools.Format(max_nr_digits=3, allow_strip=True) +FMT_SHORT = SITools.Format(max_nr_digits=4) FMT_WAVELENGTH = SITools.Format(max_nr_digits=4, space_str=" ") FMT_PARSE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True, parse_clamp_min=0) - +FMT_PARSE_VALUE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True) def format_frequency(freq: Number) -> str: return str(SITools.Value(freq, "Hz", FMT_FREQ)) @@ -52,6 +53,8 @@ def format_frequency_inputs(freq: float) -> str: def format_frequency_short(freq: Number) -> str: return str(SITools.Value(freq, "Hz", FMT_FREQ_SHORT)) +def format_frequency_chart(freq: Number) -> str: + return str(SITools.Value(freq, "", FMT_FREQ_SHORT)) def format_frequency_space(freq: float, fmt=FMT_FREQ_SPACE) -> str: return str(SITools.Value(freq, "Hz", fmt)) @@ -107,6 +110,18 @@ def format_phase(val: float) -> str: return f"{math.degrees(val):.2f}""\N{DEGREE SIGN}" +def format_complex_adm(z: complex, allow_negative: bool = False) -> str: + if z == 0: + return "- S" + adm = 1/z + + fmt_re = FMT_COMPLEX + if allow_negative: + fmt_re = FMT_COMPLEX_NEG + re = SITools.Value(adm.real, fmt=fmt_re) + im = SITools.Value(abs(adm.imag), fmt=FMT_COMPLEX) + return f"{re}{'-' if adm.imag < 0 else '+'}j{im} S" + def format_complex_imp(z: complex, allow_negative: bool = False) -> str: fmt_re = FMT_COMPLEX if allow_negative: @@ -118,9 +133,19 @@ def format_complex_imp(z: complex, allow_negative: bool = False) -> str: def format_wavelength(length: Number) -> str: return str(SITools.Value(length, "m", FMT_WAVELENGTH)) +def format_y_axis(val: float, unit: str="") -> str: + return str(SITools.Value(val, unit, FMT_SHORT)) def parse_frequency(freq: str) -> int: try: return int(SITools.Value(freq, "Hz", FMT_PARSE)) except (ValueError, IndexError): return -1 + +def parse_value(val: str, unit: str = "", + fmt: SITools.Format = FMT_PARSE_VALUE) -> int: + try: + val.replace(',', '.') + return float(SITools.Value(val, unit, fmt)) + except (ValueError, IndexError): + return 0.0 diff --git a/NanoVNASaver/Hardware/AVNA.py b/NanoVNASaver/Hardware/AVNA.py index a26ed9a1..267532f9 100644 --- a/NanoVNASaver/Hardware/AVNA.py +++ b/NanoVNASaver/Hardware/AVNA.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Hardware/Hardware.py b/NanoVNASaver/Hardware/Hardware.py index 0d3a3869..68a5d7b0 100644 --- a/NanoVNASaver/Hardware/Hardware.py +++ b/NanoVNASaver/Hardware/Hardware.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,6 +32,7 @@ from NanoVNASaver.Hardware.NanoVNA_H import NanoVNA_H from NanoVNASaver.Hardware.NanoVNA_H4 import NanoVNA_H4 from NanoVNASaver.Hardware.NanoVNA_V2 import NanoVNA_V2 +from NanoVNASaver.Hardware.TinySA import TinySA from NanoVNASaver.Hardware.Serial import drain_serial, Interface @@ -48,10 +49,24 @@ TIMEOUT = 0.2 WAIT = 0.05 +NAME2DEVICE = { + "S-A-A-2" : NanoVNA_V2, + "AVNA": AVNA, + "H4": NanoVNA_H4, + "H": NanoVNA_H, + "F_V2": NanoVNA_F_V2, + "F": NanoVNA_F, + "NanoVNA": NanoVNA, + "tinySA": TinySA, + "Unknown": NanoVNA, +} + # The USB Driver for NanoVNA V2 seems to deliver an # incompatible hardware info like: # 'PORTS\\VID_04B4&PID_0008\\DEMO' # This function will fix it. + + def _fix_v2_hwinfo(dev): if dev.hwid == r'PORTS\VID_04B4&PID_0008\DEMO': dev.vid, dev.pid = 0x04b4, 0x0008 @@ -72,64 +87,68 @@ def get_interfaces() -> List[Interface]: t.name, d.vid, d.pid, d.device) iface = Interface('serial', t.name) iface.port = d.device + iface.open() + iface.comment = get_comment(iface) + iface.close() interfaces.append(iface) + + logger.debug("Interfaces: %s", interfaces) return interfaces def get_VNA(iface: Interface) -> 'VNA': # serial_port.timeout = TIMEOUT + return NAME2DEVICE[iface.comment](iface) +def get_comment(iface: Interface) -> str: logger.info("Finding correct VNA type...") with iface.lock: vna_version = detect_version(iface) if vna_version == 'v2': - logger.info("Type: NanoVNA-V2") - return NanoVNA_V2(iface) + return "S-A-A-2" logger.info("Finding firmware variant...") info = get_info(iface) - if info.find("AVNA + Teensy") >= 0: - logger.info("Type: AVNA") - return AVNA(iface) - if info.find("NanoVNA-H 4") >= 0: - logger.info("Type: NanoVNA-H4") - vna = NanoVNA_H4(iface) - return vna - if info.find("NanoVNA-H") >= 0: - logger.info("Type: NanoVNA-H") - vna = NanoVNA_H(iface) - return vna - if info.find("NanoVNA-F_V2") >= 0: - logger.info("Type: NanoVNA-F_V2") - return NanoVNA_F_V2(iface) - if info.find("NanoVNA-F") >= 0: - logger.info("Type: NanoVNA-F") - return NanoVNA_F(iface) - if info.find("NanoVNA") >= 0: - logger.info("Type: Generic NanoVNA") - return NanoVNA(iface) + for search, name in ( + ("AVNA + Teensy", "AVNA"), + ("NanoVNA-H 4", "H4"), + ("NanoVNA-H", "H"), + ("NanoVNA-F_V2", "F_V2"), + ("NanoVNA-F", "F"), + ("NanoVNA", "NanoVNA"), + ("tinySA", "tinySA"), + ): + if info.find(search) >= 0: + return name logger.warning("Did not recognize NanoVNA type from firmware.") - return NanoVNA(iface) + return "Unknown" def detect_version(serial_port: serial.Serial) -> str: data = "" for i in range(RETRIES): + drain_serial(serial_port) + serial_port.write("\r".encode("ascii")) + # workaround for some UnicodeDecodeError ... repeat ;-) drain_serial(serial_port) serial_port.write("\r".encode("ascii")) sleep(0.05) + data = serial_port.read(128).decode("ascii") if data.startswith("ch> "): return "v1" # -H versions if data.startswith("\r\nch> "): return "vh" + if data.startswith("\r\n?\r\nch> "): + return "vh" if data.startswith("2"): return "v2" logger.debug("Retry detection: %s", i + 1) logger.error('No VNA detected. Hardware responded to CR with: %s', data) return "" + def get_info(serial_port: serial.Serial) -> str: for _ in range(RETRIES): drain_serial(serial_port) @@ -151,4 +170,5 @@ def get_info(serial_port: serial.Serial) -> str: logger.debug("Needed retries: %s", retries) break lines.append(line) + logger.debug("Info output: %s", lines) return "\n".join(lines) diff --git a/NanoVNASaver/Hardware/NanoVNA.py b/NanoVNASaver/Hardware/NanoVNA.py index d0cea41c..4c360093 100644 --- a/NanoVNASaver/Hardware/NanoVNA.py +++ b/NanoVNASaver/Hardware/NanoVNA.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,16 +47,13 @@ def __init__(self, iface: Interface): def _get_running_frequencies(self): - if self.name == "NanoVNA": - logger.debug("Reading values: frequencies") - try: - frequencies = super().readValues("frequencies") - return frequencies[0], frequencies[-1] - except Exception as e: - logger.warning("%s reading frequencies", e) - logger.info("falling back to generic") - else: - logger.debug("Name %s, fallback to generic", self.name) + logger.debug("Reading values: frequencies") + try: + frequencies = super().readValues("frequencies") + return frequencies[0], frequencies[-1] + except Exception as e: + logger.warning("%s reading frequencies", e) + logger.info("falling back to generic") return VNA._get_running_frequencies(self) diff --git a/NanoVNASaver/Hardware/NanoVNA_F.py b/NanoVNASaver/Hardware/NanoVNA_F.py index 4852fbda..c5efdffc 100644 --- a/NanoVNASaver/Hardware/NanoVNA_F.py +++ b/NanoVNASaver/Hardware/NanoVNA_F.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,10 +18,6 @@ # along with this program. If not, see . import logging -import serial -import numpy as np -from PyQt5 import QtGui - from NanoVNASaver.Hardware.NanoVNA import NanoVNA from NanoVNASaver.Hardware.Serial import Interface diff --git a/NanoVNASaver/Hardware/NanoVNA_F_V2.py b/NanoVNASaver/Hardware/NanoVNA_F_V2.py index c3fcd92a..b23d44fe 100644 --- a/NanoVNASaver/Hardware/NanoVNA_F_V2.py +++ b/NanoVNASaver/Hardware/NanoVNA_F_V2.py @@ -1,8 +1,24 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . import logging -from NanoVNASaver.Hardware.Serial import drain_serial, Interface + import serial -import struct -import numpy as np from PyQt5 import QtGui from NanoVNASaver.Hardware.NanoVNA import NanoVNA diff --git a/NanoVNASaver/Hardware/NanoVNA_H.py b/NanoVNASaver/Hardware/NanoVNA_H.py index a8ae05ae..681b0a52 100644 --- a/NanoVNASaver/Hardware/NanoVNA_H.py +++ b/NanoVNASaver/Hardware/NanoVNA_H.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Hardware/NanoVNA_H4.py b/NanoVNASaver/Hardware/NanoVNA_H4.py index be0c14c8..47427083 100644 --- a/NanoVNASaver/Hardware/NanoVNA_H4.py +++ b/NanoVNASaver/Hardware/NanoVNA_H4.py @@ -1,7 +1,7 @@ # NanoVNASaver # # A python program to view and export Touchstone data from a NanoVNA -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Hardware/NanoVNA_V2.py b/NanoVNASaver/Hardware/NanoVNA_V2.py index d45a04cc..e87c7560 100644 --- a/NanoVNASaver/Hardware/NanoVNA_V2.py +++ b/NanoVNASaver/Hardware/NanoVNA_V2.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -202,6 +202,8 @@ def readValues(self, value) -> List[str]: ret = [str(x.real) + ' ' + str(x.imag) for x in ret] return ret + return [] + def resetSweep(self, start: int, stop: int): self.setSweep(start, stop) @@ -214,8 +216,8 @@ def readVersion(self) -> 'Version': sleep(WRITE_SLEEP) resp = self.serial.read(2) if len(resp) != 2: - logger.error("Timeout reading version registers") - return None + logger.error("Timeout reading version registers. Got: %s", resp) + raise IOError("Timeout reading version registers") result = Version(f"{resp[0]}.0.{resp[1]}") logger.debug("readVersion: %s", result) return result diff --git a/NanoVNASaver/Hardware/Serial.py b/NanoVNASaver/Hardware/Serial.py index b7abdfda..16006374 100644 --- a/NanoVNASaver/Hardware/Serial.py +++ b/NanoVNASaver/Hardware/Serial.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Hardware/TinySA.py b/NanoVNASaver/Hardware/TinySA.py new file mode 100644 index 00000000..cff4e6a5 --- /dev/null +++ b/NanoVNASaver/Hardware/TinySA.py @@ -0,0 +1,124 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import logging +import struct +from typing import List + +import serial +import numpy as np +from PyQt5 import QtGui + +from NanoVNASaver.Hardware.Serial import drain_serial, Interface +from NanoVNASaver.Hardware.VNA import VNA + +logger = logging.getLogger(__name__) + + +class TinySA(VNA): + name = "tinySA" + screenwidth = 320 + screenheight = 240 + valid_datapoints = (290, ) + + def __init__(self, iface: Interface): + super().__init__(iface) + self.features = set(('Screenshots',)) + logger.debug("Setting initial start,stop") + self.start, self.stop = self._get_running_frequencies() + self.sweep_max_freq_Hz = 950e6 + self._sweepdata = [] + + def _get_running_frequencies(self): + + logger.debug("Reading values: frequencies") + try: + frequencies = super().readValues("frequencies") + return frequencies[0], frequencies[-1] + except Exception as e: + logger.warning("%s reading frequencies", e) + logger.info("falling back to generic") + + return VNA._get_running_frequencies(self) + + def _capture_data(self) -> bytes: + timeout = self.serial.timeout + with self.serial.lock: + drain_serial(self.serial) + timeout = self.serial.timeout + self.serial.write("capture\r".encode('ascii')) + self.serial.readline() + self.serial.timeout = 4 + image_data = self.serial.read( + self.screenwidth * self.screenheight * 2) + self.serial.timeout = timeout + self.serial.timeout = timeout + return image_data + + def _convert_data(self, image_data: bytes) -> bytes: + rgb_data = struct.unpack( + f">{self.screenwidth * self.screenheight}H", + image_data) + rgb_array = np.array(rgb_data, dtype=np.uint32) + return (0xFF000000 + + ((rgb_array & 0xF800) << 8) + + ((rgb_array & 0x07E0) << 5) + + ((rgb_array & 0x001F) << 3)) + + def getScreenshot(self) -> QtGui.QPixmap: + logger.debug("Capturing screenshot...") + if not self.connected(): + return QtGui.QPixmap() + try: + rgba_array = self._convert_data(self._capture_data()) + image = QtGui.QImage( + rgba_array, + self.screenwidth, + self.screenheight, + QtGui.QImage.Format_ARGB32) + logger.debug("Captured screenshot") + return QtGui.QPixmap(image) + except serial.SerialException as exc: + logger.exception( + "Exception while capturing screenshot: %s", exc) + return QtGui.QPixmap() + + def resetSweep(self, start: int, stop: int): + return + + def setSweep(self, start, stop): + self.start = start + self.stop = stop + list(self.exec_command(f"sweep {start} {stop} {self.datapoints}")) + list(self.exec_command("trigger auto")) + + def readFrequencies(self) -> List[int]: + logger.debug("readFrequencies") + return [int(line) for line in self.exec_command("frequencies")] + + def readValues(self, value) -> List[str]: + logger.debug("Read: %s", value) + if value == "data 0": + self._sweepdata = [] + for line in self.exec_command("data"): + self._sweepdata.append(f"0 {line.strip()}") + return self._sweepdata + if value == "data 0": + return [x[0] for x in self._sweepdata] + if value == "data 1": + return [x[0] for x in self._sweepdata] diff --git a/NanoVNASaver/Hardware/VNA.py b/NanoVNASaver/Hardware/VNA.py index fd4fe1ec..ae749b46 100644 --- a/NanoVNASaver/Hardware/VNA.py +++ b/NanoVNASaver/Hardware/VNA.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Inputs.py b/NanoVNASaver/Inputs.py index 494dfbfb..ca73605c 100644 --- a/NanoVNASaver/Inputs.py +++ b/NanoVNASaver/Inputs.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Marker/Delta.py b/NanoVNASaver/Marker/Delta.py index 7488ea9e..1f068768 100644 --- a/NanoVNASaver/Marker/Delta.py +++ b/NanoVNASaver/Marker/Delta.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ from NanoVNASaver import RFTools from NanoVNASaver.Formatting import ( format_capacitance, + format_complex_adm, format_complex_imp, format_frequency_space, format_gain, @@ -51,8 +52,8 @@ def set_markers(self, marker_a: Marker, marker_b: Marker): def updateLabels(self): # pylint: disable=arguments-differ a = self.marker_a b = self.marker_b - s11_a = a.s11data[1] - s11_b = b.s11data[1] + s11_a = a.s11[1] + s11_b = b.s11[1] imp_a = s11_a.impedance() imp_b = s11_b.impedance() @@ -90,7 +91,7 @@ def updateLabels(self): # pylint: disable=arguments-differ format_frequency_space(s11_b.freq - s11_a.freq)) self.label['lambda'].setText( format_wavelength(s11_b.wavelength - s11_a.wavelength)) - self.label['admittance'].setText(format_complex_imp(imp_p, True)) + self.label['admittance'].setText(format_complex_adm(imp_p, True)) self.label['impedance'].setText(format_complex_imp(imp, True)) self.label['parc'].setText(cap_p_str) @@ -101,8 +102,8 @@ def updateLabels(self): # pylint: disable=arguments-differ self.label['returnloss'].setText( format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive)) self.label['s11groupdelay'].setText(format_group_delay( - RFTools.groupDelay(b.s11data, 1) - - RFTools.groupDelay(a.s11data, 1))) + RFTools.groupDelay(b.s11, 1) - + RFTools.groupDelay(a.s11, 1))) self.label['s11mag'].setText( format_magnitude(abs(s11_b.z) - abs(s11_a.z))) @@ -119,14 +120,14 @@ def updateLabels(self): # pylint: disable=arguments-differ self.label['serr'].setText(format_resistance(imp.real, True)) self.label['vswr'].setText(format_vswr(s11_b.vswr - s11_a.vswr)) - if len(a.s21data) == len(a.s11data): - s21_a = a.s21data[1] - s21_b = b.s21data[1] + if len(a.s21) == len(a.s11): + s21_a = a.s21[1] + s21_b = b.s21[1] self.label['s21gain'].setText(format_gain( s21_b.gain - s21_a.gain)) self.label['s21groupdelay'].setText(format_group_delay( - (RFTools.groupDelay(b.s21data, 1) - - RFTools.groupDelay(a.s21data, 1)) / 2)) + (RFTools.groupDelay(b.s21, 1) - + RFTools.groupDelay(a.s21, 1)) / 2)) self.label['s21mag'].setText(format_magnitude( abs(s21_b.z) - abs(s21_a.z))) self.label['s21phase'].setText(format_phase( diff --git a/NanoVNASaver/Marker/Values.py b/NanoVNASaver/Marker/Values.py index e1b4193b..ee58d071 100644 --- a/NanoVNASaver/Marker/Values.py +++ b/NanoVNASaver/Marker/Values.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -68,26 +68,26 @@ def default_label_ids() -> str: class Value(): """Contains the data area to calculate marker values from""" def __init__(self, freq: int = 0, - s11data: List[Datapoint] = None, - s21data: List[Datapoint] = None): + s11: List[Datapoint] = None, + s21: List[Datapoint] = None): self.freq = freq - self.s11data = [] if s11data is None else s11data[:] - self.s21data = [] if s21data is None else s21data[:] + self.s11 = [] if s11 is None else s11[:] + self.s21 = [] if s21 is None else s21[:] def store(self, index: int, - s11data: List[Datapoint], - s21data: List[Datapoint]): + s11: List[Datapoint], + s21: List[Datapoint]): # handle boundaries if index == 0: index = 1 - s11data = [s11data[0], ] + s11data - if s21data: - s21data = [s21data[0], ] + s21data - if index == len(s11data): - s11data = s11data + [s11data[-1], ] - if s21data: - s21data = s21data + [s21data[-1], ] - self.freq = s11data[1].freq - self.s11data = s11data[index-1:index+2] - if s21data: - self.s21data = s21data[index-1:index+2] + s11 = [s11[0], ] + s11 + if s21: + s21 = [s21[0], ] + s21 + if index == len(s11): + s11 = s11 + [s11[-1], ] + if s21: + s21 = s21 + [s21[-1], ] + self.freq = s11[1].freq + self.s11 = s11[index-1:index+2] + if s21: + self.s21 = s21[index-1:index+2] diff --git a/NanoVNASaver/Marker/Widget.py b/NanoVNASaver/Marker/Widget.py index 5708f426..cf19aa19 100644 --- a/NanoVNASaver/Marker/Widget.py +++ b/NanoVNASaver/Marker/Widget.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ from NanoVNASaver import RFTools from NanoVNASaver.Formatting import ( format_capacitance, + format_complex_adm, format_complex_imp, format_frequency_space, format_gain, @@ -87,6 +88,7 @@ def __init__(self, name: str = "", qsettings: QtCore.QSettings = None): self.name = f"Marker {Marker._instances}" self.frequencyInput = FrequencyInput() + self.frequencyInput.setMinimumHeight(20) self.frequencyInput.setAlignment(QtCore.Qt.AlignRight) self.frequencyInput.editingFinished.connect( lambda: self.setFrequency( @@ -107,6 +109,7 @@ def __init__(self, name: str = "", qsettings: QtCore.QSettings = None): ############################################################### self.btnColorPicker = QtWidgets.QPushButton("█") + self.btnColorPicker.setMinimumHeight(20) self.btnColorPicker.setFixedWidth(20) self.btnColorPicker.clicked.connect( lambda: self.setColor(QtWidgets.QColorDialog.getColor( @@ -141,7 +144,9 @@ def __init__(self, name: str = "", qsettings: QtCore.QSettings = None): # line only if more then 3 selected self.left_form = QtWidgets.QFormLayout() + self.left_form.setVerticalSpacing(0) self.right_form = QtWidgets.QFormLayout() + self.right_form.setVerticalSpacing(0) box_layout.addLayout(self.left_form) box_layout.addWidget(line) box_layout.addLayout(self.right_form) @@ -281,32 +286,36 @@ def resetLabels(self): v.setText("") def updateLabels(self, - s11data: List[RFTools.Datapoint], - s21data: List[RFTools.Datapoint]): - if not s11data: + s11: List[RFTools.Datapoint], + s21: List[RFTools.Datapoint]): + if not s11: return if self.location == -1: # initial position try: - location = (self.index -1) / (self._instances - 1) * (len(s11data) - 1) + location = (self.index -1) / (self._instances - 1) * (len(s11) - 1) self.location = int(location) except ZeroDivisionError: self.location = 0 try: - s11 = s11data[self.location] + _s11 = s11[self.location] except IndexError: self.location = 0 return - self.frequencyInput.setText(s11.freq) - self.store(self.location, s11data, s21data) + self.frequencyInput.setText(_s11.freq) + self.store(self.location, s11, s21) - imp = s11.impedance() - cap_str = format_capacitance(RFTools.impedance_to_capacitance(imp, s11.freq)) - ind_str = format_inductance(RFTools.impedance_to_inductance(imp, s11.freq)) + imp = _s11.impedance() + cap_str = format_capacitance( + RFTools.impedance_to_capacitance(imp, _s11.freq)) + ind_str = format_inductance( + RFTools.impedance_to_inductance(imp, _s11.freq)) imp_p = RFTools.serial_to_parallel(imp) - cap_p_str = format_capacitance(RFTools.impedance_to_capacitance(imp_p, s11.freq)) - ind_p_str = format_inductance(RFTools.impedance_to_inductance(imp_p, s11.freq)) + cap_p_str = format_capacitance( + RFTools.impedance_to_capacitance(imp_p, _s11.freq)) + ind_p_str = format_inductance( + RFTools.impedance_to_inductance(imp_p, _s11.freq)) if imp.imag < 0: x_str = cap_str @@ -318,40 +327,44 @@ def updateLabels(self, else: x_p_str = ind_p_str - self.label['actualfreq'].setText(format_frequency_space(s11.freq)) - self.label['lambda'].setText(format_wavelength(s11.wavelength)) - self.label['admittance'].setText(format_complex_imp(imp_p)) + self.label['actualfreq'].setText(format_frequency_space(_s11.freq)) + self.label['lambda'].setText(format_wavelength(_s11.wavelength)) + self.label['admittance'].setText(format_complex_adm(imp)) self.label['impedance'].setText(format_complex_imp(imp)) self.label['parc'].setText(cap_p_str) self.label['parl'].setText(ind_p_str) self.label['parlc'].setText(x_p_str) self.label['parr'].setText(format_resistance(imp_p.real)) self.label['returnloss'].setText( - format_gain(s11.gain, self.returnloss_is_positive)) + format_gain(_s11.gain, self.returnloss_is_positive)) self.label['s11groupdelay'].setText( - format_group_delay(RFTools.groupDelay(s11data, self.location))) - self.label['s11mag'].setText(format_magnitude(abs(s11.z))) - self.label['s11phase'].setText(format_phase(s11.phase)) + format_group_delay(RFTools.groupDelay(s11, self.location))) + self.label['s11mag'].setText(format_magnitude(abs(_s11.z))) + self.label['s11phase'].setText(format_phase(_s11.phase)) self.label['s11polar'].setText( - str(round(abs(s11.z), 2)) + "∠" + format_phase(s11.phase)) - self.label['s11q'].setText(format_q_factor(s11.qFactor())) + str(round(abs(_s11.z), 2)) + "∠" + format_phase(_s11.phase)) + self.label['s11q'].setText(format_q_factor(_s11.qFactor())) self.label['s11z'].setText(format_resistance(abs(imp))) self.label['serc'].setText(cap_str) self.label['serl'].setText(ind_str) self.label['serlc'].setText(x_str) self.label['serr'].setText(format_resistance(imp.real)) - self.label['vswr'].setText(format_vswr(s11.vswr)) + self.label['vswr'].setText(format_vswr(_s11.vswr)) - if len(s21data) == len(s11data): - s21 = s21data[self.location] - self.label['s21gain'].setText(format_gain(s21.gain)) + if len(s21) == len(s11): + _s21 = s21[self.location] + self.label['s21gain'].setText(format_gain(_s21.gain)) self.label['s21groupdelay'].setText( - format_group_delay(RFTools.groupDelay(s21data, self.location) / 2)) - self.label['s21mag'].setText(format_magnitude(abs(s21.z))) - self.label['s21phase'].setText(format_phase(s21.phase)) + format_group_delay(RFTools.groupDelay(s21, self.location) / 2)) + self.label['s21mag'].setText(format_magnitude(abs(_s21.z))) + self.label['s21phase'].setText(format_phase(_s21.phase)) self.label['s21polar'].setText( - str(round(abs(s21.z), 2)) + "∠" + format_phase(s21.phase)) - self.label['s21magshunt'].setText(format_magnitude(abs(s21.shuntImpedance()))) - self.label['s21magseries'].setText(format_magnitude(abs(s21.seriesImpedance()))) - self.label['s21realimagshunt'].setText(format_complex_imp(s21.shuntImpedance(), allow_negative=True)) - self.label['s21realimagseries'].setText(format_complex_imp(s21.seriesImpedance(), allow_negative=True)) + str(round(abs(_s21.z), 2)) + "∠" + format_phase(_s21.phase)) + self.label['s21magshunt'].setText( + format_magnitude(abs(_s21.shuntImpedance()))) + self.label['s21magseries'].setText( + format_magnitude(abs(_s21.seriesImpedance()))) + self.label['s21realimagshunt'].setText( + format_complex_imp(_s21.shuntImpedance(), allow_negative=True)) + self.label['s21realimagseries'].setText( + format_complex_imp(_s21.seriesImpedance(), allow_negative=True)) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 6a94c1b1..6818f5b0 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,21 +20,20 @@ import sys import threading from collections import OrderedDict -from time import sleep, strftime, localtime -from typing import List +from time import strftime, localtime from PyQt5 import QtWidgets, QtCore, QtGui from .Windows import ( AboutWindow, AnalysisWindow, CalibrationWindow, DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow, - TDRWindow + TDRWindow, FilesWindow ) -from .Controls import MarkerControl, SweepControl +from .Controls import MarkerControl, SweepControl, SerialControl from .Formatting import format_frequency, format_vswr, format_gain -from .Hardware.Hardware import Interface, get_interfaces, get_VNA +from .Hardware.Hardware import Interface from .Hardware.VNA import VNA -from .RFTools import Datapoint, corr_att_data +from .RFTools import corr_att_data from .Charts.Chart import Chart from .Charts import ( CapacitanceChart, @@ -72,7 +71,7 @@ def __init__(self): self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, "NanoVNASaver", "NanoVNASaver") - print(f"Settings: {self.settings.fileName()}") + logger.info("Settings from: %s", self.settings.fileName()) self.threadpool = QtCore.QThreadPool() self.sweep = Sweep() self.worker = SweepWorker(self) @@ -80,7 +79,6 @@ def __init__(self): self.worker.signals.updated.connect(self.dataUpdated) self.worker.signals.finished.connect(self.sweepFinished) self.worker.signals.sweepError.connect(self.showSweepError) - self.worker.signals.fatalSweepError.connect(self.showFatalSweepError) self.markers = [] @@ -91,18 +89,19 @@ def __init__(self): self.sweep_control = SweepControl(self) self.marker_control = MarkerControl(self) + self.serial_control = SerialControl(self) self.bands = BandsModel() self.interface = Interface("serial", "None") - self.vna = VNA(self.interface) + try: + self.vna = VNA(self.interface) + except IOError as exc: + self.showError(f"{exc}\n\nPlease try reconnect") self.dataLock = threading.Lock() - # TODO: use Touchstone class as data container - self.data11: List[Datapoint] = [] - self.data21: List[Datapoint] = [] - self.referenceS11data: List[Datapoint] = [] - self.referenceS21data: List[Datapoint] = [] + self.data = Touchstone() + self.ref_data = Touchstone() self.sweepSource = "" self.referenceSource = "" @@ -179,8 +178,9 @@ def __init__(self): self.combinedCharts = list(self.charts["combined"].values()) # List of all charts that can be selected for display - self.selectable_charts = self.s11charts + self.s21charts + self.combinedCharts - self.selectable_charts.append(self.tdr_mainwindow_chart) + self.selectable_charts = ( + self.s11charts + self.s21charts + + self.combinedCharts + [self.tdr_mainwindow_chart, ]) # List of all charts that subscribe to updates (including duplicates!) self.subscribing_charts = [] @@ -192,6 +192,8 @@ def __init__(self): self.charts_layout = QtWidgets.QGridLayout() + QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Q"), self, self.close) + ############################################################### # Create main layout ############################################################### @@ -224,7 +226,7 @@ def __init__(self): # "analysis": AnalysisWindow(self), "calibration": CalibrationWindow(self), "device_settings": DeviceSettingsWindow(self), - "file": QtWidgets.QWidget(), + "file": FilesWindow(self), "sweep_settings": SweepSettingsWindow(self), "setup": DisplaySettingsWindow(self), "tdr": TDRWindow(self), @@ -253,7 +255,7 @@ def __init__(self): self.marker_data_layout.addWidget(m.get_data_layout()) scroll2 = QtWidgets.QScrollArea() - scroll2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + #scroll2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) scroll2.setWidgetResizable(True) scroll2.setVisible(True) @@ -275,6 +277,7 @@ def __init__(self): s11_control_box = QtWidgets.QGroupBox() s11_control_box.setTitle("S11") s11_control_layout = QtWidgets.QFormLayout() + s11_control_layout.setVerticalSpacing(0) s11_control_box.setLayout(s11_control_layout) self.s11_min_swr_label = QtWidgets.QLabel() @@ -287,6 +290,7 @@ def __init__(self): s21_control_box = QtWidgets.QGroupBox() s21_control_box.setTitle("S21") s21_control_layout = QtWidgets.QFormLayout() + s21_control_layout.setVerticalSpacing(0) s21_control_box.setLayout(s21_control_layout) self.s21_min_gain_label = QtWidgets.QLabel() @@ -301,6 +305,7 @@ def __init__(self): self.windows["analysis"] = AnalysisWindow(self) btn_show_analysis = QtWidgets.QPushButton("Analysis ...") + btn_show_analysis.setMinimumHeight(20) btn_show_analysis.clicked.connect( lambda: self.display_window("analysis")) self.marker_column.addWidget(btn_show_analysis) @@ -318,14 +323,16 @@ def __init__(self): tdr_control_box.setTitle("TDR") tdr_control_layout = QtWidgets.QFormLayout() tdr_control_box.setLayout(tdr_control_layout) - tdr_control_box.setMaximumWidth(250) + tdr_control_box.setMaximumWidth(240) self.tdr_result_label = QtWidgets.QLabel() + self.tdr_result_label.setMinimumHeight(20) tdr_control_layout.addRow( "Estimated cable length:", self.tdr_result_label) self.tdr_button = QtWidgets.QPushButton( "Time Domain Reflectometry ...") + self.tdr_button.setMinimumHeight(20) self.tdr_button.clicked.connect(lambda: self.display_window("tdr")) tdr_control_layout.addRow(self.tdr_button) @@ -345,13 +352,15 @@ def __init__(self): ############################################################### reference_control_box = QtWidgets.QGroupBox() - reference_control_box.setMaximumWidth(250) + reference_control_box.setMaximumWidth(240) reference_control_box.setTitle("Reference sweep") reference_control_layout = QtWidgets.QFormLayout(reference_control_box) btn_set_reference = QtWidgets.QPushButton("Set current as reference") + btn_set_reference.setMinimumHeight(20) btn_set_reference.clicked.connect(self.setReference) self.btnResetReference = QtWidgets.QPushButton("Reset reference") + self.btnResetReference.setMinimumHeight(20) self.btnResetReference.clicked.connect(self.resetReference) self.btnResetReference.setDisabled(True) @@ -364,84 +373,14 @@ def __init__(self): # Serial control ############################################################### - serial_control_box = QtWidgets.QGroupBox() - serial_control_box.setMaximumWidth(250) - serial_control_box.setTitle("Serial port control") - serial_control_layout = QtWidgets.QFormLayout(serial_control_box) - self.serialPortInput = QtWidgets.QComboBox() - self.rescanSerialPort() - self.serialPortInput.setEditable(True) - btn_rescan_serial_port = QtWidgets.QPushButton("Rescan") - btn_rescan_serial_port.setFixedWidth(65) - btn_rescan_serial_port.clicked.connect(self.rescanSerialPort) - serial_port_input_layout = QtWidgets.QHBoxLayout() - serial_port_input_layout.addWidget(self.serialPortInput) - serial_port_input_layout.addWidget(btn_rescan_serial_port) - serial_control_layout.addRow( - QtWidgets.QLabel("Serial port"), serial_port_input_layout) - - serial_button_layout = QtWidgets.QHBoxLayout() - - self.btnSerialToggle = QtWidgets.QPushButton("Connect to device") - self.btnSerialToggle.clicked.connect(self.serialButtonClick) - serial_button_layout.addWidget(self.btnSerialToggle, stretch=1) - - self.btnDeviceSettings = QtWidgets.QPushButton("Manage") - self.btnDeviceSettings.setFixedWidth(65) - self.btnDeviceSettings.clicked.connect( - lambda: self.display_window("device_settings")) - serial_button_layout.addWidget(self.btnDeviceSettings, stretch=0) - serial_control_layout.addRow(serial_button_layout) - left_column.addWidget(serial_control_box) - - ############################################################### - # File control - ############################################################### - - self.windows["file"].setWindowTitle("Files") - self.windows["file"].setWindowIcon(self.icon) - self.windows["file"].setMinimumWidth(200) - QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self.windows["file"], - self.windows["file"].hide) - file_window_layout = QtWidgets.QVBoxLayout() - self.windows["file"].setLayout(file_window_layout) - - load_file_control_box = QtWidgets.QGroupBox("Import file") - load_file_control_box.setMaximumWidth(300) - load_file_control_layout = QtWidgets.QFormLayout(load_file_control_box) - - btn_load_sweep = QtWidgets.QPushButton("Load as sweep") - btn_load_sweep.clicked.connect(self.loadSweepFile) - btn_load_reference = QtWidgets.QPushButton("Load reference") - btn_load_reference.clicked.connect(self.loadReferenceFile) - load_file_control_layout.addRow(btn_load_sweep) - load_file_control_layout.addRow(btn_load_reference) - - file_window_layout.addWidget(load_file_control_box) - - save_file_control_box = QtWidgets.QGroupBox("Export file") - save_file_control_box.setMaximumWidth(300) - save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box) - - btn_export_file = QtWidgets.QPushButton("Save 1-Port file (S1P)") - btn_export_file.clicked.connect(lambda: self.exportFile(1)) - save_file_control_layout.addRow(btn_export_file) - - btn_export_file = QtWidgets.QPushButton("Save 2-Port file (S2P)") - btn_export_file.clicked.connect(lambda: self.exportFile(4)) - save_file_control_layout.addRow(btn_export_file) - - file_window_layout.addWidget(save_file_control_box) - - btn_open_file_window = QtWidgets.QPushButton("Files ...") - btn_open_file_window.clicked.connect( - lambda: self.display_window("file")) + left_column.addWidget(self.serial_control) ############################################################### # Calibration ############################################################### btnOpenCalibrationWindow = QtWidgets.QPushButton("Calibration ...") + btnOpenCalibrationWindow.setMinimumHeight(20) self.calibrationWindow = CalibrationWindow(self) btnOpenCalibrationWindow.clicked.connect( lambda: self.display_window("calibration")) @@ -451,16 +390,26 @@ def __init__(self): ############################################################### btn_display_setup = QtWidgets.QPushButton("Display setup ...") - btn_display_setup.setMaximumWidth(250) + btn_display_setup.setMinimumHeight(20) + btn_display_setup.setMaximumWidth(240) btn_display_setup.clicked.connect( lambda: self.display_window("setup")) btn_about = QtWidgets.QPushButton("About ...") - btn_about.setMaximumWidth(250) + btn_about.setMinimumHeight(20) + btn_about.setMaximumWidth(240) btn_about.clicked.connect( lambda: self.display_window("about")) + + btn_open_file_window = QtWidgets.QPushButton("Files") + btn_open_file_window.setMinimumHeight(20) + btn_open_file_window.setMaximumWidth(240) + + btn_open_file_window.clicked.connect( + lambda: self.display_window("file")) + button_grid = QtWidgets.QGridLayout() button_grid.addWidget(btn_open_file_window, 0, 0) button_grid.addWidget(btnOpenCalibrationWindow, 0, 1) @@ -470,119 +419,6 @@ def __init__(self): logger.debug("Finished building interface") - def rescanSerialPort(self): - self.serialPortInput.clear() - for iface in get_interfaces(): - self.serialPortInput.insertItem(1, f"{iface}", iface) - self.serialPortInput.repaint() - - def exportFile(self, nr_params: int = 1): - if len(self.data11) == 0: - QtWidgets.QMessageBox.warning( - self, "No data to save", "There is no data to save.") - return - if nr_params > 2 and len(self.data21) == 0: - QtWidgets.QMessageBox.warning( - self, "No S21 data to save", "There is no S21 data to save.") - return - - filedialog = QtWidgets.QFileDialog(self) - if nr_params == 1: - filedialog.setDefaultSuffix("s1p") - filedialog.setNameFilter( - "Touchstone 1-Port Files (*.s1p);;All files (*.*)") - else: - filedialog.setDefaultSuffix("s2p") - filedialog.setNameFilter( - "Touchstone 2-Port Files (*.s2p);;All files (*.*)") - filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) - selected = filedialog.exec() - if not selected: - return - filename = filedialog.selectedFiles()[0] - if filename == "": - logger.debug("No file name selected.") - return - - ts = Touchstone(filename) - ts.sdata[0] = self.data11 - if nr_params > 1: - ts.sdata[1] = self.data21 - for dp in self.data11: - ts.sdata[2].append(Datapoint(dp.freq, 0, 0)) - ts.sdata[3].append(Datapoint(dp.freq, 0, 0)) - try: - ts.save(nr_params) - except IOError as e: - logger.exception("Error during file export: %s", e) - return - - def serialButtonClick(self): - if not self.vna.connected(): - self.connect_device() - else: - self.disconnect_device() - - def connect_device(self): - if not self.interface: - return - with self.interface.lock: - self.interface = self.serialPortInput.currentData() - logger.info("Connection %s", self.interface) - try: - self.interface.open() - - except (IOError, AttributeError) as exc: - logger.error("Tried to open %s and failed: %s", - self.interface, exc) - return - if not self.interface.isOpen(): - logger.error("Unable to open port %s", self.interface) - return - self.interface.timeout = 0.05 - sleep(0.1) - try: - self.vna = get_VNA(self.interface) - except IOError as exc: - logger.error("Unable to connect to VNA: %s", exc) - - self.vna.validateInput = self.settings.value( - "SerialInputValidation", True, bool) - - # connected - self.btnSerialToggle.setText("Disconnect") - self.btnSerialToggle.repaint() - - frequencies = self.vna.readFrequencies() - if not frequencies: - logger.warning("No frequencies read") - return - logger.info("Read starting frequency %s and end frequency %s", - frequencies[0], frequencies[-1]) - self.sweep_control.set_start(frequencies[0]) - if frequencies[0] < frequencies[-1]: - self.sweep_control.set_end(frequencies[-1]) - else: - self.sweep_control.set_end( - frequencies[0] + - self.vna.datapoints * self.sweep_control.get_segments()) - - self.sweep_control.set_segments(1) # speed up things - self.sweep_control.update_center_span() - self.sweep_control.update_step_size() - - self.windows["sweep_settings"].vna_connected() - - logger.debug("Starting initial sweep") - self.sweep_start() - - def disconnect_device(self): - with self.interface.lock: - logger.info("Closing connection to %s", self.interface) - self.interface.close() - self.btnSerialToggle.setText("Connect to device") - self.btnSerialToggle.repaint() - def sweep_start(self): # Run the device data update if not self.vna.connected(): @@ -612,10 +448,10 @@ def sweep_stop(self): def saveData(self, data, data21, source=None): with self.dataLock: - self.data11 = data - self.data21 = data21 + self.data.s11 = data + self.data.s21 = data21 if self.s21att > 0: - self.data21 = corr_att_data(self.data21, self.s21att) + self.data.s21 = corr_att_data(self.data.s21, self.s21att) if source is not None: self.sweepSource = source else: @@ -626,9 +462,9 @@ def saveData(self, data, data21, source=None): def markerUpdated(self, marker: Marker): with self.dataLock: - marker.findLocation(self.data11) + marker.findLocation(self.data.s11) marker.resetLabels() - marker.updateLabels(self.data11, self.data21) + marker.updateLabels(self.data.s11, self.data.s21) for c in self.subscribing_charts: c.update() if Marker.count() >= 2 and not self.delta_marker_layout.isHidden(): @@ -641,27 +477,27 @@ def markerUpdated(self, marker: Marker): def dataUpdated(self): with self.dataLock: - s11data = self.data11[:] - s21data = self.data21[:] + s11 = self.data.s11[:] + s21 = self.data.s21[:] for m in self.markers: m.resetLabels() - m.updateLabels(s11data, s21data) + m.updateLabels(s11, s21) for c in self.s11charts: - c.setData(s11data) + c.setData(s11) for c in self.s21charts: - c.setData(s21data) + c.setData(s21) for c in self.combinedCharts: - c.setCombinedData(s11data, s21data) + c.setCombinedData(s11, s21) self.sweep_control.progress_bar.setValue(self.worker.percentage) self.windows["tdr"].updateTDR() - if s11data: - min_vswr = min(s11data, key=lambda data: data.vswr) + if s11: + min_vswr = min(s11, key=lambda data: data.vswr) self.s11_min_swr_label.setText( f"{format_vswr(min_vswr.vswr)} @ {format_frequency(min_vswr.freq)}") self.s11_min_rl_label.setText(format_gain(min_vswr.gain)) @@ -669,9 +505,9 @@ def dataUpdated(self): self.s11_min_swr_label.setText("") self.s11_min_rl_label.setText("") - if s21data: - min_gain = min(s21data, key=lambda data: data.gain) - max_gain = max(s21data, key=lambda data: data.gain) + if s21: + min_gain = min(s21, key=lambda data: data.gain) + max_gain = max(s21, key=lambda data: data.gain) self.s21_min_gain_label.setText( f"{format_gain(min_gain.gain)}" f" @ {format_frequency(min_gain.freq)}") @@ -695,22 +531,22 @@ def sweepFinished(self): marker.frequencyInput.textEdited.emit( marker.frequencyInput.text()) - def setReference(self, s11data=None, s21data=None, source=None): - if not s11data: + def setReference(self, s11=None, s21=None, source=None): + if not s11: with self.dataLock: - s11data = self.data11[:] - s21data = self.data21[:] + s11 = self.data.s11[:] + s21 = self.data.s21[:] - self.referenceS11data = s11data + self.ref_data.s11 = s11 for c in self.s11charts: - c.setReference(s11data) + c.setReference(s11) - self.referenceS21data = s21data + self.ref_data.s21 = s21 for c in self.s21charts: - c.setReference(s21data) + c.setReference(s21) for c in self.combinedCharts: - c.setCombinedReference(s11data, s21data) + c.setCombinedReference(s11, s21) self.btnResetReference.setDisabled(False) @@ -725,45 +561,24 @@ def updateTitle(self): insert = "(" if self.sweepSource != "": insert += ( - f"Sweep: {self.sweepSource} @ {len(self.data11)} points" + f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points" f"{', ' if self.referenceSource else ''}") if self.referenceSource != "": insert += ( f"Reference: {self.referenceSource} @" - f" {len(self.referenceS11data)} points") + f" {len(self.ref_data.s11)} points") insert += ")" title = f"{self.baseTitle} {insert if insert else ''}" self.setWindowTitle(title) def resetReference(self): - self.referenceS11data = [] - self.referenceS21data = [] + self.ref_data = Touchstone() self.referenceSource = "" self.updateTitle() for c in self.subscribing_charts: c.resetReference() self.btnResetReference.setDisabled(True) - def loadReferenceFile(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName( - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") - if filename != "": - self.resetReference() - t = Touchstone(filename) - t.load() - self.setReference(t.s11data, t.s21data, filename) - - def loadSweepFile(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName( - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") - if filename != "": - self.data11 = [] - self.data21 = [] - t = Touchstone(filename) - t.load() - self.saveData(t.s11data, t.s21data, filename) - self.dataUpdated() - def sizeHint(self) -> QtCore.QSize: return QtCore.QSize(1100, 950) @@ -774,10 +589,6 @@ def display_window(self, name): def showError(self, text): QtWidgets.QMessageBox.warning(self, "Error", text) - def showFatalSweepError(self): - self.showError(self.worker.error_message) - self.stopSerial() - def showSweepError(self): self.showError(self.worker.error_message) try: @@ -829,8 +640,8 @@ def changeFont(self, font: QtGui.QFont) -> None: qf_normal = QtGui.QFontMetricsF(normal_font) # Characters we would normally display standard_string = "0.123456789 0.123456789 MHz \N{OHM SIGN}" - new_width = qf_new.boundingRect(standard_string).width() - old_width = qf_normal.boundingRect(standard_string).width() + new_width = qf_new.horizontalAdvance(standard_string) + old_width = qf_normal.horizontalAdvance(standard_string) self.scaleFactor = new_width / old_width logger.debug("New font width: %f, normal font: %f, factor: %f", new_width, old_width, self.scaleFactor) @@ -842,6 +653,3 @@ def changeFont(self, font: QtGui.QFont) -> None: def update_sweep_title(self): for c in self.subscribing_charts: c.setSweepTitle(self.sweep.properties.name) - - def set_tx_power(self, freq_range, power_desc): - self.vna.setTXPower(freq_range, power_desc) diff --git a/NanoVNASaver/RFTools.py b/NanoVNASaver/RFTools.py index d52429eb..f52ac57e 100644 --- a/NanoVNASaver/RFTools.py +++ b/NanoVNASaver/RFTools.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,11 +18,7 @@ # along with this program. If not, see . import math import cmath -from threading import Lock -from typing import Iterator, List, NamedTuple, Tuple - -import numpy as np -from scipy.interpolate import interp1d +from typing import List, NamedTuple from NanoVNASaver.SITools import Format, clamp_value @@ -38,8 +34,7 @@ class Datapoint(NamedTuple): @property def z(self) -> complex: - """ return the datapoint impedance as complex number """ - # FIXME: not impedance, but s11 ? + """ return the s value complex number """ return complex(self.re, self.im) @property @@ -108,8 +103,7 @@ def groupDelay(data: List[Datapoint], index: int) -> float: delta_freq = data[idx1].freq - data[idx0].freq if delta_freq == 0: return 0 - val = -delta_angle / math.tau / delta_freq - return val + return -delta_angle / math.tau / delta_freq def impedance_to_capacitance(z: complex, freq: float) -> float: @@ -170,8 +164,7 @@ def corr_att_data(data: List[Datapoint], att: float) -> List[Datapoint]: """Correct the ratio for a given attenuation on s21 input""" if att <= 0: return data - else: - att = 10**(att / 20) + att = 10**(att / 20) ndata = [] for dp in data: corrected = dp.z * att diff --git a/NanoVNASaver/SITools.py b/NanoVNASaver/SITools.py index ec730677..b952e05b 100644 --- a/NanoVNASaver/SITools.py +++ b/NanoVNASaver/SITools.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -158,8 +158,8 @@ def parse(self, value: str) -> "Value": try: self._value = (decimal.Decimal(value, context=Value.CTX) * decimal.Decimal(factor, context=Value.CTX)) - except decimal.InvalidOperation: - raise ValueError + except decimal.InvalidOperation as exc: + raise ValueError() from exc self._value = clamp_value( self._value, self.fmt.parse_clamp_min, self.fmt.parse_clamp_max) return self diff --git a/NanoVNASaver/Settings/Bands.py b/NanoVNASaver/Settings/Bands.py index 620e6e2c..ac897056 100644 --- a/NanoVNASaver/Settings/Bands.py +++ b/NanoVNASaver/Settings/Bands.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,6 +41,8 @@ "70 cm;432000000;438000000", "23 cm;1240000000;1300000000", "13 cm;2320000000;2450000000", + "9 cm;3300000000;3500000000", + "5 cm;5650000000;5925000000", ) _HEADER_DATA = ("Band", "Start (Hz)", "End (Hz)") diff --git a/NanoVNASaver/Settings/Sweep.py b/NanoVNASaver/Settings/Sweep.py index 2ce88096..ec0d7f64 100644 --- a/NanoVNASaver/Settings/Sweep.py +++ b/NanoVNASaver/Settings/Sweep.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index 40fc8158..602d7580 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,7 +52,6 @@ class WorkerSignals(QtCore.QObject): updated = pyqtSignal() finished = pyqtSignal() sweepError = pyqtSignal() - fatalSweepError = pyqtSignal() class SweepWorker(QtCore.QRunnable): @@ -107,13 +106,11 @@ def _run(self): self.sweep = sweep self.init_data() - finished = False - while not finished: + while True: for i in range(sweep.segments): logger.debug("Sweep segment no %d", i) if self.stopped: logger.debug("Stopping sweeping as signalled") - finished = True break start, stop = sweep.get_index_range(i) @@ -123,13 +120,11 @@ def _run(self): self.percentage = (i + 1) * 100 / sweep.segments self.updateData(freq, values11, values21, i) except ValueError as e: - self.error_message = str(e) - self.stopped = True - self.running = False - self.signals.sweepError.emit() - - if not sweep.properties.mode == SweepMode.CONTINOUS: - finished = True + self.gui_error(str(e)) + else: + if sweep.properties.mode == SweepMode.CONTINOUS: + continue + break if sweep.segments > 1: start = sweep.start @@ -190,33 +185,30 @@ def applyCalibration(self, raw_data11: List[Datapoint], raw_data21: List[Datapoint] ) -> Tuple[List[Datapoint], List[Datapoint]]: - if self.offsetDelay != 0: - tmp = [] - for dp in raw_data11: - tmp.append(correct_delay(dp, self.offsetDelay, reflect=True)) - raw_data11 = tmp - tmp = [] - for dp in raw_data21: - tmp.append(correct_delay(dp, self.offsetDelay)) - raw_data21 = tmp - - if not self.app.calibration.isCalculated: - return raw_data11, raw_data21 data11: List[Datapoint] = [] data21: List[Datapoint] = [] - if self.app.calibration.isValid1Port(): - for dp in raw_data11: - data11.append(self.app.calibration.correct11(dp)) + if not self.app.calibration.isCalculated: + data11 = raw_data11.copy() + data21 = raw_data21.copy() else: - data11 = raw_data11 + if self.app.calibration.isValid1Port(): + for dp in raw_data11: + data11.append(self.app.calibration.correct11(dp)) + else: + data11 = raw_data11.copy() + + if self.app.calibration.isValid2Port(): + for dp in raw_data21: + data21.append(self.app.calibration.correct21(dp)) + else: + data21 = raw_data21.copy() + + if self.offsetDelay != 0: + data11 = [correct_delay(dp, self.offsetDelay, reflect=True) for dp in data11] + data21 = [correct_delay(dp, self.offsetDelay) for dp in data21] - if self.app.calibration.isValid2Port(): - for dp in raw_data21: - data21.append(self.app.calibration.correct21(dp)) - else: - data21 = raw_data21 return data11, data21 def readAveragedSegment(self, start, stop, averages=1): @@ -232,8 +224,17 @@ def readAveragedSegment(self, start, stop, averages=1): break logger.warning("Stop during average. Discarding sweep result.") return [], [], [] - logger.debug("Reading average no %d / %d", i+1, averages) - freq, tmp11, tmp21 = self.readSegment(start, stop) + logger.debug("Reading average no %d / %d", i + 1, averages) + retry = 0 + tmp11 = [] + while not tmp11 and retry < 5: + sleep(0.5 * retry) + retry += 1 + freq, tmp11, tmp21 = self.readSegment(start, stop) + if retry > 1: + logger.error("retry %s readSegment(%s,%s)", + retry, start, stop) + sleep(0.5) values11.append(tmp11) values21.append(tmp21) self.percentage += 100 / (self.sweep.segments * averages) @@ -314,7 +315,6 @@ def readData(self, data): f"device settings screen.") return returndata - def gui_error(self, message: str): self.error_message = message self.stopped = True diff --git a/NanoVNASaver/Touchstone.py b/NanoVNASaver/Touchstone.py index 1813ebb1..df855b36 100644 --- a/NanoVNASaver/Touchstone.py +++ b/NanoVNASaver/Touchstone.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -98,7 +98,7 @@ def parse(self, line: str): class Touchstone: FIELD_ORDER = ("11", "21", "12", "22") - def __init__(self, filename: str): + def __init__(self, filename: str=""): self.filename = filename self.sdata = [[], [], [], []] # at max 4 data pairs self.comments = [] @@ -106,35 +106,35 @@ def __init__(self, filename: str): self._interp = {} @property - def s11data(self) -> List[Datapoint]: + def s11(self) -> List[Datapoint]: return self.s("11") - @s11data.setter - def s11data(self, value: List[Datapoint]): + @s11.setter + def s11(self, value: List[Datapoint]): self.sdata[0] = value @property - def s12data(self) -> List[Datapoint]: + def s12(self) -> List[Datapoint]: return self.s("12") - @s12data.setter - def s12data(self, value: List[Datapoint]): + @s12.setter + def s12(self, value: List[Datapoint]): self.sdata[2] = value @property - def s21data(self) -> List[Datapoint]: + def s21(self) -> List[Datapoint]: return self.s("21") - @s21data.setter - def s21data(self, value: List[Datapoint]): + @s21.setter + def s21(self, value: List[Datapoint]): self.sdata[1] = value @property - def s22data(self) -> List[Datapoint]: + def s22(self) -> List[Datapoint]: return self.s("22") - @s22data.setter - def s22data(self, value: List[Datapoint]): + @s22.setter + def s22(self, value: List[Datapoint]): self.sdata[3] = value @property @@ -149,6 +149,9 @@ def s_freq(self, name: str, freq: int) -> Datapoint: float(self._interp[name]["real"](freq)), float(self._interp[name]["imag"](freq))) + def swap(self): + self.sdata = [self.s22, self.s12, self.s21, self.s11] + def min_freq(self) -> int: return self.s("11")[0].freq @@ -279,7 +282,7 @@ def saves(self, nr_params: int = 1) -> str: assert nr_params in (1, 4) ts_str = "# HZ S RI R 50\n" - for i, dp_s11 in enumerate(self.s11data): + for i, dp_s11 in enumerate(self.s11): ts_str += f"{dp_s11.freq} {dp_s11.re} {dp_s11.im}" for j in range(1, nr_params): dp = self.sdata[j][i] diff --git a/NanoVNASaver/Version.py b/NanoVNASaver/Version.py index 6fff5300..1df6233f 100644 --- a/NanoVNASaver/Version.py +++ b/NanoVNASaver/Version.py @@ -1,7 +1,7 @@ # NanoVNASaver # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/About.py b/NanoVNASaver/Windows/About.py index 40cb45f4..6b3b5d5d 100644 --- a/NanoVNASaver/Windows/About.py +++ b/NanoVNASaver/Windows/About.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/AnalysisWindow.py b/NanoVNASaver/Windows/AnalysisWindow.py index 04b69950..a453727f 100644 --- a/NanoVNASaver/Windows/AnalysisWindow.py +++ b/NanoVNASaver/Windows/AnalysisWindow.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/Bands.py b/NanoVNASaver/Windows/Bands.py index 89db8097..c878842b 100644 --- a/NanoVNASaver/Windows/Bands.py +++ b/NanoVNASaver/Windows/Bands.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/CalibrationSettings.py b/NanoVNASaver/Windows/CalibrationSettings.py index 7db5ded4..f990cb73 100644 --- a/NanoVNASaver/Windows/CalibrationSettings.py +++ b/NanoVNASaver/Windows/CalibrationSettings.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -71,11 +71,13 @@ def __init__(self, app: QtWidgets.QWidget): self.cal_label[label_name] = QtWidgets.QLabel("Uncalibrated") cal_btn[label_name] = QtWidgets.QPushButton( label_name.capitalize()) + cal_btn[label_name].setMinimumHeight(20) cal_btn[label_name].clicked.connect(partial(self.manual_save, label_name)) calibration_control_layout.addRow( cal_btn[label_name], self.cal_label[label_name]) self.input_offset_delay = QtWidgets.QDoubleSpinBox() + self.input_offset_delay.setMinimumHeight(20) self.input_offset_delay.setValue(0) self.input_offset_delay.setSuffix(" ps") self.input_offset_delay.setAlignment(QtCore.Qt.AlignRight) @@ -86,15 +88,18 @@ def __init__(self, app: QtWidgets.QWidget): calibration_control_layout.addRow("Offset delay", self.input_offset_delay) self.btn_automatic = QtWidgets.QPushButton("Calibration assistant") + self.btn_automatic.setMinimumHeight(20) calibration_control_layout.addRow(self.btn_automatic) self.btn_automatic.clicked.connect(self.automaticCalibration) apply_reset_layout = QtWidgets.QHBoxLayout() btn_apply = QtWidgets.QPushButton("Apply") + btn_apply.setMinimumHeight(20) btn_apply.clicked.connect(self.calculate) btn_reset = QtWidgets.QPushButton("Reset") + btn_reset.setMinimumHeight(20) btn_reset.clicked.connect(self.reset) apply_reset_layout.addWidget(btn_apply) @@ -114,8 +119,10 @@ def __init__(self, app: QtWidgets.QWidget): file_box = QtWidgets.QGroupBox("Files") file_layout = QtWidgets.QFormLayout(file_box) btn_save_file = QtWidgets.QPushButton("Save calibration") + btn_save_file.setMinimumHeight(20) btn_save_file.clicked.connect(lambda: self.saveCalibration()) btn_load_file = QtWidgets.QPushButton("Load calibration") + btn_load_file.setMinimumHeight(20) btn_load_file.clicked.connect(lambda: self.loadCalibration()) save_load_layout = QtWidgets.QHBoxLayout() @@ -137,10 +144,15 @@ def __init__(self, app: QtWidgets.QWidget): cal_short_form = QtWidgets.QFormLayout(self.cal_short_box) self.cal_short_box.setDisabled(True) self.short_l0_input = QtWidgets.QLineEdit("0") + self.short_l0_input.setMinimumHeight(20) self.short_l1_input = QtWidgets.QLineEdit("0") + self.short_l1_input.setMinimumHeight(20) self.short_l2_input = QtWidgets.QLineEdit("0") + self.short_l2_input.setMinimumHeight(20) self.short_l3_input = QtWidgets.QLineEdit("0") + self.short_l3_input.setMinimumHeight(20) self.short_length = QtWidgets.QLineEdit("0") + self.short_length.setMinimumHeight(20) cal_short_form.addRow("L0 (H(e-12))", self.short_l0_input) cal_short_form.addRow("L1 (H(e-24))", self.short_l1_input) cal_short_form.addRow("L2 (H(e-33))", self.short_l2_input) @@ -151,10 +163,15 @@ def __init__(self, app: QtWidgets.QWidget): cal_open_form = QtWidgets.QFormLayout(self.cal_open_box) self.cal_open_box.setDisabled(True) self.open_c0_input = QtWidgets.QLineEdit("50") + self.open_c0_input.setMinimumHeight(20) self.open_c1_input = QtWidgets.QLineEdit("0") + self.open_c1_input.setMinimumHeight(20) self.open_c2_input = QtWidgets.QLineEdit("0") + self.open_c2_input.setMinimumHeight(20) self.open_c3_input = QtWidgets.QLineEdit("0") + self.open_c3_input.setMinimumHeight(20) self.open_length = QtWidgets.QLineEdit("0") + self.open_length.setMinimumHeight(20) cal_open_form.addRow("C0 (F(e-15))", self.open_c0_input) cal_open_form.addRow("C1 (F(e-27))", self.open_c1_input) cal_open_form.addRow("C2 (F(e-36))", self.open_c2_input) @@ -165,19 +182,24 @@ def __init__(self, app: QtWidgets.QWidget): cal_load_form = QtWidgets.QFormLayout(self.cal_load_box) self.cal_load_box.setDisabled(True) self.load_resistance = QtWidgets.QLineEdit("50") + self.load_resistance.setMinimumHeight(20) self.load_inductance = QtWidgets.QLineEdit("0") - # self.load_capacitance = QtWidgets.QLineEdit("0") - # self.load_capacitance.setDisabled(True) # Not yet implemented + self.load_inductance.setMinimumHeight(20) + self.load_capacitance = QtWidgets.QLineEdit("0") + self.load_capacitance.setMinimumHeight(20) + #self.load_capacitance.setDisabled(True) # Not yet implemented self.load_length = QtWidgets.QLineEdit("0") + self.load_length.setMinimumHeight(20) cal_load_form.addRow("Resistance (\N{OHM SIGN})", self.load_resistance) cal_load_form.addRow("Inductance (H(e-12))", self.load_inductance) - # cal_load_form.addRow("Capacitance (F(e-12))", self.load_capacitance) + cal_load_form.addRow("Capacitance (F(e-15))", self.load_capacitance) cal_load_form.addRow("Offset Delay (ps)", self.load_length) self.cal_through_box = QtWidgets.QGroupBox("Through") cal_through_form = QtWidgets.QFormLayout(self.cal_through_box) self.cal_through_box.setDisabled(True) self.through_length = QtWidgets.QLineEdit("0") + self.through_length.setMinimumHeight(20) cal_through_form.addRow("Offset Delay (ps)", self.through_length) cal_standard_layout.addWidget(self.cal_short_box) @@ -190,14 +212,18 @@ def __init__(self, app: QtWidgets.QWidget): self.cal_standard_save_box.setDisabled(True) self.cal_standard_save_selector = QtWidgets.QComboBox() + self.cal_standard_save_selector.setMinimumHeight(20) self.listCalibrationStandards() cal_standard_save_layout.addWidget(self.cal_standard_save_selector) cal_standard_save_button_layout = QtWidgets.QHBoxLayout() btn_save_standard = QtWidgets.QPushButton("Save") + btn_save_standard.setMinimumHeight(20) btn_save_standard.clicked.connect(self.saveCalibrationStandard) btn_load_standard = QtWidgets.QPushButton("Load") + btn_load_standard.setMinimumHeight(20) btn_load_standard.clicked.connect(self.loadCalibrationStandard) btn_delete_standard = QtWidgets.QPushButton("Delete") + btn_delete_standard.setMinimumHeight(20) btn_delete_standard.clicked.connect(self.deleteCalibrationStandard) cal_standard_save_button_layout.addWidget(btn_load_standard) cal_standard_save_button_layout.addWidget(btn_save_standard) @@ -231,11 +257,11 @@ def checkExpertUser(self): def cal_save(self, name: str): if name in ("through", "isolation"): - self.app.calibration.insert(name, self.app.data21) + self.app.calibration.insert(name, self.app.data.s21) else: - self.app.calibration.insert(name, self.app.data11) + self.app.calibration.insert(name, self.app.data.s11) self.cal_label[name].setText( - _format_cal_label(len(self.app.data11))) + _format_cal_label(len(self.app.data.s11))) def manual_save(self, name: str): if self.checkExpertUser(): @@ -287,7 +313,7 @@ def saveCalibrationStandard(self): self.app.settings.setValue("LoadR", self.load_resistance.text()) self.app.settings.setValue("LoadL", self.load_inductance.text()) - # self.app.settings.setValue("LoadC", self.load_capacitance.text()) + self.app.settings.setValue("LoadC", self.load_capacitance.text()) self.app.settings.setValue("LoadDelay", self.load_length.text()) self.app.settings.setValue("ThroughDelay", self.through_length.text()) @@ -322,7 +348,7 @@ def loadCalibrationStandard(self): self.load_resistance.setText(str(self.app.settings.value("LoadR", 50))) self.load_inductance.setText(str(self.app.settings.value("LoadL", 0))) - # self.load_capacitance.setText(str(self.app.settings.value("LoadC", 0))) + self.load_capacitance.setText(str(self.app.settings.value("LoadC", 0))) self.load_length.setText(str(self.app.settings.value("LoadDelay", 0))) self.through_length.setText(str(self.app.settings.value("ThroughDelay", 0))) @@ -485,8 +511,6 @@ def calculate(self): try: self.app.calibration.openC0 = self.getFloatValue( self.open_c0_input.text())/10**15 - if self.app.calibration.openC0 == 0: - raise ValueError("C0 cannot be 0.") self.app.calibration.openC1 = self.getFloatValue( self.open_c1_input.text())/10**27 self.app.calibration.openC2 = self.getFloatValue( @@ -505,9 +529,9 @@ def calculate(self): self.app.calibration.loadR = self.getFloatValue( self.load_resistance.text()) self.app.calibration.loadL = self.getFloatValue( - self.load_inductance.text())/10**12 - # self.app.calibration.loadC = self.getFloatValue( - # self.load_capacitance.text()) / 10 ** 12 + self.load_inductance.text()) / 10**12 + self.app.calibration.loadC = self.getFloatValue( + self.load_capacitance.text()) / 10 ** 15 self.app.calibration.loadLength = self.getFloatValue( self.load_length.text())/10**12 self.app.calibration.useIdealLoad = False diff --git a/NanoVNASaver/Windows/DeviceSettings.py b/NanoVNASaver/Windows/DeviceSettings.py index 8fc7c709..6a0ffb78 100644 --- a/NanoVNASaver/Windows/DeviceSettings.py +++ b/NanoVNASaver/Windows/DeviceSettings.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/DisplaySettings.py b/NanoVNASaver/Windows/DisplaySettings.py index 33a7285d..e6819066 100644 --- a/NanoVNASaver/Windows/DisplaySettings.py +++ b/NanoVNASaver/Windows/DisplaySettings.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,9 +21,12 @@ from PyQt5 import QtWidgets, QtCore, QtGui +from NanoVNASaver.Charts.Chart import ( + Chart, ChartColors, ChartMarker, ChartMarkerConfig) from NanoVNASaver.Windows.Bands import BandsWindow from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow from NanoVNASaver.Marker import Marker + logger = logging.getLogger(__name__) @@ -34,8 +37,9 @@ def __init__(self, app: QtWidgets.QWidget): self.app = app self.setWindowTitle("Display settings") self.setWindowIcon(self.app.icon) - + self.marker_cfg = ChartMarkerConfig() self.marker_window = MarkerSettingsWindow(self.app) + self.callback_params = {} QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -75,61 +79,10 @@ def __init__(self, app: QtWidgets.QWidget): self.dark_mode_option.stateChanged.connect(self.changeDarkMode) display_options_layout.addRow(self.dark_mode_option, dark_mode_label) - self.btnColorPicker = QtWidgets.QPushButton("█") - self.btnColorPicker.setFixedWidth(20) - self.sweepColor = self.app.settings.value( - "SweepColor", defaultValue=QtGui.QColor(160, 140, 20, 128), - type=QtGui.QColor) - self.setSweepColor(self.sweepColor) - self.btnColorPicker.clicked.connect(lambda: self.setSweepColor( - QtWidgets.QColorDialog.getColor( - self.sweepColor, options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - display_options_layout.addRow("Sweep color", self.btnColorPicker) - - self.btnSecondaryColorPicker = QtWidgets.QPushButton("█") - self.btnSecondaryColorPicker.setFixedWidth(20) - self.secondarySweepColor = self.app.settings.value("SecondarySweepColor", - defaultValue=QtGui.QColor( - 20, 160, 140, 128), - type=QtGui.QColor) - self.setSecondarySweepColor(self.secondarySweepColor) - self.btnSecondaryColorPicker.clicked.connect(lambda: self.setSecondarySweepColor( - QtWidgets.QColorDialog.getColor(self.secondarySweepColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - display_options_layout.addRow("Second sweep color", self.btnSecondaryColorPicker) - - self.btnReferenceColorPicker = QtWidgets.QPushButton("█") - self.btnReferenceColorPicker.setFixedWidth(20) - self.referenceColor = self.app.settings.value( - "ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 48), - type=QtGui.QColor) - self.setReferenceColor(self.referenceColor) - self.btnReferenceColorPicker.clicked.connect(lambda: self.setReferenceColor( - QtWidgets.QColorDialog.getColor( - self.referenceColor, options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - display_options_layout.addRow("Reference color", self.btnReferenceColorPicker) - - self.btnSecondaryReferenceColorPicker = QtWidgets.QPushButton("█") - self.btnSecondaryReferenceColorPicker.setFixedWidth(20) - self.secondaryReferenceColor = self.app.settings.value( - "SecondaryReferenceColor", - defaultValue=QtGui.QColor(0, 0, 255, 48), - type=QtGui.QColor) - self.setSecondaryReferenceColor(self.secondaryReferenceColor) - self.btnSecondaryReferenceColorPicker.clicked.connect( - lambda: self.setSecondaryReferenceColor( - QtWidgets.QColorDialog.getColor( - self.secondaryReferenceColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - display_options_layout.addRow( - "Second reference color", - self.btnSecondaryReferenceColorPicker) + self.trace_colors(display_options_layout) self.pointSizeInput = QtWidgets.QSpinBox() + self.pointSizeInput.setMinimumHeight(20) pointsize = self.app.settings.value("PointSize", 2, int) self.pointSizeInput.setValue(pointsize) self.changePointSize(pointsize) @@ -141,6 +94,7 @@ def __init__(self, app: QtWidgets.QWidget): display_options_layout.addRow("Point size", self.pointSizeInput) self.lineThicknessInput = QtWidgets.QSpinBox() + self.lineThicknessInput.setMinimumHeight(20) linethickness = self.app.settings.value("LineThickness", 1, int) self.lineThicknessInput.setValue(linethickness) self.changeLineThickness(linethickness) @@ -152,16 +106,16 @@ def __init__(self, app: QtWidgets.QWidget): display_options_layout.addRow("Line thickness", self.lineThicknessInput) self.markerSizeInput = QtWidgets.QSpinBox() + self.markerSizeInput.setMinimumHeight(20) markersize = self.app.settings.value("MarkerSize", 6, int) self.markerSizeInput.setValue(markersize) - self.changeMarkerSize(markersize) + self.markerSizeInput.setMinimum(4) self.markerSizeInput.setMinimum(4) self.markerSizeInput.setMaximum(20) self.markerSizeInput.setSingleStep(2) self.markerSizeInput.setSuffix(" px") self.markerSizeInput.setAlignment(QtCore.Qt.AlignRight) self.markerSizeInput.valueChanged.connect(self.changeMarkerSize) - self.markerSizeInput.editingFinished.connect(self.validateMarkerSize) display_options_layout.addRow("Marker size", self.markerSizeInput) self.show_marker_number_option = QtWidgets.QCheckBox("Show marker numbers") @@ -195,42 +149,10 @@ def __init__(self, app: QtWidgets.QWidget): color_options_layout = QtWidgets.QFormLayout(color_options_box) self.use_custom_colors = QtWidgets.QCheckBox("Use custom chart colors") - self.use_custom_colors.stateChanged.connect(self.changeCustomColors) + self.use_custom_colors.stateChanged.connect(self.updateCharts) color_options_layout.addRow(self.use_custom_colors) - self.btn_background_picker = QtWidgets.QPushButton("█") - self.btn_background_picker.setFixedWidth(20) - self.btn_background_picker.clicked.connect( - lambda: self.setColor( - "background", - QtWidgets.QColorDialog.getColor( - self.backgroundColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - color_options_layout.addRow( - "Chart background", self.btn_background_picker) - - self.btn_foreground_picker = QtWidgets.QPushButton("█") - self.btn_foreground_picker.setFixedWidth(20) - self.btn_foreground_picker.clicked.connect( - lambda: self.setColor( - "foreground", - QtWidgets.QColorDialog.getColor( - self.foregroundColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - color_options_layout.addRow("Chart foreground", self.btn_foreground_picker) - - self.btn_text_picker = QtWidgets.QPushButton("█") - self.btn_text_picker.setFixedWidth(20) - self.btn_text_picker.clicked.connect( - lambda: self.setColor( - "text", - QtWidgets.QColorDialog.getColor( - self.textColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - color_options_layout.addRow("Chart text", self.btn_text_picker) + self.custom_colors(color_options_layout) right_layout = QtWidgets.QVBoxLayout() layout.addLayout(right_layout) @@ -238,6 +160,7 @@ def __init__(self, app: QtWidgets.QWidget): font_options_box = QtWidgets.QGroupBox("Font") font_options_layout = QtWidgets.QFormLayout(font_options_box) self.font_dropdown = QtWidgets.QComboBox() + self.font_dropdown.setMinimumHeight(20) self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"]) font_size = self.app.settings.value("FontSize", defaultValue="8", @@ -255,19 +178,12 @@ def __init__(self, app: QtWidgets.QWidget): self.show_bands.setChecked(self.app.bands.enabled) self.show_bands.stateChanged.connect(lambda: self.setShowBands(self.show_bands.isChecked())) bands_layout.addRow(self.show_bands) - - self.btn_bands_picker = QtWidgets.QPushButton("█") - self.btn_bands_picker.setFixedWidth(20) - self.btn_bands_picker.clicked.connect( - lambda: self.setColor( - "bands", - QtWidgets.QColorDialog.getColor( - self.bandsColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - bands_layout.addRow("Chart bands", self.btn_bands_picker) + bands_layout.addRow( + "Chart bands", + self.color_picker("BandsColor", "bands")) self.btn_manage_bands = QtWidgets.QPushButton("Manage bands") + self.btn_manage_bands.setMinimumHeight(20) self.bandsWindow = BandsWindow(self.app) self.btn_manage_bands.clicked.connect(self.displayBandsWindow) @@ -286,18 +202,11 @@ def __init__(self, app: QtWidgets.QWidget): # Single values from the .ini become floats rather than lists. Convert them. self.vswrMarkers = [self.vswrMarkers] - self.btn_vswr_picker = QtWidgets.QPushButton("█") - self.btn_vswr_picker.setFixedWidth(20) - self.btn_vswr_picker.clicked.connect( - lambda: self.setColor( - "vswr", - QtWidgets.QColorDialog.getColor( - self.vswrColor, - options=QtWidgets.QColorDialog.ShowAlphaChannel))) - - vswr_marker_layout.addRow("VSWR Markers", self.btn_vswr_picker) + vswr_marker_layout.addRow( + "VSWR Markers",self.color_picker("VSWRColor", "swr")) self.vswr_marker_dropdown = QtWidgets.QComboBox() + self.vswr_marker_dropdown.setMinimumHeight(20) vswr_marker_layout.addRow(self.vswr_marker_dropdown) if len(self.vswrMarkers) == 0: @@ -310,7 +219,9 @@ def __init__(self, app: QtWidgets.QWidget): self.vswr_marker_dropdown.setCurrentIndex(0) btn_add_vswr_marker = QtWidgets.QPushButton("Add ...") + btn_add_vswr_marker.setMinimumHeight(20) btn_remove_vswr_marker = QtWidgets.QPushButton("Remove") + btn_remove_vswr_marker.setMinimumHeight(20) vswr_marker_btn_layout = QtWidgets.QHBoxLayout() vswr_marker_btn_layout.addWidget(btn_add_vswr_marker) vswr_marker_btn_layout.addWidget(btn_remove_vswr_marker) @@ -323,10 +234,13 @@ def __init__(self, app: QtWidgets.QWidget): markers_layout = QtWidgets.QFormLayout(markers_box) btn_add_marker = QtWidgets.QPushButton("Add") + btn_add_marker.setMinimumHeight(30) btn_add_marker.clicked.connect(self.addMarker) self.btn_remove_marker = QtWidgets.QPushButton("Remove") + self.btn_remove_marker.setMinimumHeight(30) self.btn_remove_marker.clicked.connect(self.removeMarker) btn_marker_settings = QtWidgets.QPushButton("Settings ...") + btn_marker_settings.setMinimumHeight(30) btn_marker_settings.clicked.connect(self.displayMarkerWindow) marker_btn_layout = QtWidgets.QHBoxLayout() @@ -355,6 +269,7 @@ def __init__(self, app: QtWidgets.QWidget): selections.append("None") chart00_selection = QtWidgets.QComboBox() + chart00_selection.setMinimumHeight(30) chart00_selection.addItems(selections) chart00 = self.app.settings.value("Chart00", "S11 Smith Chart") if chart00_selection.findText(chart00) > -1: @@ -366,6 +281,7 @@ def __init__(self, app: QtWidgets.QWidget): charts_layout.addWidget(chart00_selection, 0, 0) chart01_selection = QtWidgets.QComboBox() + chart01_selection.setMinimumHeight(30) chart01_selection.addItems(selections) chart01 = self.app.settings.value("Chart01", "S11 Return Loss") if chart01_selection.findText(chart01) > -1: @@ -377,6 +293,7 @@ def __init__(self, app: QtWidgets.QWidget): charts_layout.addWidget(chart01_selection, 0, 1) chart02_selection = QtWidgets.QComboBox() + chart02_selection.setMinimumHeight(30) chart02_selection.addItems(selections) chart02 = self.app.settings.value("Chart02", "None") if chart02_selection.findText(chart02) > -1: @@ -388,6 +305,7 @@ def __init__(self, app: QtWidgets.QWidget): charts_layout.addWidget(chart02_selection, 0, 2) chart10_selection = QtWidgets.QComboBox() + chart10_selection.setMinimumHeight(30) chart10_selection.addItems(selections) chart10 = self.app.settings.value("Chart10", "S21 Polar Plot") if chart10_selection.findText(chart10) > -1: @@ -399,6 +317,7 @@ def __init__(self, app: QtWidgets.QWidget): charts_layout.addWidget(chart10_selection, 1, 0) chart11_selection = QtWidgets.QComboBox() + chart11_selection.setMinimumHeight(30) chart11_selection.addItems(selections) chart11 = self.app.settings.value("Chart11", "S21 Gain") if chart11_selection.findText(chart11) > -1: @@ -410,6 +329,7 @@ def __init__(self, app: QtWidgets.QWidget): charts_layout.addWidget(chart11_selection, 1, 1) chart12_selection = QtWidgets.QComboBox() + chart12_selection.setMinimumHeight(30) chart12_selection.addItems(selections) chart12 = self.app.settings.value("Chart12", "None") if chart12_selection.findText(chart12) > -1: @@ -427,21 +347,21 @@ def __init__(self, app: QtWidgets.QWidget): self.changeChart(1, 1, chart11_selection.currentText()) self.changeChart(1, 2, chart12_selection.currentText()) - self.backgroundColor = self.app.settings.value( - "BackgroundColor", defaultValue=QtGui.QColor("white"), + Chart.color.background = self.app.settings.value( + "BackgroundColor", defaultValue=ChartColors.background, type=QtGui.QColor) - self.foregroundColor = self.app.settings.value( - "ForegroundColor", defaultValue=QtGui.QColor("lightgray"), + Chart.color.foreground = self.app.settings.value( + "ForegroundColor", defaultValue=ChartColors.foreground, type=QtGui.QColor) - self.textColor = self.app.settings.value( - "TextColor", defaultValue=QtGui.QColor("black"), + Chart.color.text = self.app.settings.value( + "TextColor", defaultValue=ChartColors.text, type=QtGui.QColor) self.bandsColor = self.app.settings.value( - "BandsColor", defaultValue=QtGui.QColor(128, 128, 128, 48), + "BandsColor", defaultValue=ChartColors.bands, type=QtGui.QColor) - self.app.bands.color = self.bandsColor - self.vswrColor = self.app.settings.value( - "VSWRColor", defaultValue=QtGui.QColor(192, 0, 0, 128), + self.app.bands.color = Chart.color.bands + Chart.color.swr = self.app.settings.value( + "VSWRColor", defaultValue=ChartColors.swr, type=QtGui.QColor) self.dark_mode_option.setChecked( @@ -449,41 +369,15 @@ def __init__(self, app: QtWidgets.QWidget): self.show_lines_option.setChecked( self.app.settings.value("ShowLines", False, bool)) self.show_marker_number_option.setChecked( - self.app.settings.value("ShowMarkerNumbers", False, bool)) + self.app.settings.value("ShowMarkerNumbers", ChartMarkerConfig.draw_label, bool)) self.filled_marker_option.setChecked( - self.app.settings.value("FilledMarkers", False, bool)) + self.app.settings.value("FilledMarkers", ChartMarkerConfig.fill, bool)) if self.app.settings.value("UseCustomColors", defaultValue=False, type=bool): self.dark_mode_option.setDisabled(True) self.dark_mode_option.setChecked(False) self.use_custom_colors.setChecked(True) - else: - self.btn_background_picker.setDisabled(True) - self.btn_foreground_picker.setDisabled(True) - self.btn_text_picker.setDisabled(True) - - self.changeCustomColors() # Update all the colours of all the charts - - p = self.btn_background_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, self.backgroundColor) - self.btn_background_picker.setPalette(p) - - p = self.btn_foreground_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, self.foregroundColor) - self.btn_foreground_picker.setPalette(p) - - p = self.btn_text_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, self.textColor) - self.btn_text_picker.setPalette(p) - - p = self.btn_bands_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, self.bandsColor) - self.btn_bands_picker.setPalette(p) - - p = self.btn_vswr_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, self.vswrColor) - self.btn_vswr_picker.setPalette(p) left_layout.addWidget(display_options_box) left_layout.addWidget(charts_box) @@ -495,6 +389,42 @@ def __init__(self, app: QtWidgets.QWidget): right_layout.addWidget(bands_box) right_layout.addWidget(vswr_marker_box) right_layout.addStretch(1) + self.update() + + def trace_colors(self, layout: QtWidgets.QLayout): + for setting, name, attr in ( + ('SweepColor', 'Sweep color', 'sweep'), + ('SecondarySweepColor', 'Second sweep color', 'sweep_secondary'), + ('ReferenceColor', 'Reference color', 'reference'), + ('SecondaryReferenceColor', + 'Second reference color', 'reference_secondary'), + ): + cp = self.color_picker(setting, attr) + layout.addRow(name, cp) + + def custom_colors(self, layout: QtWidgets.QLayout): + for setting, name, attr in ( + ('BackgroundColor', 'Chart background', 'background'), + ('ForegroundColor', 'Chart foreground', 'foreground'), + ('TextColor', 'Chart text', 'text'), + ): + cp = self.color_picker(setting, attr) + layout.addRow(name, cp) + + def color_picker(self, setting: str, attr: str) -> QtWidgets.QPushButton: + cp = QtWidgets.QPushButton("█") + cp.setFixedWidth(20) + cp.setMinimumHeight(20) + default = getattr(Chart.color, attr) + color = self.app.settings.value( + setting, defaultValue=default, type=QtGui.QColor) + setattr(Chart.color, attr, color) + self.callback_params[cp] = (setting, attr) + cp.clicked.connect(self.setColor) + p = cp.palette() + p.setColor(QtGui.QPalette.ButtonText, getattr(Chart.color, attr)) + cp.setPalette(p) + return cp def changeChart(self, x, y, chart): found = None @@ -524,7 +454,7 @@ def changeReturnLoss(self): for m in self.app.markers: m.returnloss_is_positive = state - m.updateLabels(self.app.data11, self.app.data21) + m.updateLabels(self.app.data.s11, self.app.data.s21) self.marker_window.exampleMarker.returnloss_is_positive = state self.marker_window.updateMarker() self.app.charts["s11"]["log_mag"].isInverted = state @@ -539,20 +469,20 @@ def changeShowLines(self): def changeShowMarkerNumber(self): state = self.show_marker_number_option.isChecked() self.app.settings.setValue("ShowMarkerNumbers", state) - for c in self.app.subscribing_charts: - c.setDrawMarkerNumbers(state) + ChartMarker.cfg.draw_label = state + self.updateCharts() def changeFilledMarkers(self): state = self.filled_marker_option.isChecked() self.app.settings.setValue("FilledMarkers", state) - for c in self.app.subscribing_charts: - c.setFilledMarkers(state) + ChartMarker.cfg.fill = state + self.updateCharts() def changeMarkerAtTip(self): state = self.marker_at_tip.isChecked() self.app.settings.setValue("MarkerAtTip", state) - for c in self.app.subscribing_charts: - c.setMarkerAtTip(state) + ChartMarker.cfg.at_tip = state + self.updateCharts() def changePointSize(self, size: int): self.app.settings.setValue("PointSize", size) @@ -565,131 +495,49 @@ def changeLineThickness(self, size: int): c.setLineThickness(size) def changeMarkerSize(self, size: int): - if size % 2 == 0: - self.app.settings.setValue("MarkerSize", size) - for c in self.app.subscribing_charts: - c.setMarkerSize(int(size / 2)) - - def validateMarkerSize(self): - size = self.markerSizeInput.value() - if size % 2 != 0: - self.markerSizeInput.setValue(size + 1) + self.app.settings.setValue("MarkerSize", size) + ChartMarker.cfg.size = size + self.markerSizeInput.setValue(size) + self.updateCharts() def changeDarkMode(self): state = self.dark_mode_option.isChecked() self.app.settings.setValue("DarkMode", state) + Chart.color.foreground = QtGui.QColor(QtCore.Qt.lightGray) if state: - for c in self.app.subscribing_charts: - c.setBackgroundColor(QtGui.QColor(QtCore.Qt.black)) - c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray)) - c.setTextColor(QtGui.QColor(QtCore.Qt.white)) - c.setSWRColor(self.vswrColor) - else: - for c in self.app.subscribing_charts: - c.setBackgroundColor(QtGui.QColor(QtCore.Qt.white)) - c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray)) - c.setTextColor(QtGui.QColor(QtCore.Qt.black)) - c.setSWRColor(self.vswrColor) - - def changeCustomColors(self): - self.app.settings.setValue("UseCustomColors", self.use_custom_colors.isChecked()) - if self.use_custom_colors.isChecked(): - self.dark_mode_option.setDisabled(True) - self.dark_mode_option.setChecked(False) - self.btn_background_picker.setDisabled(False) - self.btn_foreground_picker.setDisabled(False) - self.btn_text_picker.setDisabled(False) - for c in self.app.subscribing_charts: - c.setBackgroundColor(self.backgroundColor) - c.setForegroundColor(self.foregroundColor) - c.setTextColor(self.textColor) - c.setSWRColor(self.vswrColor) + Chart.color.background = QtGui.QColor(QtCore.Qt.black) + Chart.color.text = QtGui.QColor(QtCore.Qt.white) + Chart.color.swr = Chart.color.swr else: - self.dark_mode_option.setDisabled(False) - self.btn_background_picker.setDisabled(True) - self.btn_foreground_picker.setDisabled(True) - self.btn_text_picker.setDisabled(True) - self.changeDarkMode() # Reset to the default colors depending on Dark Mode setting - - def setColor(self, name: str, color: QtGui.QColor): - if name == "background": - p = self.btn_background_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btn_background_picker.setPalette(p) - self.backgroundColor = color - self.app.settings.setValue("BackgroundColor", color) - elif name == "foreground": - p = self.btn_foreground_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btn_foreground_picker.setPalette(p) - self.foregroundColor = color - self.app.settings.setValue("ForegroundColor", color) - elif name == "text": - p = self.btn_text_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btn_text_picker.setPalette(p) - self.textColor = color - self.app.settings.setValue("TextColor", color) - elif name == "bands": - p = self.btn_bands_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btn_bands_picker.setPalette(p) - self.bandsColor = color - self.app.settings.setValue("BandsColor", color) - self.app.bands.setColor(color) - elif name == "vswr": - p = self.btn_vswr_picker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btn_vswr_picker.setPalette(p) - self.vswrColor = color - self.app.settings.setValue("VSWRColor", color) - self.changeCustomColors() - - def setSweepColor(self, color: QtGui.QColor): - if color.isValid(): - self.sweepColor = color - p = self.btnColorPicker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btnColorPicker.setPalette(p) - self.app.settings.setValue("SweepColor", color) - self.app.settings.sync() - for c in self.app.subscribing_charts: - c.setSweepColor(color) - - def setSecondarySweepColor(self, color: QtGui.QColor): - if color.isValid(): - self.secondarySweepColor = color - p = self.btnSecondaryColorPicker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btnSecondaryColorPicker.setPalette(p) - self.app.settings.setValue("SecondarySweepColor", color) - self.app.settings.sync() - for c in self.app.subscribing_charts: - c.setSecondarySweepColor(color) - - def setReferenceColor(self, color): - if color.isValid(): - self.referenceColor = color - p = self.btnReferenceColorPicker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btnReferenceColorPicker.setPalette(p) - self.app.settings.setValue("ReferenceColor", color) - self.app.settings.sync() - - for c in self.app.subscribing_charts: - c.setReferenceColor(color) - - def setSecondaryReferenceColor(self, color): - if color.isValid(): - self.secondaryReferenceColor = color - p = self.btnSecondaryReferenceColorPicker.palette() - p.setColor(QtGui.QPalette.ButtonText, color) - self.btnSecondaryReferenceColorPicker.setPalette(p) - self.app.settings.setValue("SecondaryReferenceColor", color) - self.app.settings.sync() - - for c in self.app.subscribing_charts: - c.setSecondaryReferenceColor(color) + Chart.color.background = QtGui.QColor(QtCore.Qt.white) + Chart.color.text = QtGui.QColor(QtCore.Qt.black) + Chart.color.swr = Chart.color.swr + self.updateCharts() + + def changeSetting(self, setting: str, value: str): + logger.debug("Setting %s: %s", setting, value) + self.app.settings.setValue(setting, value) + self.app.settings.sync() + self.updateCharts() + + def setColor(self): + sender = self.sender() + logger.debug("Sender %s", sender) + setting, attr = self.callback_params[sender] + logger.debug("Setting: %s Attribute: %s", setting, attr) + + color = getattr(Chart.color, attr) + color = QtWidgets.QColorDialog.getColor( + color, options=QtWidgets.QColorDialog.ShowAlphaChannel) + + if not color.isValid(): + logger.info("Invalid color") + return + + palette = sender.palette() + palette.setColor(QtGui.QPalette.ButtonText, color) + sender.setPalette(palette) + self.changeSetting(setting, color) def setShowBands(self, show_bands): self.app.bands.enabled = show_bands @@ -776,3 +624,7 @@ def removeVSWRMarker(self): self.app.settings.setValue("VSWRMarkers", self.vswrMarkers) for c in self.app.s11charts: c.removeSWRMarker(value) + + def updateCharts(self): + for c in self.app.subscribing_charts: + c.update() diff --git a/NanoVNASaver/Windows/Files.py b/NanoVNASaver/Windows/Files.py new file mode 100644 index 00000000..28c37e44 --- /dev/null +++ b/NanoVNASaver/Windows/Files.py @@ -0,0 +1,129 @@ +# NanoVNASaver +# +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019, 2020 Rune B. Broberg +# Copyright (C) 2020,2021 NanoVNA-Saver Authors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import logging + +from PyQt5 import QtWidgets, QtCore +from NanoVNASaver.Touchstone import Touchstone +from NanoVNASaver.RFTools import Datapoint + +logger = logging.getLogger(__name__) + +class FilesWindow(QtWidgets.QWidget): + def __init__(self, app: QtWidgets.QWidget): + super().__init__() + self.app = app + + self.setWindowTitle("Files") + self.setWindowIcon(self.app.icon) + self.setMinimumWidth(200) + QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) + file_window_layout = QtWidgets.QVBoxLayout() + self.setLayout(file_window_layout) + + load_file_control_box = QtWidgets.QGroupBox("Import file") + load_file_control_box.setMaximumWidth(300) + load_file_control_layout = QtWidgets.QFormLayout(load_file_control_box) + + btn_load_sweep = QtWidgets.QPushButton("Load as sweep") + btn_load_sweep.clicked.connect(self.loadSweepFile) + btn_load_reference = QtWidgets.QPushButton("Load reference") + btn_load_reference.clicked.connect(self.loadReferenceFile) + load_file_control_layout.addRow(btn_load_sweep) + load_file_control_layout.addRow(btn_load_reference) + + file_window_layout.addWidget(load_file_control_box) + + save_file_control_box = QtWidgets.QGroupBox("Export file") + save_file_control_box.setMaximumWidth(300) + save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box) + + btn_export_file = QtWidgets.QPushButton("Save 1-Port file (S1P)") + btn_export_file.clicked.connect(lambda: self.exportFile(1)) + save_file_control_layout.addRow(btn_export_file) + + btn_export_file = QtWidgets.QPushButton("Save 2-Port file (S2P)") + btn_export_file.clicked.connect(lambda: self.exportFile(4)) + save_file_control_layout.addRow(btn_export_file) + + file_window_layout.addWidget(save_file_control_box) + + btn_open_file_window = QtWidgets.QPushButton("Files ...") + btn_open_file_window.clicked.connect( + lambda: self.app.display_window("file")) + + def exportFile(self, nr_params: int = 1): + if len(self.app.data.s11) == 0: + QtWidgets.QMessageBox.warning( + self, "No data to save", "There is no data to save.") + return + if nr_params > 2 and len(self.app.data.s21) == 0: + QtWidgets.QMessageBox.warning( + self, "No S21 data to save", "There is no S21 data to save.") + return + + filedialog = QtWidgets.QFileDialog(self) + if nr_params == 1: + filedialog.setDefaultSuffix("s1p") + filedialog.setNameFilter( + "Touchstone 1-Port Files (*.s1p);;All files (*.*)") + else: + filedialog.setDefaultSuffix("s2p") + filedialog.setNameFilter( + "Touchstone 2-Port Files (*.s2p);;All files (*.*)") + filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + selected = filedialog.exec() + if not selected: + return + filename = filedialog.selectedFiles()[0] + if filename == "": + logger.debug("No file name selected.") + return + + ts = Touchstone(filename) + ts.sdata[0] = self.app.data.s11 + if nr_params > 1: + ts.sdata[1] = self.app.data.s21 + for dp in self.app.data.s11: + ts.sdata[2].append(Datapoint(dp.freq, 0, 0)) + ts.sdata[3].append(Datapoint(dp.freq, 0, 0)) + try: + ts.save(nr_params) + except IOError as e: + logger.exception("Error during file export: %s", e) + return + + def loadReferenceFile(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName( + filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + if filename != "": + self.app.resetReference() + t = Touchstone(filename) + t.load() + self.app.setReference(t.s11, t.s21, filename) + + def loadSweepFile(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName( + filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + if filename != "": + self.app.data.s11 = [] + self.app.data.s21 = [] + t = Touchstone(filename) + t.load() + self.app.saveData(t.s11, t.s21, filename) + self.app.dataUpdated() diff --git a/NanoVNASaver/Windows/MarkerSettings.py b/NanoVNASaver/Windows/MarkerSettings.py index 82c762d0..a00b3063 100644 --- a/NanoVNASaver/Windows/MarkerSettings.py +++ b/NanoVNASaver/Windows/MarkerSettings.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/Screenshot.py b/NanoVNASaver/Windows/Screenshot.py index 218eb983..bc8e09e3 100644 --- a/NanoVNASaver/Windows/Screenshot.py +++ b/NanoVNASaver/Windows/Screenshot.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/NanoVNASaver/Windows/SweepSettings.py b/NanoVNASaver/Windows/SweepSettings.py index 710d519c..9e6e9011 100644 --- a/NanoVNASaver/Windows/SweepSettings.py +++ b/NanoVNASaver/Windows/SweepSettings.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ def title_box(self): layout = QtWidgets.QFormLayout(box) input_title = QtWidgets.QLineEdit(self.app.sweep.properties.name) + input_title.setMinimumHeight(20) input_title.editingFinished.connect( lambda: self.update_title(input_title.text())) layout.addRow(input_title) @@ -66,26 +67,33 @@ def settings_box(self) -> 'QtWidgets.QWidget': layout = QtWidgets.QFormLayout(box) # Sweep Mode + sweep_btn_layout = QtWidgets.QHBoxLayout() + radio_button = QtWidgets.QRadioButton("Single sweep") + radio_button.setMinimumHeight(20) radio_button.setChecked( self.app.sweep.properties.mode == SweepMode.SINGLE) radio_button.clicked.connect( lambda: self.update_mode(SweepMode.SINGLE)) - layout.addWidget(radio_button) + sweep_btn_layout.addWidget(radio_button) radio_button = QtWidgets.QRadioButton("Continous sweep") + radio_button.setMinimumHeight(20) radio_button.setChecked( self.app.sweep.properties.mode == SweepMode.CONTINOUS) radio_button.clicked.connect( lambda: self.update_mode(SweepMode.CONTINOUS)) - layout.addWidget(radio_button) + sweep_btn_layout.addWidget(radio_button) radio_button = QtWidgets.QRadioButton("Averaged sweep") + radio_button.setMinimumHeight(20) radio_button.setChecked( self.app.sweep.properties.mode == SweepMode.AVERAGE) radio_button.clicked.connect( lambda: self.update_mode(SweepMode.AVERAGE)) - layout.addWidget(radio_button) + sweep_btn_layout.addWidget(radio_button) + + layout.addRow(sweep_btn_layout) # Log sweep label = QtWidgets.QLabel( @@ -94,23 +102,28 @@ def settings_box(self) -> 'QtWidgets.QWidget': " amount of datapoints and many segments. Step display in" " SweepControl cannot reflect this currently.") label.setWordWrap(True) + label.setMinimumSize(600,70) layout.addRow(label) checkbox = QtWidgets.QCheckBox("Logarithmic sweep") + checkbox.setMinimumHeight(20) checkbox.setCheckState(self.app.sweep.properties.logarithmic) checkbox.toggled.connect( lambda: self.update_logarithmic(checkbox.isChecked())) - layout.addWidget(checkbox) + layout.addRow(checkbox) # Averaging label = QtWidgets.QLabel( "Averaging allows discarding outlying samples to get better" " averages. Common values are 3/0, 5/2, 9/4 and 25/6.") label.setWordWrap(True) + label.setMinimumHeight(50) layout.addRow(label) averages = QtWidgets.QLineEdit( str(self.app.sweep.properties.averages[0])) + averages.setMinimumHeight(20) truncates = QtWidgets.QLineEdit( str(self.app.sweep.properties.averages[1])) + truncates.setMinimumHeight(20) averages.editingFinished.connect( lambda: self.update_averaging(averages, truncates)) truncates.editingFinished.connect( @@ -124,9 +137,11 @@ def settings_box(self) -> 'QtWidgets.QWidget': " attenuator in line with the S21 input (CH1) here you can" " specify it.") label.setWordWrap(True) + label.setMinimumHeight(50) layout.addRow(label) input_att = QtWidgets.QLineEdit(str(self.app.s21att)) + input_att.setMinimumHeight(20) input_att.editingFinished.connect( lambda: self.update_attenuator(input_att)) layout.addRow("Attenuator in port CH1 (s21) in dB", input_att) @@ -135,26 +150,29 @@ def settings_box(self) -> 'QtWidgets.QWidget': def sweep_box(self) -> 'QtWidgets.QWidget': box = QtWidgets.QGroupBox("Sweep band") layout = QtWidgets.QFormLayout(box) + sweep_pad_layout = QtWidgets.QHBoxLayout() self.band_list = QtWidgets.QComboBox() + self.band_list.setMinimumHeight(20) self.band_list.setModel(self.app.bands) # pylint: disable=unnecessary-lambda self.band_list.currentIndexChanged.connect(lambda: self.update_band()) layout.addRow("Select band", self.band_list) - for raw_label, btn_label, value in (("Pad band limits", "None", 0), - ("", "10%", 10), - ("", "25%", 25), - ("", "100%", 100),): + sweep_pad_layout.addWidget(QtWidgets.QLabel("Pad band limits:")) + for btn_label, value in (("None", 0), ("10%", 10), ("25%", 25), ("100%", 100),): radio_button = QtWidgets.QRadioButton(btn_label) + radio_button.setMinimumHeight(20) radio_button.setChecked(self.padding == value) radio_button.clicked.connect(partial(self.update_padding, value)) - layout.addRow(raw_label, radio_button) + sweep_pad_layout.addWidget(radio_button) + layout.addRow(sweep_pad_layout) self.band_label = QtWidgets.QLabel() layout.addRow(self.band_label) btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep") + btn_set_band_sweep.setMinimumHeight(20) btn_set_band_sweep.clicked.connect(lambda: self.update_band(True)) layout.addRow(btn_set_band_sweep) return box @@ -252,4 +270,4 @@ def update_title(self, title: str = ""): def update_tx_power(self, freq_range, power_desc): logger.debug("update_tx_power(%r)", power_desc) with self.app.sweep.lock: - self.app.set_tx_power(freq_range, power_desc) + self.app.vna.setTXPower(freq_range, power_desc) diff --git a/NanoVNASaver/Windows/TDR.py b/NanoVNASaver/Windows/TDR.py index 1102c348..7006dbe8 100644 --- a/NanoVNASaver/Windows/TDR.py +++ b/NanoVNASaver/Windows/TDR.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020, 2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -62,7 +62,14 @@ def __init__(self, app: QtWidgets.QWidget): self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66) self.tdr_velocity_dropdown.addItem("RG-8/U Foam (Belden 8214) (0.78)", 0.78) self.tdr_velocity_dropdown.addItem("RG-8/U (Belden 9913) (0.84)", 0.84) + # Next one added by EKZ, KC3KZ, from measurement of actual cable + self.tdr_velocity_dropdown.addItem("RG-8/U (Shireen RFC®400 Low Loss) (0.86)", 0.86) self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82) + # Next three added by EKZ, KC3KZ, from measurement of actual cable + self.tdr_velocity_dropdown.addItem("RG-8X (Wireman \"Super 8\" CQ106) (0.81)", 0.81) + self.tdr_velocity_dropdown.addItem("RG-8X (Wireman \"MINI-8 Lo-Loss\" CQ118) (0.82)", 0.82) + self.tdr_velocity_dropdown.addItem( + "RG-58 (Wireman \"CQ 58 Lo-Loss Flex\" CQ129FF) (0.79)", 0.79) self.tdr_velocity_dropdown.addItem( "RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84) self.tdr_velocity_dropdown.addItem("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66) @@ -107,7 +114,7 @@ def updateTDR(self): # TODO: Let the user select whether to use high or low resolution TDR? FFT_POINTS = 2**14 - if len(self.app.data11) < 2: + if len(self.app.data.s11) < 2: return if self.tdr_velocity_dropdown.currentData() == -1: @@ -121,17 +128,17 @@ def updateTDR(self): except ValueError: return - step_size = self.app.data11[1].freq - self.app.data11[0].freq + step_size = self.app.data.s11[1].freq - self.app.data.s11[0].freq if step_size == 0: self.tdr_result_label.setText("") logger.info("Cannot compute cable length at 0 span") return s11 = [] - for d in self.app.data11: + for d in self.app.data.s11: s11.append(np.complex(d.re, d.im)) - window = np.blackman(len(self.app.data11)) + window = np.blackman(len(self.app.data.s11)) windowed_s11 = window * s11 self.td = np.abs(np.fft.ifft(windowed_s11, FFT_POINTS)) diff --git a/NanoVNASaver/Windows/__init__.py b/NanoVNASaver/Windows/__init__.py index 73d5ab2f..73e51386 100644 --- a/NanoVNASaver/Windows/__init__.py +++ b/NanoVNASaver/Windows/__init__.py @@ -4,6 +4,7 @@ from .CalibrationSettings import CalibrationWindow from .DeviceSettings import DeviceSettingsWindow from .DisplaySettings import DisplaySettingsWindow +from .Files import FilesWindow from .MarkerSettings import MarkerSettingsWindow from .Screenshot import ScreenshotWindow from .SweepSettings import SweepSettingsWindow diff --git a/NanoVNASaver/__main__.py b/NanoVNASaver/__main__.py index 19e9a90f..817e1ba4 100644 --- a/NanoVNASaver/__main__.py +++ b/NanoVNASaver/__main__.py @@ -4,7 +4,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +34,7 @@ from NanoVNASaver.About import VERSION, INFO from NanoVNASaver.NanoVNASaver import NanoVNASaver +from NanoVNASaver.Touchstone import Touchstone def main(): @@ -44,6 +45,10 @@ def main(): help="Set loglevel to debug") parser.add_argument("-D", "--debug-file", help="File to write debug logging output to") + parser.add_argument("-f", "--file", + help="Touchstone file to load as sweep for off device usage") + parser.add_argument("-r", "--ref-file", + help="Touchstone file to load as reference for off device usage") parser.add_argument("--version", action="version", version=f"NanoVNASaver {VERSION}") args = parser.parse_args() @@ -79,10 +84,22 @@ def main(): app = QtWidgets.QApplication(sys.argv) window = NanoVNASaver() window.show() + + if args.file: + t = Touchstone(args.file) + t.load() + window.saveData(t.s11, t.s21, args.file) + window.dataUpdated() + if args.ref_file: + t = Touchstone(args.ref_file) + t.load() + window.setReference(t.s11, t.s21, args.ref_file) + window.dataUpdated() try: app.exec_() except BaseException as exc: logger.exception("%s", exc) + raise exc if __name__ == '__main__': main() diff --git a/README.md b/README.md index 6824534a..7e902f2f 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,22 @@ sweep frequency spans in segments to gain more than 101 data points, and generally display and analyze the resulting data. - Copyright 2019, 2020 Rune B. Broberg -- Copyright 2020 NanoVNA-Saver Authors +- Copyright 2020, 2021 NanoVNA-Saver Authors Latest Changes -------------- +### Changes in v0.3.10 + +- Default Band ranges for 5 and 9cm +- Layout should fit on smaller screens +- Fixed fixed axis settings +- Show VNA type in port selector +- Recognise tinySA (screenshot only) +- Some more cables in TDR +- Reference plane applied after calibration +- Calibration fixes by DiSlord + ### Changes in v0.3.9 - TX Power on V2 @@ -24,13 +35,6 @@ Latest Changes - Magnitude Z Chart - VSWR Chart improvements -### Changes in v0.3.8 - -- Allow editing of bands above 2.4GHz -- Restore column layout on start -- Support for Nanovna-F V2 -- Fixes a crash with S21 hack - Introduction ------------ @@ -69,13 +73,14 @@ Running the application The software was written in Python on Windows, using Pycharm, and the modules PyQT5, numpy, scipy and pyserial. +Main development is currently done on Linux (Ubuntu 21.04) ### Binary releases You can find 64bit binary releases for Windows, Linux and MacOS under -Versions older than Windows 7 are not known to work. +Versions older than Windows 7 are not known to work. #### Windows 7 @@ -102,11 +107,14 @@ The downloadable executable runs directly, and requires no installation. NanoVNASaver -#### Ubuntu 18.04 & 19.04 +#### Ubuntu 20.04 -1. Install python3.7 and pip +1. Install python3.8 and pip - sudo apt install python3.7 python3-pip + sudo apt install python3.8 python3-pip + python3 -m venv ~/.venv_nano + . ~/.venv_nano/bin/activate + pip install -U pip 2. Clone repo and cd into the directory @@ -115,8 +123,7 @@ The downloadable executable runs directly, and requires no installation. 3. Update pip and run the pip installation - python3.7 -m pip install -U pip - python3.7 -m pip install . + python3 -m pip install . (You may need to install the additional packages python3-distutils, python3-setuptools and python3-wheel for this command to work on some @@ -124,7 +131,9 @@ The downloadable executable runs directly, and requires no installation. 4. Once completed run with the following command - python3.7 nanovna-saver.py + . ~/.venv_nano/bin/activate + python3 nanovna-saver.py + #### MacPorts @@ -144,19 +153,19 @@ Via a MacPorts distribution maintained by @ra1nb0w. 1. Install Homebrew from (This will ask for your password) /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - + 2. Python : brew install python -3. Pip :
+3. Pip :
Download the get-pip.py file and run it to install pip curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python3 get-pip.py -4. NanoVNASaver Installation :
+4. NanoVNASaver Installation :
clone the source code to the nanovna-saver folder git clone https://github.com/NanoVNA-Saver/nanovna-saver diff --git a/requirements.txt b/requirements.txt index 1cf3ea0f..1c49ba0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -scipy -cython -pyqt5 -pyserial -numpy +pyserial==3.5 +PyQt5==5.15.4 +numpy==1.21.1 +scipy==1.7.1 +cython==0.29.24 diff --git a/setup.cfg b/setup.cfg index f2a8d320..646e60b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,10 +3,25 @@ name = NanoVNASaver author = Rune B. Broberg license = GNU GPL V3 license_file = LICENSE -description = A multiplatform tool to save Touchstone files from the - NanoVNA, sweep frequency spans in segments to gain more - data points, and generally display and analyze the - resulting data. +description = + A multiplatform tool to save Touchstone files from the + NanoVNA, sweep frequency spans in segments to gain more + data points, and generally display and analyze the + resulting data. long_description = file: README.md url = https://github.com/NanoVNA-Saver/nanovna-saver version = attr: NanoVNASaver.About.VERSION + +[options] +packages = find_namespace: +install_requires= + pyserial==3.5 + PyQt5==5.15.4 + numpy==1.21.1 + scipy==1.7.1 + cython==0.29.24 +python_requires = >=3.8, <4 + +[options.entry_points] +console_scripts = + NanoVNASaver = NanoVNASaver.__main__:main diff --git a/setup.py b/setup.py index 86056c77..75f50e2c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,33 +16,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import sys +from setuptools import setup -if sys.version_info < (3, 7): - print("You need at least Python 3.7 for this application!") - if sys.version_info[0] < 3: - print("try running with python3 {}".format(" ".join(sys.argv))) - sys.exit(1) - -try: - from setuptools import setup, find_packages -except ImportError: - print("Could not find setuptools") - print("Try installing them with pip install setuptools") - sys.exit(1) - -setup( - packages=find_packages(exclude=["test", ]), - entry_points={ - 'console_scripts': [ - 'NanoVNASaver = NanoVNASaver.__main__:main' - ], - }, - install_requires=[ - 'pyserial', - 'PyQt5', - 'numpy', - 'scipy', - 'cython', - ], -) +setup() diff --git a/test/__init__.py b/test/__init__.py index f9173b28..e9dffab6 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/data/ferrit_1.s1p b/test/data/ferrit_1.s1p new file mode 100644 index 00000000..a1e82b4d --- /dev/null +++ b/test/data/ferrit_1.s1p @@ -0,0 +1,1011 @@ +# HZ S RI R 50 +50000 -1.0067842098182076 0.0011153017447806102 +248166 -1.006174928728769 0.00442840579792926 +446332 -1.0054439612523787 0.0077487273768852725 +644498 -1.0051549662655994 0.011149517613500231 +842664 -1.0050452360708364 0.014481014389026327 +1040830 -1.0043882428768678 0.017227297350967206 +1238996 -1.0037918310691745 0.020354932787176084 +1437162 -1.0034141170468516 0.02325987454782127 +1635328 -1.002876109656067 0.025990732983152822 +1833494 -1.0020559524877648 0.029194786788658375 +2031660 -1.00176838980764 0.03213195914966064 +2229826 -1.0013439220260976 0.03467199034934392 +2427992 -1.0007786288604381 0.03777020385543433 +2626158 -1.0004468217453402 0.04017374368900063 +2824324 -0.9997982833527883 0.043162483565451874 +3022490 -0.9992418600237188 0.04568584083685219 +3220656 -0.9984741818329218 0.04859816904997604 +3418822 -0.9977206561394698 0.05125952742965386 +3616988 -0.9971904467236362 0.05403877920402576 +3815154 -0.9966180177851902 0.056630485917703394 +4013320 -0.995834219065029 0.0593084159556062 +4211486 -0.9950376645435575 0.061983646195783615 +4409652 -0.9940868145151401 0.06441032507087249 +4607818 -0.9933964223238081 0.06714042051651717 +4805984 -0.9929049501080238 0.06960290959433812 +5004150 -0.9919489151940186 0.07222780069291514 +5202316 -0.9909244067864482 0.07472432558356851 +5400482 -0.9903416584194137 0.0772191342059417 +5598648 -0.9894742888969045 0.07986170715805861 +5796814 -0.9886201270399215 0.08222425433067682 +5994980 -0.987659421461512 0.08445997530147394 +6193146 -0.9869386417633438 0.08720052144993128 +6391312 -0.9859102716906557 0.08970662974597833 +6589478 -0.9848381345608361 0.09210987395236654 +6787644 -0.984550026274278 0.09359798838738188 +6985810 -0.9833702086908046 0.0961213631945155 +7183976 -0.9827490470288655 0.09896777364573935 +7382142 -0.981700224166055 0.10116965223861381 +7580308 -0.9809946599431056 0.10343621396570513 +7778474 -0.9798102963405603 0.1058522271955124 +7976640 -0.979117828235555 0.1082789037353792 +8174806 -0.9781903851534418 0.11009982705129343 +8372972 -0.9770134859468044 0.11251438669723268 +8571138 -0.9761715134567278 0.11466580976696307 +8769304 -0.9751041793122773 0.11692821093872065 +8967470 -0.9740652566402871 0.11876885388940353 +9165636 -0.9731686938270698 0.12114745447289928 +9363802 -0.9723853549799725 0.12334488050532212 +9561968 -0.9714493091685139 0.12534871005675216 +9760134 -0.9704436777588432 0.1273418078672132 +9958300 -0.9698079814231773 0.12954084870142762 +10156466 -0.967976971798632 0.13164867317648446 +10354632 -0.9670545323802334 0.13356791075056315 +10552798 -0.9660461691241133 0.13577543278935073 +10750964 -0.9651076466159975 0.1379756063282972 +10949130 -0.9640930099431616 0.14017972448644114 +11147296 -0.9630667960340629 0.14207472612317798 +11345462 -0.9621520693950755 0.14394388120022455 +11543628 -0.961148303683412 0.14589672886932037 +11741794 -0.9600295222945815 0.14788564654788577 +11939960 -0.9590942934634199 0.1498017109375031 +12138126 -0.9581103455640871 0.1519097956659754 +12336292 -0.9570039922793734 0.15356668874059393 +12534458 -0.955924248031724 0.15568790154092102 +12732624 -0.9551055939835781 0.15739706129127182 +12930790 -0.9541470120958915 0.15931123620164145 +13128956 -0.9532388298114454 0.16132665832661935 +13327122 -0.9521117632684364 0.16317279963585063 +13525288 -0.9511213977487271 0.1650208859216832 +13723454 -0.9500729168297845 0.1669760144141851 +13921620 -0.9488018735868131 0.16884185968989465 +14119786 -0.9480608771693675 0.17045066481798085 +14317952 -0.9468319529246911 0.17230661432279473 +14516118 -0.9457750514684694 0.17426703538006946 +14714284 -0.944843107639742 0.17581728384101158 +14912450 -0.9438868549662003 0.17780360827119143 +15110616 -0.9428002474851552 0.17944202901537606 +15308782 -0.9413602948497594 0.18129937926727888 +15506948 -0.9406043598007816 0.1831110835241129 +15705114 -0.939531744001078 0.18464612861912305 +15903280 -0.9385099661963262 0.18636077191358758 +16101446 -0.9374401696013832 0.1881195121673736 +16299612 -0.9366588377036488 0.18981717689813293 +16497778 -0.9355822351106935 0.19123319659663018 +16695944 -0.9345018205636292 0.19332504949025395 +16894110 -0.9330866897864665 0.19461956935224467 +17092276 -0.9322753797183345 0.19641077756599704 +17290442 -0.9314426314102576 0.1979891038290788 +17488608 -0.930379039924932 0.19986480357564965 +17686774 -0.9296394143488722 0.20162864621358062 +17884940 -0.9283287052955264 0.2031231020370391 +18083106 -0.927091383377227 0.20441080188077884 +18281272 -0.926177608471449 0.2064746946202179 +18479438 -0.9251670407673479 0.20800552117500065 +18677604 -0.9239941661653075 0.20944648620705394 +18875770 -0.923145070005502 0.21104656045784723 +19073936 -0.9217865151937135 0.21256243639497213 +19272102 -0.9208561294258383 0.2139120959354267 +19470268 -0.9198835793392668 0.21551580951518073 +19668434 -0.9187472147569371 0.21701172294894344 +19866600 -0.9176074718083468 0.21880840926719022 +20064766 -0.9166004026011741 0.2204187078578255 +20262932 -0.9155462474470061 0.2219481371999832 +20461098 -0.9142534236961943 0.22316654321623372 +20659264 -0.9132949169926747 0.22464679953692732 +20857430 -0.9124799620221374 0.22648404052717702 +21055596 -0.9115660814760211 0.22774841296495946 +21253762 -0.9103499118889637 0.22924099525612676 +21451928 -0.9089356058479902 0.23092041108623035 +21650094 -0.9080848529655329 0.2321565462173064 +21848260 -0.9070984332393975 0.2337383111644792 +22046426 -0.9058740915675512 0.23517068617187112 +22244592 -0.9049919581664149 0.2364685962219484 +22442758 -0.9039568244006437 0.23771552482791045 +22640924 -0.9029617427621202 0.23934749122085083 +22839090 -0.9017370336355649 0.24070315025090508 +23037256 -0.9008043489891565 0.24200105743039937 +23235422 -0.8999735168822897 0.24349372226486066 +23433588 -0.8983984226311618 0.2447833497394113 +23631754 -0.8980395586687081 0.24618258511208024 +23829920 -0.8967618441093705 0.2474401275952972 +24028086 -0.8956164185097459 0.24878819900652316 +24226252 -0.8946196186085239 0.2504257615086617 +24424418 -0.8932607663878046 0.2514850758981898 +24622584 -0.8926278199363855 0.25301783603568406 +24820750 -0.8917556326270796 0.2542404777806109 +25018916 -0.8907418743693375 0.2555941930268214 +25217082 -0.889459991841326 0.2570338110089938 +25415248 -0.8886629381696207 0.258200962457762 +25613414 -0.887638424895949 0.2593713827495892 +25811580 -0.886429822760192 0.26096673443003837 +26009746 -0.8853046534764225 0.26209056700363625 +26207912 -0.8843700991216942 0.2631346055509418 +26406078 -0.8835240701674437 0.2648469720496766 +26604244 -0.8822121987782577 0.2655134462314514 +26802410 -0.8813754938310732 0.26723021454605106 +27000576 -0.8804429366358041 0.2688982923155748 +27198742 -0.8793842206541188 0.2693534248026331 +27396908 -0.8786026218898448 0.2705911911783534 +27595074 -0.8775714336336032 0.272263593126237 +27793240 -0.876472292015038 0.27313412548182303 +27991406 -0.8754684101353883 0.27462609812031596 +28189572 -0.8741117837697518 0.27551181320506163 +28387738 -0.8733767878306009 0.2767798185189747 +28585904 -0.8725463836954229 0.2780635212198391 +28784070 -0.871321324819546 0.27924943773169075 +28982236 -0.8706526706011414 0.2798414309789514 +29180402 -0.8698783834285405 0.28184374945467006 +29378568 -0.8686095786398663 0.28252944953335685 +29576734 -0.8677662773515005 0.283548264817558 +29774900 -0.8663575690445269 0.284885705569434 +29973066 -0.865193436713664 0.28567716959534284 +30171232 -0.8644839592234874 0.28703202697661034 +30369398 -0.863643663383751 0.28843853513469325 +30567564 -0.8625173651062521 0.2893546705239978 +30765730 -0.8614453003444347 0.2905675528250923 +30963896 -0.8606660543459355 0.2918606391766511 +31162062 -0.8597303485492499 0.29255927048210084 +31360228 -0.8587990025284072 0.29400981100178725 +31558394 -0.8576454117497913 0.2949848798864381 +31756560 -0.8565408260669212 0.2963546992780333 +31954726 -0.8556526152466318 0.2969139376919538 +32152892 -0.8546340182896094 0.2984172210449983 +32351058 -0.8537808923287259 0.2993298191230459 +32549224 -0.8526780661632113 0.3002192209665124 +32747390 -0.8518329382863373 0.30150059596031387 +32945556 -0.8507322510364121 0.3023742017499433 +33143722 -0.8498960937416468 0.3034235797309205 +33341888 -0.8492251103801843 0.3046409964123543 +33540054 -0.8477492757988856 0.30569793853910976 +33738220 -0.8468070237658533 0.30714294956857224 +33936386 -0.8460356489216412 0.3078928077256031 +34134552 -0.8448928176402594 0.30900401867238103 +34332718 -0.8443035423921404 0.30972217285151665 +34530884 -0.8431058728115677 0.31073751636016833 +34729050 -0.8420718299602861 0.3120003094517087 +34927216 -0.841432390032838 0.31281419428018536 +35125382 -0.840337624839326 0.31410741502451556 +35323548 -0.8394145734817079 0.31480587310344577 +35521714 -0.8383591082153516 0.3158635326072553 +35719880 -0.8372603524692601 0.31665754245859307 +35918046 -0.8364555231168818 0.3174399050827135 +36116212 -0.8357510049676001 0.3189237098381592 +36314378 -0.8346458666652832 0.3199522935365079 +36512544 -0.8336646028712648 0.32107678373297605 +36710710 -0.8327999828089462 0.321564371496974 +36908876 -0.8317112951675103 0.3221185784018417 +37107042 -0.8309079791394892 0.3239811959448411 +37305208 -0.8300046358436488 0.32464765499252257 +37503374 -0.8295545354892664 0.32557239265837595 +37701540 -0.8281332670080382 0.3266595158819125 +37899706 -0.8273896733161065 0.3273043475352658 +38097872 -0.8262906865884024 0.32841303964438384 +38296038 -0.8255379921604364 0.32900090860663067 +38494204 -0.8242353547724388 0.3300456443681177 +38692370 -0.823595207008422 0.33080533394441997 +38890536 -0.8227742939131598 0.3322714173243776 +39088702 -0.8219673551254358 0.3328855049479158 +39286868 -0.8211805705787962 0.33387070123472334 +39485034 -0.8200573898364841 0.33511542643005054 +39683200 -0.8189866621255957 0.3357515021603277 +39881366 -0.8182541092992759 0.33683639148964856 +40079532 -0.817430554891509 0.33744125591379037 +40277698 -0.8165702947551676 0.33828770687631293 +40475864 -0.8155886948112112 0.33948529206784345 +40674030 -0.8145273614218946 0.33999059082953836 +40872196 -0.8135816663416201 0.3408307416015232 +41070362 -0.8128276351709028 0.3420128529352481 +41268528 -0.8119968006060437 0.34300530100711635 +41466694 -0.8113449705660916 0.343736848019489 +41664860 -0.8100915085783266 0.34433033607569297 +41863026 -0.8094167651754376 0.34530749032817604 +42061192 -0.8085466976584907 0.3462602225863128 +42259358 -0.8076736055750685 0.3471584064137924 +42457524 -0.8065856563027525 0.34759393474464395 +42655690 -0.8057384806813437 0.34869018318538825 +42853856 -0.8052396936565194 0.3495183070884364 +43052022 -0.8044276424563785 0.3505451370871938 +43250188 -0.8032230759483038 0.3515508482612722 +43448354 -0.8026919693962659 0.3520621778475138 +43646520 -0.801904169056342 0.35244596904631775 +43844686 -0.8007482337077697 0.3533512577118196 +44042852 -0.7999237315013898 0.35461674576093244 +44241018 -0.7991370254538763 0.3551138305167819 +44439184 -0.7984136099053074 0.35580870829310224 +44637350 -0.7974390146414406 0.35700416584175326 +44835516 -0.7969868972713983 0.35783781644094076 +45033682 -0.7956216545086933 0.35843180884298637 +45231848 -0.7951907288434826 0.35914782503588333 +45430014 -0.7940815261765292 0.3599197721316027 +45628180 -0.7934814023180559 0.3610826285391673 +45826346 -0.7924431090683105 0.361916701792193 +46024512 -0.7910982146293022 0.3624931348310251 +46222678 -0.7907214350331777 0.3636195389239837 +46420844 -0.7899408712783115 0.36394880072654323 +46619010 -0.7891129510896255 0.3645639533025291 +46817176 -0.7883203634687393 0.36562997562280436 +47015342 -0.7873194306746885 0.36610733641487736 +47213508 -0.7864728972838668 0.36709624807598096 +47411674 -0.7855792423044223 0.36795442060241534 +47609840 -0.7853357874625253 0.3685689613029082 +47808006 -0.7843678546397739 0.36933277199963516 +48006172 -0.7832228583515005 0.37009000026442607 +48204338 -0.7825809548838522 0.3707621913002217 +48402504 -0.7818591107688656 0.3715264178439368 +48600670 -0.7811741590104013 0.37180826950726553 +48798836 -0.780080793409311 0.372755030461636 +48997002 -0.7795267431491626 0.37359722414215507 +49195168 -0.7785665575444365 0.374297125901011 +49393334 -0.7773246870306969 0.3752110123634278 +49591500 -0.7768484075641537 0.3756650028570994 +49789666 -0.7764139535258106 0.3764273792458343 +49987832 -0.7744502118795498 0.3764104305011052 +50185998 -0.7707908982264537 0.37429529247881604 +50384164 -0.7730922936078594 0.37724586154133305 +50582330 -0.7707562469183715 0.3767318220433405 +50780496 -0.7708885659654905 0.3778926574072053 +50978662 -0.7714891571044346 0.3797933999818348 +51176828 -0.770520290546701 0.38022780511637433 +51374994 -0.7691483836190265 0.38070266560258004 +51573160 -0.7683572152570004 0.38145165351584476 +51771326 -0.7672125937029892 0.38147498963288523 +51969492 -0.7656367964023196 0.3817477093903656 +52167658 -0.7658426535279186 0.38337584204722164 +52365824 -0.7640651713231891 0.38267619169559863 +52563990 -0.7637114912509817 0.3841071762932735 +52762156 -0.7640153068348827 0.38607231785177826 +52960322 -0.7596650927326614 0.38360297803619325 +53158488 -0.7595276303930529 0.3835399519327589 +53356654 -0.7574605184326988 0.3845386557524129 +53554820 -0.7592049332239773 0.38646964571582254 +53752986 -0.758438985410661 0.3874349156058 +53951152 -0.7579882408607237 0.38861137132820445 +54149318 -0.7589263820988276 0.3905335069914232 +54347484 -0.7572892407892087 0.3903992946551311 +54545650 -0.7549458747329352 0.38865520230041734 +54743816 -0.7545389348305513 0.38965421335395317 +54941982 -0.754169228669463 0.39057053521022145 +55140148 -0.7529738544068736 0.3906828450146439 +55338314 -0.7525216741156927 0.39127664000960544 +55536480 -0.7515347507778944 0.39235966004305156 +55734646 -0.7511687415901723 0.3931101629887206 +55932812 -0.7502018155438528 0.39374830323862536 +56130978 -0.749383233738564 0.3938909292986401 +56329144 -0.7485467564545134 0.3948984263574892 +56527310 -0.7474074772469045 0.3947119283917348 +56725476 -0.7470598086201965 0.3954204715163911 +56923642 -0.7463831764701275 0.39588176346405074 +57121808 -0.7459145821153726 0.39662612494644417 +57319974 -0.7447036853422573 0.3971686990945173 +57518140 -0.7439176401288284 0.3977886308440768 +57716306 -0.7434430807742319 0.3985275655121905 +57914472 -0.742645887024075 0.3990257718380538 +58112638 -0.7417306514193842 0.3991174825970683 +58310804 -0.7408410711945405 0.39999905062576147 +58508970 -0.7399470588290185 0.4003516895469387 +58707136 -0.7396767549317234 0.4009286050028892 +58905302 -0.7387382834672998 0.4018618771364599 +59103468 -0.7382613654956163 0.4021579486179507 +59301634 -0.7375654413865972 0.4032163873380167 +59499800 -0.7369259314748674 0.4034201684394169 +59697966 -0.7359763767481418 0.4039953630625601 +59896132 -0.7353274947971663 0.4042451032881088 +60094298 -0.7313435546576718 0.4046425556593557 +60292464 -0.7309682744445333 0.40498260397984664 +60490630 -0.7300154297892028 0.4056723060736549 +60688796 -0.7293376250857881 0.40611113801676063 +60886962 -0.728725849199044 0.4070440472749164 +61085128 -0.727920492865363 0.4076082042669511 +61283294 -0.7273397724027149 0.4082626995937004 +61481460 -0.7267025271346965 0.4087684484839439 +61679626 -0.7258277735386227 0.40909913862939185 +61877792 -0.7249186784676336 0.4097011072286138 +62075958 -0.724412281312979 0.41030959375505605 +62274124 -0.7236281870072936 0.41045358895310263 +62472290 -0.7228081666853691 0.41124965711527595 +62670456 -0.7224489127118217 0.41125086333711186 +62868622 -0.7213434806288317 0.4123224094695672 +63066788 -0.7207886071207602 0.4128992906814895 +63264954 -0.7200010769991486 0.4130437729697932 +63463120 -0.7194829935681456 0.4137474303208616 +63661286 -0.7186217605065742 0.4142297769264026 +63859452 -0.7181332164040228 0.41467483777981407 +64057618 -0.7174813364050859 0.41498123088142513 +64255784 -0.7165732925018745 0.41531160908540893 +64453950 -0.7161586074104362 0.4163020026691458 +64652116 -0.7152744900768129 0.4165678134912524 +64850282 -0.7146815158494956 0.4169558961499926 +65048448 -0.7138660472983613 0.41737371527394646 +65246614 -0.7133399810831555 0.4181500539038303 +65444780 -0.712621144210001 0.41841133790861307 +65642946 -0.7121258910532637 0.41893550746016966 +65841112 -0.7113638380343305 0.41958934610431975 +66039278 -0.7103092780767438 0.4198646959260608 +66237444 -0.7098983683219684 0.4202096812582987 +66435610 -0.7091650481142554 0.4206842809071137 +66633776 -0.7087774090113091 0.42151269584570344 +66831942 -0.708056544925192 0.42176819318712927 +67030108 -0.7075583848839757 0.4218376825656261 +67228274 -0.7068170569880725 0.42234288015956417 +67426440 -0.7062938597869833 0.4230236093751219 +67624606 -0.705565508209929 0.42338196468664774 +67822772 -0.7051313402485792 0.4240068828445301 +68020938 -0.7042383319316167 0.4243614566105464 +68219104 -0.7035058071614918 0.42469290685386857 +68417270 -0.702924728588514 0.42545544079499975 +68615436 -0.7023291486137295 0.42579892477386627 +68813602 -0.7014804926167261 0.4259489704918657 +69011768 -0.7009170178054169 0.42663056317811043 +69209934 -0.7003827183779021 0.42727077507770045 +69408100 -0.6995511791609504 0.427484470317891 +69606266 -0.6989060669176294 0.42822116142772076 +69804432 -0.6984884630843597 0.4285104010430358 +70002598 -0.6975406933313404 0.4290286977004459 +70200764 -0.6971239422256154 0.42972404636322137 +70398930 -0.6964254960634217 0.429925627746694 +70597096 -0.6960604463573461 0.43045669229394284 +70795262 -0.6955629467555031 0.43039266915017405 +70993428 -0.694485076988798 0.4311111579699656 +71191594 -0.6940804645072466 0.4318256031277793 +71389760 -0.6932089452286561 0.4320264605417869 +71587926 -0.6924652582053614 0.4323437982337504 +71786092 -0.6921905523269134 0.4325754592736454 +71984258 -0.6912170515356005 0.43331208298773793 +72182424 -0.6907158302865427 0.43371755681343743 +72380590 -0.6902478074038026 0.43387940844424105 +72578756 -0.6889167677763421 0.43462184959368055 +72776922 -0.6887004302819859 0.4349081332661876 +72975088 -0.6879842398113776 0.4356741501388379 +73173254 -0.6873629858231866 0.4356798225191158 +73371420 -0.6872001429733703 0.4363699870324563 +73569586 -0.6863208947010055 0.43664546789275066 +73767752 -0.6856416067451563 0.4369298758397648 +73965918 -0.6850133463297691 0.43748362604759966 +74164084 -0.684464311531097 0.4379881851740689 +74362250 -0.6839055729789474 0.4382852865769898 +74560416 -0.6831770657666768 0.43846150968387576 +74758582 -0.6824597662663247 0.43922400018089874 +74956748 -0.6818534266691687 0.43958053941373015 +75154914 -0.6811616277496443 0.4399769266885436 +75353080 -0.6805651982640576 0.44021655524511016 +75551246 -0.6800322879054607 0.44058690854094235 +75749412 -0.679325319952532 0.4412190050319499 +75947578 -0.678901051913593 0.4415273775260319 +76145744 -0.6779117992106786 0.4417858491171413 +76343910 -0.6774210158385806 0.44210619751273067 +76542076 -0.6768796387835402 0.4426459091092083 +76740242 -0.6764617414525529 0.4433152571145129 +76938408 -0.675857656133877 0.4436593843327578 +77136574 -0.6748409799241236 0.4437312289276521 +77334740 -0.6741721443472006 0.44442787228279035 +77532906 -0.6734840817996968 0.4446054667031962 +77731072 -0.673028097260254 0.44468768013051785 +77929238 -0.6722972806542328 0.4456433885646921 +78127404 -0.672044162748587 0.44560969275508555 +78325570 -0.6713710746623028 0.4458990149116179 +78523736 -0.6706251604364583 0.4458987252470313 +78721902 -0.6699539315767794 0.44636842880174976 +78920068 -0.6694284039472631 0.4466700159050375 +79118234 -0.6691715701125515 0.44679814950548796 +79316400 -0.6685228410964374 0.4470646722357106 +79514566 -0.6684890183220961 0.4474087062102144 +79712732 -0.6680761906949918 0.44777358104678705 +79910898 -0.6677935452267622 0.4476727106185119 +80109064 -0.6670841586233227 0.44817206893514067 +80307230 -0.6666283954941178 0.44874047972534026 +80505396 -0.6666700339090063 0.4490686018853184 +80703562 -0.6660568598451043 0.4494778700259624 +80901728 -0.6653933578931477 0.450290497156489 +81099894 -0.6652709839770297 0.4504864952328522 +81298060 -0.6649118735438916 0.45090649151775836 +81496226 -0.6639197809821653 0.4515852751233085 +81694392 -0.6634440292684904 0.4522257817006163 +81892558 -0.6630885390283017 0.45256476172231297 +82090724 -0.6623085946289428 0.4527303492403151 +82288890 -0.6618661344489934 0.4528356724359824 +82487056 -0.6607047122592 0.4537172892727244 +82685222 -0.6602569671767351 0.4533091081600843 +82883388 -0.6601634986601657 0.4536785307048919 +83081554 -0.6596887049591031 0.4535977190589028 +83279720 -0.6592464965511378 0.4540528569388933 +83477886 -0.6589829114683257 0.45476528848169595 +83676052 -0.6583454810664189 0.4546263241051149 +83874218 -0.6582497235820739 0.45508317291184786 +84072384 -0.6575086362127088 0.4555029339468835 +84270550 -0.6571117011796981 0.4556722528334468 +84468716 -0.6568636645891508 0.45640257224350755 +84666882 -0.6563600262128679 0.45657147427425426 +84865048 -0.6554926702908731 0.4565031850479298 +85063214 -0.6555914213419471 0.4571022977443253 +85261380 -0.6546786460524068 0.45723051164379236 +85459546 -0.6543366631871695 0.45777261194069496 +85657712 -0.65390016763469 0.45782260068959385 +85855878 -0.653469006233002 0.4582423960151682 +86054044 -0.6529322055950684 0.4592645952886424 +86252210 -0.6524408502068084 0.4590500665284407 +86450376 -0.6518446591308378 0.4592742422209962 +86648542 -0.6517330071988745 0.45962296966424315 +86846708 -0.6517847838656406 0.460158672819799 +87044874 -0.6509865548472552 0.4599396655060736 +87243040 -0.6500646746619909 0.4604808731242747 +87441206 -0.6499475857846898 0.4605852319718203 +87639372 -0.6491839474024073 0.46119290394438606 +87837538 -0.6487316644155428 0.4618101566442238 +88035704 -0.6484976701221442 0.4619577533238802 +88233870 -0.6480117594395981 0.462184101891592 +88432036 -0.6475279764484525 0.46232331053976433 +88630202 -0.6472458883083045 0.4625211533280293 +88828368 -0.6467485824978817 0.4630167591614821 +89026534 -0.6460518653581958 0.4630818328640295 +89224700 -0.6455133956958451 0.46390510623905973 +89422866 -0.6448644982885476 0.4638330066776778 +89621032 -0.6448840587738164 0.4638723207930118 +89819198 -0.6447172870304005 0.46457152485718395 +90017364 -0.6440716478855432 0.46481442849184057 +90215530 -0.6434898109542775 0.46479160229281735 +90413696 -0.6428673155604864 0.4653611382809977 +90611862 -0.6428611709288591 0.46553113819253683 +90810028 -0.642254100950694 0.46561519597192347 +91008194 -0.6417709039969375 0.4658091084568882 +91206360 -0.6417592603144574 0.4661908570626736 +91404526 -0.6410412721835878 0.466519673922666 +91602692 -0.6408028880155469 0.46662204400666546 +91800858 -0.6408186870187983 0.46683881114726117 +91999024 -0.6401916052991847 0.4671736153681838 +92197190 -0.6397731898722043 0.46719505051299715 +92395356 -0.6395384822264618 0.4679923328296805 +92593522 -0.6390683268980196 0.46792883736179747 +92791688 -0.6390547187350607 0.468509319481825 +92989854 -0.639094057628195 0.46835616637193916 +93188020 -0.6386035512586636 0.4695166104033484 +93386186 -0.637970408304923 0.46913535057797423 +93584352 -0.6380029973697632 0.46972003344726737 +93782518 -0.6375980739018302 0.4701303383374838 +93980684 -0.637776845094603 0.4706578590959565 +94178850 -0.6370788151562904 0.4713465227071158 +94377016 -0.6372220161403322 0.4720203806901549 +94575182 -0.6365694390704542 0.4726700539693395 +94773348 -0.6357552483982966 0.4734185928985557 +94971514 -0.6354769167250633 0.4742723725637641 +95169680 -0.6347075258414979 0.47472515282530064 +95367846 -0.6335991894586191 0.47542530156168183 +95566012 -0.6334610880274091 0.47598886697432796 +95764178 -0.6326037346346127 0.47659607469682236 +95962344 -0.6315446780060631 0.47714219505879224 +96160510 -0.6308730133133656 0.47749827083947977 +96358676 -0.6296781213946386 0.4776695413481489 +96556842 -0.6286209173700816 0.4779471046011256 +96755008 -0.6284493107947633 0.4782594942389335 +96953174 -0.6275584163872552 0.4784827419910441 +97151340 -0.6267272604814096 0.478437006501568 +97349506 -0.6266363811027783 0.4785590621748273 +97547672 -0.6251932847881864 0.4787776531767344 +97745838 -0.6243769616704046 0.4783420868188296 +97944004 -0.624008200773998 0.47893116867550284 +98142170 -0.6233315761163514 0.47863467480754895 +98340336 -0.622930183193089 0.478618742641787 +98538502 -0.6225243124383903 0.4785628529667713 +98736668 -0.6222075320433874 0.47850907707738277 +98934834 -0.621397201207573 0.4787159144187491 +99133000 -0.6208550303476552 0.47912049037732257 +99331166 -0.6208309049865064 0.4790544918546379 +99529332 -0.6203935920895504 0.47912129062757464 +99727498 -0.619722970743443 0.47936164583563384 +99925664 -0.6195757564193543 0.47907691717440376 +100123830 -0.6183407798420926 0.4796732535471175 +100321996 -0.6182808420573925 0.4795803965469518 +100520162 -0.6171570457801356 0.48015905466357484 +100718328 -0.6174058052480385 0.4798729056188302 +100916494 -0.6177997893813632 0.4801731423838648 +101114660 -0.61653174701606 0.48036798541265646 +101312826 -0.6162469373199918 0.4806642396873073 +101510992 -0.6156467331541975 0.4803223999576466 +101709158 -0.6155599952605131 0.4806500157321498 +101907324 -0.614986895360278 0.4808633867477019 +102105490 -0.6143987787544126 0.4809344899950372 +102303656 -0.6145034301389778 0.4809387358586457 +102501822 -0.6138780891451949 0.4810864176440074 +102699988 -0.6136139135229629 0.48144178061822884 +102898154 -0.6132622365560887 0.4815592663507585 +103096320 -0.6124830640614451 0.48231901111649866 +103294486 -0.6118152612693558 0.48193702468084976 +103492652 -0.611890416948505 0.48235210895211456 +103690818 -0.611537143540606 0.4821289989391377 +103888984 -0.611220002572808 0.4825928575648137 +104087150 -0.610155460640548 0.4824016452508259 +104285316 -0.6094541725268401 0.48257264060222566 +104483482 -0.6092032253390163 0.48303992732215656 +104681648 -0.6090298670113712 0.48269057899081597 +104879814 -0.6091860458296996 0.4828165440961778 +105077980 -0.608425787602816 0.4829238862032395 +105276146 -0.6079481400100649 0.4840008062501346 +105474312 -0.6077336502475178 0.4831852313480371 +105672478 -0.6071411528400177 0.4837157672833861 +105870644 -0.6065651428440506 0.48360599331025195 +106068810 -0.6061997845420667 0.48422675994259845 +106266976 -0.605979559630762 0.48449294522835085 +106465142 -0.6062200217963503 0.48476101146994544 +106663308 -0.6053868991142947 0.484551009008495 +106861474 -0.604959362218139 0.4844415589519883 +107059640 -0.6039922599178241 0.4847811384560404 +107257806 -0.6038824772843425 0.4850970104974923 +107455972 -0.6036482079778263 0.48546809500520816 +107654138 -0.6030227943980635 0.48513758703688986 +107852304 -0.603257520399321 0.48527321114674243 +108050470 -0.6024801026673421 0.4857345708138489 +108248636 -0.601778179527166 0.4853005321965566 +108446802 -0.6015866644148039 0.48573999878120766 +108644968 -0.6016809123616229 0.48550665046688 +108843134 -0.6012503622224767 0.48620341565335323 +109041300 -0.6004559846442283 0.48558215469420357 +109239466 -0.5998114329304941 0.48640418620904263 +109437632 -0.5995264955803536 0.4863713159650829 +109635798 -0.59953351298893 0.48642209428004 +109833964 -0.599139055485738 0.48692829007787114 +110032130 -0.5987355494744729 0.4870030711465913 +110230296 -0.5988294010796436 0.48762361364064793 +110428462 -0.5980654685667355 0.4871069279655053 +110626628 -0.5980244923887029 0.48757253491566405 +110824794 -0.5972748938543292 0.48711748182227504 +111022960 -0.5968370689208994 0.48709125459118224 +111221126 -0.5962702464066559 0.4877677106557131 +111419292 -0.5961249375335418 0.48805911584829015 +111617458 -0.5953965048818808 0.4875506932305826 +111815624 -0.5949667753730773 0.4874598409062234 +112013790 -0.594955101272929 0.4876693936525423 +112211956 -0.5944235992348059 0.488203107286507 +112410122 -0.593630393050059 0.4882251603120292 +112608288 -0.5942569662672487 0.48811814048456215 +112806454 -0.5933126759184295 0.4882013283478036 +113004620 -0.5933132721390955 0.4886215606660619 +113202786 -0.5930112797134635 0.48853355817425786 +113400952 -0.5920989270766649 0.487863145208888 +113599118 -0.5922863818140492 0.48858058912801916 +113797284 -0.591392672035369 0.48858431054999635 +113995450 -0.5914587889620107 0.4883134923548115 +114193616 -0.590927436236996 0.4889185036576553 +114391782 -0.5906840949956041 0.48868707294317293 +114589948 -0.5906184872251365 0.48904060569847624 +114788114 -0.5902743884667673 0.48914852298087974 +114986280 -0.5899789656060771 0.4886935774080529 +115184446 -0.5899019818545123 0.48929279762346906 +115382612 -0.5897198456170367 0.4892514610681745 +115580778 -0.5889549784971778 0.489527826485734 +115778944 -0.5890096816406007 0.48955962090829314 +115977110 -0.5881996345790527 0.4894465677779958 +116175276 -0.5882594938135409 0.48943177237030056 +116373442 -0.5882294262193875 0.4900710477799517 +116571608 -0.5872742515562575 0.4900393888292345 +116769774 -0.5872639326786874 0.4901311245232621 +116967940 -0.5873953804990886 0.48973454140679196 +117166106 -0.5870713790898984 0.4904216312742461 +117364272 -0.5866500604062839 0.4901325450840464 +117562438 -0.5864498967104699 0.4907744944885642 +117760604 -0.586155512602819 0.4907866129914429 +117958770 -0.585579399437763 0.4911399953631427 +118156936 -0.5855155058545631 0.4908458288034571 +118355102 -0.585031500118427 0.4905814504395309 +118553268 -0.5843896642654507 0.4905675925252828 +118751434 -0.5846427884855124 0.4910612796088099 +118949600 -0.5845010580278941 0.4911678175225484 +119147766 -0.5840102220531622 0.4913169444268409 +119345932 -0.5838924347785011 0.49120014849837346 +119544098 -0.5834794550536072 0.49135060460971075 +119742264 -0.582977598682201 0.4917604848889139 +119940430 -0.5831166140073434 0.49198301162403707 +120138596 -0.5827074778491068 0.49234829703123395 +120336762 -0.5824026001586855 0.49235328966560793 +120534928 -0.5816237512922889 0.4923717236896079 +120733094 -0.581414180711663 0.4917519151875198 +120931260 -0.5812669754598067 0.49226669535761836 +121129426 -0.5807986645786 0.4926179395721758 +121327592 -0.5803841682562655 0.4927484111022085 +121525758 -0.5801508792562343 0.4924651392507346 +121723924 -0.5799161759940815 0.49279589694329323 +121922090 -0.5795710074416636 0.4925115625569523 +122120256 -0.5791961014105903 0.4927907208324032 +122318422 -0.5790340669981712 0.4930425609160878 +122516588 -0.5789011521510115 0.4928118213876763 +122714754 -0.5782716966036034 0.4932006202198385 +122912920 -0.5780678700887893 0.4930134306039294 +123111086 -0.5780566791636084 0.4932833615481947 +123309252 -0.5772690245390698 0.49296884049057677 +123507418 -0.5773475219042186 0.4931226605592756 +123705584 -0.5766507846314294 0.49319350130057166 +123903750 -0.5765171075114587 0.4933412281454023 +124101916 -0.5760905385859648 0.4935634794339595 +124300082 -0.5752488039180911 0.493445212971143 +124498248 -0.5755537850199501 0.4938676470332876 +124696414 -0.57527778764158 0.4939098334275307 +124894580 -0.5752319904248395 0.4939098992513246 +125092746 -0.5745632233491486 0.49408956936352233 +125290912 -0.5747410396616348 0.4942504768272761 +125489078 -0.5747691058899445 0.49399374064980844 +125687244 -0.5744430349454287 0.49425742190146166 +125885410 -0.5737668935353437 0.4940785650302004 +126083576 -0.5733261804751885 0.49423303522927353 +126281742 -0.5737492062731365 0.49448703658061033 +126479908 -0.5729847568227814 0.49439179017801205 +126678074 -0.5723917750828152 0.4945284119279479 +126876240 -0.5723211384339555 0.4946057049839076 +127074406 -0.5726892172500444 0.49471541233076016 +127272572 -0.5720696124305398 0.4943597410904252 +127470738 -0.5714815110078629 0.49459827371463533 +127668904 -0.5717422628147759 0.49484741994744386 +127867070 -0.5715553748271487 0.4949509620021435 +128065236 -0.5709365231235051 0.4949664526539162 +128263402 -0.5707553307192557 0.49481405065982687 +128461568 -0.570522408049958 0.4948949153122092 +128659734 -0.5705122142890505 0.4952659196871134 +128857900 -0.5701276094172211 0.4949839088126708 +129056066 -0.5699938863369504 0.4950057305419786 +129254232 -0.5695980410749196 0.495253831109717 +129452398 -0.5693180226062361 0.4945103645758094 +129650564 -0.5692324600905139 0.49528890385538515 +129848730 -0.5687329558145349 0.49507918370152537 +130046896 -0.568673052022792 0.49470399798502324 +130245062 -0.5685609691304581 0.4954444221676455 +130443228 -0.5685706721773931 0.49531907886233617 +130641394 -0.5678277207166205 0.49558790773396566 +130839560 -0.5679114672566182 0.49595428552109094 +131037726 -0.5680113311460061 0.49582003521196627 +131235892 -0.5675687974879312 0.4954038082605077 +131434058 -0.5673464402093463 0.49560008106222236 +131632224 -0.5676486192895693 0.49603074429373517 +131830390 -0.5675614825735953 0.4960607786952986 +132028556 -0.5670879375164198 0.49626532960636327 +132226722 -0.5669049882689027 0.4962624773397435 +132424888 -0.5665524388585943 0.49634412582688264 +132623054 -0.5662100579421196 0.4969710360591503 +132821220 -0.5663341045261207 0.497044117308312 +133019386 -0.5658377332224578 0.49696562391917415 +133217552 -0.5653074023412655 0.49741527773490213 +133415718 -0.5647175854084606 0.49746765197517456 +133613884 -0.5649115891383082 0.49747656336483376 +133812050 -0.5641789003030057 0.4976061284223454 +134010216 -0.5637065073787833 0.49807212296063064 +134208382 -0.563532683212857 0.4981095966273183 +134406548 -0.5633552936760094 0.49809560432140265 +134604714 -0.5629896114862153 0.4978482450808561 +134802880 -0.5622568191872842 0.49789902796225405 +135001046 -0.5618275226452968 0.4981790559538528 +135199212 -0.5613929053699076 0.4980334046903302 +135397378 -0.5615815840196675 0.4979892441324557 +135595544 -0.561302489780293 0.49743902405840884 +135793710 -0.5606970605554985 0.4981186149374733 +135991876 -0.5602494003492585 0.49782271367889624 +136190042 -0.5605687690979926 0.4975531676072213 +136388208 -0.5600233001344687 0.49770114688883443 +136586374 -0.5594151059834367 0.49729734692540023 +136784540 -0.5592726083837931 0.49734345408956715 +136982706 -0.5592605363755934 0.49751289469315296 +137180872 -0.559009160850903 0.4974946571597357 +137379038 -0.5587262237325722 0.4971768298488325 +137577204 -0.5583304334290479 0.49802055119701966 +137775370 -0.5579754031984285 0.49754344197316264 +137973536 -0.5581506907385915 0.4976836011455215 +138171702 -0.557569807939111 0.497487449387719 +138369868 -0.5573758542036814 0.49708167407802667 +138568034 -0.557413895559886 0.49773345257582796 +138766200 -0.556613249798277 0.4973789529494263 +138964366 -0.5567333913572909 0.4973266380856803 +139162532 -0.5563591715039249 0.49762331388059716 +139360698 -0.5561805463135717 0.49713582648549604 +139558864 -0.5560081192295959 0.49740438302545154 +139757030 -0.5556536486064293 0.49730993493693587 +139955196 -0.5558130816560867 0.497452747479042 +140153362 -0.555674822158857 0.49738843779086506 +140351528 -0.5554169321418958 0.49714166991469577 +140549694 -0.5545919500474706 0.4972147616369226 +140747860 -0.5546666636789617 0.4969657905903836 +140946026 -0.5545906658039272 0.49683257620598437 +141144192 -0.5540609135771809 0.49739993708426716 +141342358 -0.5539989993789277 0.4971507667805723 +141540524 -0.5540277253511867 0.49713509033146497 +141738690 -0.553449054047963 0.49675141341841444 +141936856 -0.5533862487682308 0.49670170130826896 +142135022 -0.553353082871362 0.49656055551694356 +142333188 -0.5528568366094126 0.49672322923597095 +142531354 -0.5523765871321027 0.4966782881840795 +142729520 -0.552552366028916 0.49629271791353635 +142927686 -0.5520152580382367 0.49639348357531005 +143125852 -0.5520017299299252 0.4965062037815757 +143324018 -0.5518740690408437 0.49688724517705485 +143522184 -0.5513807040156683 0.49678249977201383 +143720350 -0.5512892477988934 0.49629844583972216 +143918516 -0.5513801493538806 0.49677879440217326 +144116682 -0.5510286154563795 0.4969483702147971 +144314848 -0.5510795683394184 0.49635031733702273 +144513014 -0.5506261677058878 0.49687900630137827 +144711180 -0.5501510667091125 0.4963137928803687 +144909346 -0.5502263428072923 0.4968443678478165 +145107512 -0.5497241919207809 0.49648287946128494 +145305678 -0.5497059290001767 0.49641170748792124 +145503844 -0.5501057343223146 0.4966624099465951 +145702010 -0.5492041273029967 0.49662139550081025 +145900176 -0.5490316020533877 0.49619646923682914 +146098342 -0.5487614538739329 0.49575983677054297 +146296508 -0.5488843814517332 0.4964582824127328 +146494674 -0.5488107473572137 0.49576528103141637 +146692840 -0.5482876639180191 0.49609348186288027 +146891006 -0.5485480933975097 0.49629822874146096 +147089172 -0.5483512089840422 0.4964217339284519 +147287338 -0.5478263163113849 0.49592315950512345 +147485504 -0.5472068776139443 0.4955802139983248 +147683670 -0.5477252681403598 0.4955828626489624 +147881836 -0.5469716929309877 0.49564857613490054 +148080002 -0.5473965431042292 0.4961345003451667 +148278168 -0.5468214024549286 0.4956630610134293 +148476334 -0.5462334902346915 0.49529886634136416 +148674500 -0.546859313287581 0.49593335853805026 +148872666 -0.5464167693459767 0.49610562316953677 +149070832 -0.5460169261125747 0.49550247033015216 +149268998 -0.5457438110705707 0.4957018371548848 +149467164 -0.5454546509929339 0.49503405118713995 +149665330 -0.545502368659083 0.49491826021913776 +149863496 -0.5453922448716509 0.4951189109442025 +150061662 -0.5442047941353705 0.49515085847617946 +150259828 -0.5453188101952507 0.4952244509414267 +150457994 -0.5452803070513891 0.4948414063018161 +150656160 -0.5443507430295702 0.4947990414691271 +150854326 -0.5445060090948104 0.4952772409323698 +151052492 -0.5443114186533141 0.4953906519448662 +151250658 -0.5442406152378183 0.49512636699736495 +151448824 -0.5443972149899919 0.494239311915992 +151646990 -0.5434403321271116 0.49463516038468275 +151845156 -0.5434968399604946 0.49442890199941314 +152043322 -0.5431823604272554 0.49498354452877064 +152241488 -0.5430515914078725 0.49397670067553756 +152439654 -0.5430360114538317 0.4945364875721975 +152637820 -0.5427049235333833 0.4946293965783067 +152835986 -0.5432432135180547 0.4942461532890556 +153034152 -0.5420933768588858 0.4942833651277058 +153232318 -0.5420786633464577 0.49426551170648714 +153430484 -0.5423865889926991 0.49428551054703834 +153628650 -0.5424681964116107 0.4945028088431839 +153826816 -0.5418875738744594 0.4937786678715946 +154024982 -0.5415349387658926 0.493579353590692 +154223148 -0.541519521048459 0.4936788272317445 +154421314 -0.5416983886304152 0.49431069342085976 +154619480 -0.5410698302139139 0.49392544842779207 +154817646 -0.5412028649009143 0.4940097255079638 +155015812 -0.5406182472569502 0.4932985342110125 +155213978 -0.5400628014645829 0.49368557986368317 +155412144 -0.5404804436251089 0.493911601795306 +155610310 -0.5400893696500718 0.4939844281539857 +155808476 -0.540106274142417 0.4930700330743947 +156006642 -0.5399231819125171 0.4929800746182148 +156204808 -0.5399613687031911 0.4932041425528471 +156402974 -0.5399040571084743 0.49257000060604755 +156601140 -0.5391151080291423 0.4936048780426403 +156799306 -0.5391104981073408 0.4927615239055699 +156997472 -0.5393823183379988 0.4928771004696679 +157195638 -0.538457303639529 0.4930402586900475 +157393804 -0.5391286714556148 0.49294109067662223 +157591970 -0.5387076823903757 0.4924079049004777 +157790136 -0.5388323856096546 0.4924089006135695 +157988302 -0.5383854814615648 0.49241847268088706 +158186468 -0.5387334210465654 0.4921510655744001 +158384634 -0.5376709753515191 0.4921263406911236 +158582800 -0.5383168277478237 0.4921644275791646 +158780966 -0.5379457909179439 0.49194171043890245 +158979132 -0.5373317669453495 0.4922483572422355 +159177298 -0.5373830874238802 0.4915129546406946 +159375464 -0.5374031959855252 0.4921060049604522 +159573630 -0.5372634836885778 0.49175385218850853 +159771796 -0.5369343536886118 0.4918601940882036 +159969962 -0.5365869935617983 0.4913258535277452 +160168128 -0.5362782175437121 0.4911193453375948 +160366294 -0.535950065172618 0.49168411886902835 +160564460 -0.5361624395741539 0.4910716849324317 +160762626 -0.5365314822152357 0.4906065302805193 +160960792 -0.5362161039573888 0.4914834169575682 +161158958 -0.5351942370756881 0.4915714549214734 +161357124 -0.5361063113297817 0.4911894805007131 +161555290 -0.535972116070882 0.49073667284516365 +161753456 -0.5349962713373506 0.49027138183945373 +161951622 -0.5350461959155709 0.49033696166799223 +162149788 -0.5351665215859669 0.4901341449760299 +162347954 -0.5341644906817714 0.4908064286561535 +162546120 -0.5345627832438523 0.4910353679692754 +162744286 -0.5348040000835488 0.49048925479901495 +162942452 -0.5348575173315361 0.4899353381130291 +163140618 -0.5344277135644719 0.4907838039321034 +163338784 -0.5338116069009972 0.490290017512335 +163536950 -0.5348786846845921 0.4899176580105449 +163735116 -0.5347448464591784 0.4902321123535098 +163933282 -0.5337141696430808 0.48975351650613613 +164131448 -0.5340360134273535 0.4889832644189771 +164329614 -0.5335964609451499 0.48953126939372743 +164527780 -0.5337588839904345 0.4894531631266699 +164725946 -0.5327451719252718 0.48925268010190254 +164924112 -0.5333063999183925 0.48901921267674564 +165122278 -0.5331794566598975 0.4886603796563316 +165320444 -0.5325052290342195 0.4893295325923095 +165518610 -0.5324513586016499 0.4886506306723141 +165716776 -0.532706374162579 0.488881649560147 +165914942 -0.5325540655755651 0.4882351407109197 +166113108 -0.5322959413840836 0.48799011783967017 +166311274 -0.5323699582491893 0.488540526747325 +166509440 -0.5320082377832507 0.4879036857569054 +166707606 -0.5322402573867899 0.48847508065309947 +166905772 -0.5320109315550026 0.48811726806500416 +167103938 -0.5317122464419108 0.4877904879709812 +167302104 -0.5313723404276658 0.4875433638491204 +167500270 -0.5314115275162267 0.48769809891307553 +167698436 -0.5312628082231982 0.48756376586073663 +167896602 -0.5318821209865163 0.4881125508746358 +168094768 -0.5310774746386581 0.48777268768739845 +168292934 -0.5313075551646131 0.4875050890708639 +168491100 -0.5312837896048419 0.4868194604812384 +168689266 -0.531153051709332 0.4866192539147562 +168887432 -0.5311984341991827 0.48696492403372804 +169085598 -0.530764802171508 0.4873132017444708 +169283764 -0.5302683044203927 0.48693241478100197 +169481930 -0.530399895963011 0.48671350627561805 +169680096 -0.530754006910931 0.4867397456854499 +169878262 -0.5303411363697679 0.485834045064425 +170076428 -0.5301431306060901 0.48664052717873735 +170274594 -0.5300902469313378 0.4861532109976854 +170472760 -0.5302950289523194 0.48592768533556285 +170670926 -0.5295171377170576 0.48498127024139764 +170869092 -0.5295238949251974 0.4853037255020677 +171067258 -0.5294940697781491 0.4850852551842365 +171265424 -0.5289266074175052 0.48507503501744603 +171463590 -0.5290339410772344 0.48544306322473535 +171661756 -0.5295235460996054 0.4840463692883466 +171859922 -0.5291194257209968 0.4851676626182237 +172058088 -0.5292068650306021 0.48437722564321356 +172256254 -0.5283207774642461 0.48469441222857074 +172454420 -0.5286728345934114 0.4847762082321427 +172652586 -0.528916365831792 0.4844627995082382 +172850752 -0.528728762058384 0.4843996247670509 +173048918 -0.5278934482634962 0.48369879929008813 +173247084 -0.5276944685742331 0.4832219192737278 +173445250 -0.5283746912040513 0.48342262239125544 +173643416 -0.5274029885687282 0.4832960080822912 +173841582 -0.5278545248180916 0.4830998152528022 +174039748 -0.5281062802135427 0.4835931854703849 +174237914 -0.527381668108355 0.4829113380019731 +174436080 -0.5277306640983617 0.48317034469585984 +174634246 -0.5276817038371838 0.48315298384004474 +174832412 -0.5268832376589174 0.48252070499165745 +175030578 -0.5266464097063832 0.48208522973486206 +175228744 -0.5268412675843072 0.48275205918104125 +175426910 -0.5262505029992296 0.48170140428108105 +175625076 -0.5264134267223473 0.48236689224050067 +175823242 -0.5274961222662492 0.48210400055004665 +176021408 -0.5267258162939422 0.4822765789589952 +176219574 -0.5264543973344729 0.48124023776722724 +176417740 -0.5265175123963629 0.48132040556816624 +176615906 -0.5265335143584463 0.48198552020914864 +176814072 -0.5268690585417543 0.4813363659075347 +177012238 -0.5261650718834183 0.48128523086176606 +177210404 -0.5262194104513425 0.48045719548378707 +177408570 -0.5258576930272204 0.4804262181472271 +177606736 -0.5257669191938337 0.48110916555133265 +177804902 -0.5261383718361548 0.48024045130683385 +178003068 -0.5259329311638604 0.48081966831625317 +178201234 -0.525991280600621 0.4805402302999279 +178399400 -0.5254233947369203 0.48011692235248427 +178597566 -0.5252140960686713 0.4803788301867396 +178795732 -0.5253914426509755 0.4794634242735235 +178993898 -0.5254619693634487 0.4797055280213719 +179192064 -0.5247691831835487 0.4797479728466725 +179390230 -0.5247101765038401 0.4797562716656141 +179588396 -0.5248932426256391 0.4793314843582359 +179786562 -0.5249936144038627 0.47869687974714953 +179984728 -0.5246396664988502 0.47838068992474264 +180182894 -0.525231186466234 0.47799744870868405 +180381060 -0.5246410770641243 0.47828860527989125 +180579226 -0.5246404113736808 0.478367377835382 +180777392 -0.5245056166276578 0.4787172402952823 +180975558 -0.5247375638246324 0.4780653064228829 +181173724 -0.5241360581954676 0.4777134747976045 +181371890 -0.5235170231182271 0.47800908144402093 +181570056 -0.5255547540609149 0.4773144972096866 +181768222 -0.524130102850782 0.4767243261751539 +181966388 -0.5236408542151954 0.47715144212065375 +182164554 -0.5236810720548398 0.47708123263560437 +182362720 -0.5237377835780881 0.4766628035245278 +182560886 -0.5240387778141492 0.47683346770008783 +182759052 -0.5238478847920683 0.4765019014220202 +182957218 -0.5241403860109134 0.47625216529626657 +183155384 -0.523481526344491 0.4759526643413183 +183353550 -0.5228646326385151 0.47564259826394983 +183551716 -0.5233625861581955 0.47579138312237607 +183749882 -0.523167654574928 0.4760384178660655 +183948048 -0.5223498022231744 0.4765662439470653 +184146214 -0.5222969867622275 0.4754799165658648 +184344380 -0.523035164769455 0.475053177955999 +184542546 -0.5227883497786678 0.47460934213792955 +184740712 -0.5230446483647708 0.47517169506234425 +184938878 -0.5225522785328018 0.4746366605852529 +185137044 -0.5227075891343023 0.474527895217515 +185335210 -0.5226942935939626 0.47472228266558186 +185533376 -0.5227638620665335 0.47392457167396246 +185731542 -0.522527979400426 0.47384868138855607 +185929708 -0.5221755641048343 0.4736319058633738 +186127874 -0.5225830103937726 0.4732968072465305 +186326040 -0.5221204840700419 0.47316295865683217 +186524206 -0.5215988122565453 0.47348889120855403 +186722372 -0.5219380468038364 0.4732372738053738 +186920538 -0.5217265938438538 0.47386071377227545 +187118704 -0.5217710755860814 0.4723983766022369 +187316870 -0.5212721317265826 0.47264787598066543 +187515036 -0.5213805298709981 0.4724239089449771 +187713202 -0.5215594816839564 0.472246378060787 +187911368 -0.5213711102272263 0.47197187348720565 +188109534 -0.521450160486768 0.4712801880051807 +188307700 -0.5209384544591996 0.471850736406406 +188505866 -0.5216791634403178 0.4719031202996459 +188704032 -0.5210637007930002 0.47177528150597287 +188902198 -0.5207758214095255 0.471343392207645 +189100364 -0.5213631400056529 0.4705725259994075 +189298530 -0.5209461867134247 0.47080640831447085 +189496696 -0.5208211145459307 0.47091619659558664 +189694862 -0.5203639097346344 0.470032073150943 +189893028 -0.5210470588005556 0.4701705951631817 +190091194 -0.5208415596979966 0.46997334714680783 +190289360 -0.5207253899863759 0.47022313197330634 +190487526 -0.5198667668514521 0.4699632836752004 +190685692 -0.520418652129516 0.4688520909310601 +190883858 -0.5204852574890583 0.4695430326391287 +191082024 -0.519922673992779 0.4688794420474408 +191280190 -0.5201860296607854 0.46959035682407024 +191478356 -0.520342309039519 0.4682202111139029 +191676522 -0.5201116096405651 0.4689213577720501 +191874688 -0.5198624626873155 0.4682608053168898 +192072854 -0.5197306149066134 0.4685183398214013 +192271020 -0.5200902118285993 0.4673054669913718 +192469186 -0.5199187261995978 0.467567427669073 +192667352 -0.5204491573037673 0.4669100024947316 +192865518 -0.5199576155373329 0.4666658143708899 +193063684 -0.5199667624755298 0.4668084750690734 +193261850 -0.5199752949754889 0.46663828766250437 +193460016 -0.5202664472463806 0.46659480105713474 +193658182 -0.5195557355179914 0.4669498264784356 +193856348 -0.5203706664830318 0.4666583454004931 +194054514 -0.5195962388558664 0.46598243205207746 +194252680 -0.520080265026571 0.4659073300722694 +194450846 -0.5196202588876042 0.4655026723341288 +194649012 -0.5197719989480277 0.4653275045051447 +194847178 -0.5195850007476891 0.4654535925133732 +195045344 -0.5188500152429645 0.46492920330034365 +195243510 -0.5186785592103448 0.4648492569739777 +195441676 -0.5195158181370438 0.46474149108449037 +195639842 -0.5193956947468711 0.46432455250402477 +195838008 -0.5192981014015724 0.46413729848029045 +196036174 -0.5196967756566829 0.4638913025859757 +196234340 -0.5195141393122953 0.46365145968735444 +196432506 -0.5192194752876804 0.46373839351032037 +196630672 -0.5188110390772915 0.46345582003565833 +196828838 -0.5193776589081361 0.4632101034162049 +197027004 -0.5195341545185744 0.4628832840888286 +197225170 -0.5185716105266099 0.4629025177572085 +197423336 -0.5192266937522773 0.4629069262026079 +197621502 -0.5192739514329376 0.46209160996695503 +197819668 -0.5194417035577057 0.46212468391422196 +198017834 -0.518701010183617 0.461497179488537 +198216000 -0.5187204148391155 0.4618009650362637 +198414166 -0.5185608866280346 0.46179262525785497 +198612332 -0.5182577707734736 0.46114919436584084 +198810498 -0.5186761845160872 0.4616452795980917 +199008664 -0.5186256106644875 0.4610280264078494 +199206830 -0.5193281182950799 0.4601890341065127 +199404996 -0.5188311915875279 0.4608180380251745 +199603162 -0.5192310395802778 0.4602200045870808 +199801328 -0.5186033720522678 0.46054644003281164 +199999494 -0.519072918034887 0.45974353722566413 diff --git a/test/data/ft240-43.s1p b/test/data/ft240-43.s1p new file mode 100644 index 00000000..17a9a52a --- /dev/null +++ b/test/data/ft240-43.s1p @@ -0,0 +1,2021 @@ +# HZ S RI R 50 +50000 -1.0000440487183417 0.012375249401504244 +149034 -1.000845604793446 0.036969764558453924 +248068 -0.999385670187039 0.06120170868073284 +347102 -0.9971259625471285 0.08590838524761664 +446136 -0.9941113428925963 0.11065054836715914 +545170 -0.9904250117957555 0.13532448432970737 +644204 -0.9860766628637774 0.16006690289910866 +743238 -0.9806069756692106 0.18545453574154472 +842272 -0.9745758215282093 0.21077748742362518 +941306 -0.9670342021673918 0.2365640974132041 +1040340 -0.958499622605099 0.26287813334766247 +1139374 -0.9481492795103106 0.28928249776854287 +1238408 -0.9359632351682226 0.3161466379842837 +1337442 -0.9211685385206595 0.3432669620722579 +1436476 -0.903741807707711 0.36938661710269793 +1535510 -0.8832464369249795 0.3941001666862777 +1634544 -0.8604130522488269 0.41684863157372776 +1733578 -0.8353666064520224 0.4365539062204734 +1832612 -0.809135858914292 0.4528870835910578 +1931646 -0.7828340999660549 0.46630575585700085 +2030680 -0.7566551003757538 0.4767175886896673 +2129714 -0.731279080159068 0.48464808810858345 +2228748 -0.7067325630866533 0.49030239489947286 +2327782 -0.6832641183744138 0.4941160579564003 +2426816 -0.6610739142724612 0.4964660210143591 +2525850 -0.6401829817390025 0.4975069990018587 +2624884 -0.6201531993288562 0.4971881817565435 +2723918 -0.6016373981054663 0.4963107254655197 +2822952 -0.5842597580340673 0.49469332024020857 +2921986 -0.5681708268078586 0.49263039296023947 +3021020 -0.5530382536567087 0.48984385652862666 +3120054 -0.5391376787326128 0.487017835311737 +3219088 -0.5260640358797839 0.4837646372090238 +3318122 -0.5138190876684805 0.48069975179013985 +3417156 -0.5023978873995086 0.477784108899533 +3516190 -0.49193345355010076 0.47470861249719315 +3615224 -0.48169150476612477 0.4716602815889278 +3714258 -0.47227770486328996 0.46884177410579936 +3813292 -0.4635254713503258 0.4658812597652106 +3912326 -0.45509893616151986 0.4632979857490776 +4011360 -0.44727608022726373 0.46083503292673916 +4110394 -0.4397019950263948 0.45864936637224785 +4209428 -0.43238410544509237 0.4562029385135756 +4308462 -0.4253547496677488 0.45403512376651206 +4407496 -0.4186061479715913 0.4521092057836463 +4506530 -0.4123056340946823 0.45025500327116985 +4605564 -0.40601603027830613 0.44852840416378675 +4704598 -0.4000789350656528 0.4471115201833866 +4803632 -0.39429911268711704 0.44557739857212886 +4902666 -0.38867176852870217 0.4440684385001954 +5001700 -0.38314418096154795 0.4428024363369352 +5100734 -0.37780568071454973 0.44158933845125115 +5199768 -0.3726814509751363 0.44046818951248734 +5298802 -0.3673887815751556 0.4392824024664967 +5397836 -0.36232392401296154 0.43836137324214086 +5496870 -0.35732023623857295 0.4376147508311724 +5595904 -0.3527170625956517 0.43655369830355983 +5694938 -0.347843832753303 0.43575220596078845 +5793972 -0.34325825170611673 0.43486777923773756 +5893006 -0.3385638799535938 0.434262903936108 +5992040 -0.33425674234688174 0.4334917658757122 +6091074 -0.3300186811627066 0.4329389791153027 +6190108 -0.3255759514883911 0.4321937924748982 +6289142 -0.32128002613024825 0.4314606989332759 +6388176 -0.3169374497966562 0.43117794039737256 +6487210 -0.3127747811517873 0.430540667549533 +6586244 -0.30873422412518947 0.429980986331365 +6685278 -0.30462680581833307 0.42931601366041916 +6784312 -0.3005663035754725 0.42873405564248485 +6883346 -0.29685056049604275 0.4282001731804133 +6982380 -0.2930879082492459 0.42785911868244536 +7081414 -0.2891859831204065 0.42741305896217974 +7180448 -0.28530606196504593 0.4269665110880303 +7279482 -0.2815236502029458 0.42652448337686927 +7378516 -0.2778448618419597 0.4262079894986772 +7477550 -0.2741075761634614 0.42578472968056885 +7576584 -0.27046535186173265 0.42540023342921857 +7675618 -0.2666797169742983 0.4249054949107095 +7774652 -0.2632343723832282 0.4246850349960751 +7873686 -0.25971710667262426 0.4239154032001261 +7972720 -0.2561367251187341 0.4237161586430076 +8071754 -0.2529978028472672 0.42322954209852565 +8170788 -0.24927111446785333 0.42279594651417984 +8269822 -0.24608535284345215 0.42254452660519326 +8368856 -0.2427692397461813 0.42189342161148735 +8467890 -0.23945617596531213 0.4216635109209783 +8566924 -0.23618485016284105 0.42115216922625476 +8665958 -0.23301832304888423 0.42069628088258154 +8764992 -0.22974367096594783 0.42031835909305076 +8864026 -0.2265229534424358 0.42010307750550135 +8963060 -0.2235983447986011 0.41953127432250936 +9062094 -0.2204558311381231 0.4193408187145298 +9161128 -0.21727271043491775 0.41881754588996534 +9260162 -0.21413910125442048 0.41843084776558664 +9359196 -0.21125891246324593 0.41795105739113037 +9458230 -0.20832694325946982 0.41761744032631826 +9557264 -0.20534919948958444 0.4173340407759466 +9656298 -0.20250904045626314 0.4166603238980209 +9755332 -0.19967749408018381 0.41619305000537277 +9854366 -0.19683578330703044 0.4159176418033673 +9953400 -0.1941170935320531 0.41548488788260096 +10052434 -0.19159661442522744 0.4152937413056145 +10151468 -0.18859201368605333 0.41479197854949046 +10250502 -0.1858462920745215 0.41429280960394105 +10349536 -0.18305611315115736 0.4138457638770978 +10448570 -0.18042132339027664 0.4133285036180959 +10547604 -0.1776986321632301 0.4129709059797729 +10646638 -0.17519333384574837 0.4126350590033875 +10745672 -0.17258119750592554 0.41221259138078153 +10844706 -0.1698629321726618 0.4116139718182843 +10943740 -0.1673773369799853 0.41122736860543346 +11042774 -0.1650183304425727 0.41072576166585634 +11141808 -0.16258654586377594 0.41038978827162764 +11240842 -0.1599758428784368 0.4098137052371423 +11339876 -0.1576845666643375 0.40965609343335807 +11438910 -0.15530762829026643 0.40892181031872266 +11537944 -0.1528254559700538 0.4085729683465986 +11636978 -0.15037966990738685 0.40808362212509036 +11736012 -0.14799505499103616 0.4077854711344866 +11835046 -0.1456974495771637 0.4073633850835315 +11934080 -0.14342915717325233 0.40681434372188247 +12033114 -0.14104360906930022 0.4064177603478956 +12132148 -0.1387266477065702 0.4058851758122036 +12231182 -0.13658580665121067 0.40541331009111875 +12330216 -0.13443803508675162 0.40486260897578696 +12429250 -0.13230157205748272 0.4045348907160653 +12528284 -0.1299537254930951 0.40416266727534705 +12627318 -0.12764764259023353 0.4035622568230409 +12726352 -0.12560170149383976 0.40335976520113775 +12825386 -0.1236207981683455 0.4028218414075756 +12924420 -0.1213970147295524 0.40233240647274215 +13023454 -0.11935159011977262 0.4018016908581305 +13122488 -0.1172342328612645 0.40131601799246946 +13221522 -0.11529501836232929 0.4008259209438857 +13320556 -0.11331923757767337 0.40033430287710897 +13419590 -0.11131328132162567 0.4000653768403586 +13518624 -0.10933137345255511 0.39952303393481853 +13617658 -0.10751319157107553 0.399124339466342 +13716692 -0.10527733525564519 0.3986889490816505 +13815726 -0.10351815814239211 0.39810739854335525 +13914760 -0.1014683219170833 0.3977315297475265 +14013794 -0.09967483270474763 0.3971867101260624 +14112828 -0.09781950608331745 0.39679915388647924 +14211862 -0.0958351604328481 0.3962552704766071 +14310896 -0.09389924127572022 0.3956870116840993 +14409930 -0.09225852211569856 0.39546303936533883 +14508964 -0.09047620296597696 0.3950899692537426 +14607998 -0.08856090925864106 0.3944242652904879 +14707032 -0.08673960188313122 0.3938915358008976 +14806066 -0.08504799091454185 0.3936085357908679 +14905100 -0.0832737259066695 0.39323909087236836 +15004134 -0.08169630679772236 0.39250738245006933 +15103168 -0.07988690321313502 0.3920963818634262 +15202202 -0.07813805319457508 0.3916871267638855 +15301236 -0.07637479145582703 0.39125828190601875 +15400270 -0.07481871043508284 0.3907984468890462 +15499304 -0.07304514383374992 0.3902411055817282 +15598338 -0.07136540221813067 0.38991325105296315 +15697372 -0.06982398135605054 0.3894226944764039 +15796406 -0.06812626673591 0.38904699765987566 +15895440 -0.06648990816446168 0.3885570404078556 +15994474 -0.06480600269468485 0.3881198379424092 +16093508 -0.06345473305294469 0.38757355875381355 +16192542 -0.06166386714629848 0.3870500358548991 +16291576 -0.060206180126728286 0.38693226078564574 +16390610 -0.05864992701323261 0.386393542609634 +16489644 -0.057030895784246735 0.38591543627569386 +16588678 -0.05566130547760734 0.38540863763241784 +16687712 -0.05410805684372143 0.38509494401182953 +16786746 -0.05272179680658622 0.3845630845610212 +16885780 -0.0512265414241611 0.384179764690434 +16984814 -0.04979810823691765 0.3836869917528111 +17083848 -0.04836914559444155 0.38326205283054243 +17182882 -0.04680686709956434 0.3828348328856347 +17281916 -0.045553062952683986 0.3825878174794791 +17380950 -0.04409746283422333 0.38209526006993905 +17479984 -0.04262405901186904 0.3813651853128447 +17579018 -0.041239733100135616 0.38104865081294076 +17678052 -0.03985545469349118 0.3807271024094046 +17777086 -0.03841827712178281 0.38022209770187937 +17876120 -0.037119993320735865 0.37997925668069227 +17975154 -0.035771153321643136 0.37937741324946983 +18074188 -0.03443696842927325 0.3790750665463068 +18173222 -0.033189997962804876 0.3786557449930398 +18272256 -0.031924157309091976 0.37818927859716067 +18371290 -0.030499470537547355 0.37760891016723164 +18470324 -0.029412211256504808 0.3772373795483398 +18569358 -0.027952985971153397 0.3768579368527577 +18668392 -0.026728608589207396 0.37643777004867246 +18767426 -0.025455233954375645 0.37593654061570797 +18866460 -0.024289385889030103 0.3757886964256715 +18965494 -0.022900537111343697 0.3750842726633074 +19064528 -0.021711216624276546 0.37474107535933593 +19163562 -0.02020478769978836 0.37446934620532896 +19262596 -0.019390906835535484 0.37390246535416527 +19361630 -0.01808031223364181 0.3735459454098052 +19460664 -0.017021775011879507 0.3730515483469473 +19559698 -0.015712120289242203 0.3726526815524478 +19658732 -0.014396872056040176 0.3725115361931483 +19757766 -0.013295469848449254 0.37210556547788776 +19856800 -0.012098308624520273 0.37145483107939514 +19955834 -0.010969049025210082 0.37130636694726654 +20054868 -0.010227716372844807 0.3708010231269848 +20153902 -0.008926418981638179 0.37051486627667835 +20252936 -0.0077666352181749105 0.3698855207237489 +20351970 -0.006816470916217028 0.36960225464473706 +20451004 -0.005741435969004015 0.3692498661072366 +20550038 -0.004390321806867153 0.3688474753197675 +20649072 -0.0034102133121003755 0.3685735046696806 +20748106 -0.0023691569694813154 0.3681321582664397 +20847140 -0.001257262193887764 0.36769529656597016 +20946174 4.051984693108225e-06 0.36738399520685894 +21045208 0.0007890653912741388 0.3671613602217866 +21144242 0.0019374797272924298 0.3666932283153979 +21243276 0.002819204607536686 0.3662518858038456 +21342310 0.003943167069343064 0.36584862265754015 +21441344 0.004918047255132635 0.3654330029913363 +21540378 0.005943694916988668 0.36510359736017944 +21639412 0.007078414613878926 0.36478129723469055 +21738446 0.008003576211855488 0.3644924980561215 +21837480 0.009114767881952385 0.36410830297047936 +21936514 0.009763499851275906 0.36367340349494093 +22035548 0.011101686392662987 0.3633571343368099 +22134582 0.012212910463799883 0.3629897278539641 +22233616 0.012990186417060866 0.36259136231437145 +22332650 0.014011773436192504 0.362214754691525 +22431684 0.014871161971244709 0.362013029496952 +22530718 0.015774127950887628 0.3615295280393804 +22629752 0.01685330376460739 0.36116792147962473 +22728786 0.017537868276112454 0.3607997096221492 +22827820 0.018692181563003594 0.36058987804238946 +22926854 0.019471410573186812 0.3602350651387701 +23025888 0.02044076063270116 0.3599429380710855 +23124922 0.021415767389431732 0.3594109044808562 +23223956 0.022306948218929416 0.3592975323831221 +23322990 0.023215508373695872 0.3589234178290431 +23422024 0.023979149611244876 0.3585442227922897 +23521058 0.024928808392326435 0.35822032218678684 +23620092 0.025926963998150827 0.35798523109468383 +23719126 0.02683987125948925 0.3577499861424331 +23818160 0.02759271161917185 0.35721540328781837 +23917194 0.028558740483493533 0.3569214491225801 +24016228 0.029287088647705864 0.35670281520726793 +24115262 0.030160335207617378 0.3563732137046263 +24214296 0.030935808461443018 0.3558956252434105 +24313330 0.031859793225929495 0.3556232869040077 +24412364 0.032683668175186996 0.35529263049629584 +24511398 0.03341172208951101 0.35517984697564847 +24610432 0.0343264064110862 0.35478666055812 +24709466 0.035187541945549176 0.35448209254077195 +24808500 0.03599508144275817 0.3540159546651729 +24907534 0.036690029276037865 0.3538208223622494 +25006568 0.03757585177902648 0.35331116892971454 +25105602 0.03819557234833949 0.35296657688385913 +25204636 0.03931450059406042 0.3528190718419485 +25303670 0.04002858863502328 0.3524692042711345 +25402704 0.040846307572763835 0.35236202627605784 +25501738 0.041611994353083076 0.35208810083700265 +25600772 0.04229634328477402 0.35178240862755766 +25699806 0.04309592957894165 0.3513727570907405 +25798840 0.04382706751232628 0.3512004916807285 +25897874 0.04464258428625451 0.35078839076671886 +25996908 0.04543355181542399 0.35050391276707615 +26095942 0.04620163571745694 0.35034795307747285 +26194976 0.04689126370002906 0.35008123392292795 +26294010 0.04777016525215434 0.3497682438601624 +26393044 0.048358704755932055 0.3495615419568055 +26492078 0.04913357111942969 0.3492273219995802 +26591112 0.05001149183712216 0.34902646513613955 +26690146 0.05072854116741899 0.3488284777973466 +26789180 0.051337389867188776 0.3484353141758505 +26888214 0.052150351553278365 0.3482493041504283 +26987248 0.052679048063130944 0.34779341357507026 +27086282 0.0533878211053751 0.34765960102392574 +27185316 0.054330258925734565 0.3471103894476515 +27284350 0.05461712556546158 0.3472018366774453 +27383384 0.05563653758219402 0.34678218250273773 +27482418 0.056153539607493516 0.3465724059983476 +27581452 0.05690081746070356 0.346226442147095 +27680486 0.057731231383181224 0.34616820484774685 +27779520 0.05835206614541294 0.34580796335161673 +27878554 0.05894269678237472 0.34556015073132273 +27977588 0.05974901878580039 0.3453671897231541 +28076622 0.060244920843025324 0.34506754377977844 +28175656 0.06101511703260513 0.34489591752344545 +28274690 0.06156402565152142 0.3445249166362521 +28373724 0.06228992924109188 0.34444572051093975 +28472758 0.06295474941688269 0.34408367177936383 +28571792 0.06370102566274524 0.3440198895246423 +28670826 0.06417027739860687 0.34353886096171293 +28769860 0.06482243834124306 0.3434580544613148 +28868894 0.06560128767083748 0.34317033931648405 +28967928 0.06615551745206272 0.34289811086581934 +29066962 0.06661527368981543 0.34262175093198555 +29165996 0.06732590392476417 0.34240726328596377 +29265030 0.06796343079919567 0.34217682635524554 +29364064 0.06867211492137291 0.3421010481438643 +29463098 0.06930308114834417 0.3418287613169085 +29562132 0.07003136274739698 0.34144807385964737 +29661166 0.07062933913429047 0.34104659241001933 +29760200 0.07092274088974887 0.3411885454472825 +29859234 0.07180948475197894 0.34077004122981236 +29958268 0.07240787056250363 0.34057512355251923 +30057302 0.07303857787490758 0.3404884104514696 +30156336 0.07352911151913351 0.34013676085370503 +30255370 0.07405239555291715 0.3397684446351473 +30354404 0.07464986622000332 0.3397298116066128 +30453438 0.07520609525753522 0.33949568783726997 +30552472 0.07575552414502419 0.33941197830264663 +30651506 0.07633999041468506 0.33908540631342593 +30750540 0.07711281759881351 0.33867783767920334 +30849574 0.07763960067992716 0.3384437131849167 +30948608 0.07822213715234534 0.3384183346474177 +31047642 0.07883928774725255 0.33829452217765965 +31146676 0.07929194687765137 0.3379904452417002 +31245710 0.07987387217024577 0.33794479550438994 +31344744 0.08042183555222915 0.33772933014782 +31443778 0.08096625982006693 0.33723939792654395 +31542812 0.08169455133435222 0.33717491301006314 +31641846 0.0821467139653082 0.3371264015249831 +31740880 0.08267482670046306 0.3367688206934853 +31839914 0.08313554207586873 0.33652436765425403 +31938948 0.08376474563847107 0.33633301158387846 +32037982 0.08443016927101231 0.3364043867807788 +32137016 0.08482138803270772 0.3360093973351501 +32236050 0.08538318437638232 0.33569910068421643 +32335084 0.08598793229403266 0.3356556886476026 +32434118 0.08660512532374359 0.3353394588101973 +32533152 0.08700387807369006 0.33532689398128157 +32632186 0.08749662683107094 0.3348756433673957 +32731220 0.08793981387502812 0.33486422640500374 +32830254 0.08860945179762363 0.33469803786816205 +32929288 0.08904032393157006 0.33446774122845 +33028322 0.08961106030261809 0.33417281072229515 +33127356 0.09010939185051416 0.33398069718326073 +33226390 0.0905678401809131 0.3339562909430932 +33325424 0.0910206193505532 0.33375321292530735 +33424458 0.09170198094419182 0.33356108698574266 +33523492 0.09212275052418196 0.3331704415423715 +33622526 0.09271205252044395 0.33308903701429704 +33721560 0.09312521916886898 0.3330384844362826 +33820594 0.09360508721894983 0.3328420282300695 +33919628 0.09414664951096524 0.33275728158773793 +34018662 0.0946015405550094 0.33249457552041534 +34117696 0.0950209526225676 0.3321806135465402 +34216730 0.09571895957352616 0.33221709517518216 +34315764 0.09618724088766464 0.3318744752216774 +34414798 0.09662317963914868 0.33182174425525224 +34513832 0.0970605318587363 0.3315220954522382 +34612866 0.0976237817375476 0.33145613011042935 +34711900 0.09808500008720196 0.3312424403007269 +34810934 0.09858595119784132 0.33093701574986023 +34909968 0.0989631639838757 0.3306996542776623 +35009002 0.09956022886049358 0.3307664864268501 +35108036 0.09990222427166726 0.3306184776490718 +35207070 0.10029004665718094 0.3304728164246802 +35306104 0.10068290888886226 0.3301874014247084 +35405138 0.10139589347245778 0.3301126110314642 +35504172 0.10185393678566207 0.32993264993095833 +35603206 0.10241132922519973 0.32967234308566273 +35702240 0.10274296391273467 0.3295321585706149 +35801274 0.10332642435654803 0.3293102303421031 +35900308 0.10346242772543994 0.32904301666381797 +35999342 0.1041477651339104 0.32915237728320107 +36098376 0.10433255870919637 0.32910214599225845 +36197410 0.10493772964580905 0.3286462175771481 +36296444 0.10535564173137275 0.32864362489292265 +36395478 0.10584311453422297 0.32833752076801176 +36494512 0.10639934223265478 0.3282083044863998 +36593546 0.10665572139324744 0.32808308115599916 +36692580 0.1070254190844114 0.3280035835881584 +36791614 0.10774431710647114 0.32793376764751797 +36890648 0.10817657853395007 0.3277087797593089 +36989682 0.10829907235594328 0.32752329656802115 +37088716 0.10875387008129112 0.3272612857474824 +37187750 0.10925067955193137 0.32735819084121565 +37286784 0.10968830481146981 0.3270996251221699 +37385818 0.11008671013290158 0.3269034108039648 +37484852 0.11066516689182894 0.326754357013417 +37583886 0.11110111368516395 0.3267677013798443 +37682920 0.11155348295089518 0.32642098891617666 +37781954 0.11191310757212096 0.32643729508674785 +37880988 0.11239862121537521 0.3261989538613668 +37980022 0.11287316934618336 0.3259839008255713 +38079056 0.11321222276456652 0.32598530833750533 +38178090 0.11363019812624135 0.3257645629755001 +38277124 0.1140239263376726 0.3256314683031785 +38376158 0.11431565408567287 0.32561116246736593 +38475192 0.11480721689844 0.32538234317700654 +38574226 0.11508943839707668 0.3253046833195092 +38673260 0.11548053951407554 0.32501469871175637 +38772294 0.1159607172558532 0.32493849565269434 +38871328 0.11631031918410936 0.3247562468573475 +38970362 0.11666699831356586 0.3245422091725447 +39069396 0.11700186605643365 0.3246085116094274 +39168430 0.11746852172048868 0.32425840649231275 +39267464 0.1178747548717266 0.3243276883706595 +39366498 0.11822431831210418 0.32406696371219074 +39465532 0.11875670091241873 0.3240212831420898 +39564566 0.11900559369363817 0.3237990986050767 +39663600 0.11943291459347748 0.32358095794633557 +39762634 0.11986042200764292 0.32370002494773925 +39861668 0.1202326487961674 0.3234672241826148 +39960702 0.12069827615017412 0.3234675275737465 +40059736 0.12134072488357137 0.3231756210212221 +40158770 0.1213723765581436 0.32312109622982604 +40257804 0.12203149704805753 0.32319925866874344 +40356838 0.1223321874995849 0.3228444955287431 +40455872 0.12239642319868076 0.3228639110703262 +40554906 0.12303843584389208 0.3226944992892721 +40653940 0.12336164345804143 0.322367553493246 +40752974 0.12376489199848566 0.3223725020135526 +40852008 0.12384987802386581 0.32242082989918486 +40951042 0.1244424900233537 0.3221391803557459 +41050076 0.12476416011555116 0.3222134941263583 +41149110 0.12529531896480586 0.3220882353455937 +41248144 0.12570013763341892 0.32185497528778684 +41347178 0.12586292566976218 0.32194687492608004 +41446212 0.12616637715993023 0.3217053641475544 +41545246 0.1264592901633366 0.32148332221030435 +41644280 0.12694950153548248 0.32132635953637106 +41743314 0.12724186709317942 0.3214742712237246 +41842348 0.12760171955007293 0.3211540740771081 +41941382 0.1280994576604904 0.3208442713475976 +42040416 0.12805703678096103 0.3210718300733569 +42139450 0.1286568211188197 0.32069610429611856 +42238484 0.12913110869101488 0.3206592586711514 +42337518 0.12914919407506079 0.32052857265194834 +42436552 0.129766613762605 0.32056820852500384 +42535586 0.13001336821594517 0.32043035913037354 +42634620 0.13032206042966746 0.3204565238697434 +42733654 0.13072399504117344 0.3203172514359991 +42832688 0.13105952192294693 0.32002735445501573 +42931722 0.13130516235811443 0.32010671815050307 +43030756 0.1318278024530878 0.3199733249886076 +43129790 0.132356771628508 0.3196969180701727 +43228824 0.1324283120549712 0.3196952996539896 +43327858 0.13266709536421759 0.3195617731894714 +43426892 0.1331060355655358 0.31952741611091134 +43525926 0.133418009187182 0.3193526505646155 +43624960 0.13354287847331395 0.3194122982712433 +43723994 0.13402996927221783 0.31897239159769 +43823028 0.13441266064084625 0.318936574711167 +43922062 0.13447025263389104 0.31906246237421226 +44021096 0.13502397984991493 0.31884263761314763 +44120130 0.13516204737542106 0.3186048562688516 +44219164 0.13560231187185362 0.3187437418931172 +44318198 0.13585551461816445 0.31864623820497345 +44417232 0.1362177097913103 0.3183846838659615 +44516266 0.13655641848416566 0.31829607173446417 +44615300 0.1366970428629347 0.31828010286770214 +44714334 0.13700915450502998 0.31820944033772214 +44813368 0.13738772467497484 0.31807727884139 +44912402 0.13781338461576698 0.31792840808326506 +45011436 0.13809360460939574 0.31772958285422986 +45110470 0.1382384350873972 0.31769535624585915 +45209504 0.13880180424381705 0.31765126870610355 +45308538 0.13891675515521568 0.31759078679737407 +45407572 0.13921062503399267 0.3174666075903047 +45506606 0.13949972004969835 0.3175824282838395 +45605640 0.1396660293206947 0.31740196815428007 +45704674 0.14015545953049727 0.3170725523834205 +45803708 0.14034015752045245 0.3170919812159424 +45902742 0.14076258607664163 0.3170597623796635 +46001776 0.14092863099292363 0.31704399700083874 +46100810 0.14137875056458088 0.31674865638443456 +46199844 0.14156131225748605 0.31689994320208714 +46298878 0.14171879817402594 0.31662277660324206 +46397912 0.1422965895162877 0.31675000586458635 +46496946 0.14260738091822417 0.31642990254321784 +46595980 0.14263975020828196 0.31616791895021046 +46695014 0.14281771875604402 0.31639718270568434 +46794048 0.14354649888252544 0.3161316131787417 +46893082 0.1434123596227361 0.31629476857064875 +46992116 0.14378712112551362 0.31619324283210787 +47091150 0.14394514955831367 0.31597990088450567 +47190184 0.144277869056639 0.31584362253909676 +47289218 0.1445113610548252 0.31572124199947793 +47388252 0.14488522445302923 0.31589684650622746 +47487286 0.14528802836512797 0.3156405573661851 +47586320 0.14523498534700416 0.315554830350116 +47685354 0.14554713623247265 0.31551252682980546 +47784388 0.14590852121574122 0.3153549934684646 +47883422 0.14625552984275297 0.31561789937285845 +47982456 0.1466082483309153 0.3153648099291271 +48081490 0.14653368519107848 0.31522261160651016 +48180524 0.14710360273258305 0.3151339333277947 +48279558 0.14707343840523765 0.3149508984887796 +48378592 0.14747737498211833 0.3150612479121866 +48477626 0.14769362490997728 0.31489811357422726 +48576660 0.14791796758897718 0.31487531531180724 +48675694 0.14809687138548058 0.3147564646908916 +48774728 0.14833566643770574 0.31475613742271336 +48873762 0.14864702036052366 0.31462348287669956 +48972796 0.14895265016864545 0.31445642455263706 +49071830 0.149314170619961 0.314523953226443 +49170864 0.14948615177450658 0.314389623551622 +49269898 0.14973428797319416 0.31415688267091296 +49368932 0.14990907445289706 0.31446765085484446 +49467966 0.15030811941300293 0.31414722711694515 +49567000 0.15055214649816046 0.3140696378113095 +49666034 0.15065341325888612 0.31405114754735364 +49765068 0.1509016640724813 0.31398324871735955 +49864102 0.15118207679979198 0.3140201383536916 +49963136 0.15151381796775237 0.3138957623359707 +50062170 0.1519697359968096 0.3136579497710532 +50161204 0.15208150583641036 0.31365471440342896 +50260238 0.1525116745547287 0.31366541334848447 +50359272 0.1526714408196618 0.31363134939015874 +50458306 0.15282653089679896 0.3135097281979029 +50557340 0.15323609762698429 0.3134540778111856 +50656374 0.15334749317011828 0.31330431373074824 +50755408 0.15359325009377411 0.31338634970343626 +50854442 0.15375341015384525 0.31311983712413294 +50953476 0.1541971234510377 0.31321561898391614 +51052510 0.15423809642047465 0.31312210991309164 +51151544 0.15440042857589492 0.3131579768320946 +51250578 0.1546159405785324 0.31290858511462083 +51349612 0.15505752790471178 0.31284446897130025 +51448646 0.15518874021217702 0.3129058686476869 +51547680 0.15525431387444463 0.3127076815594672 +51646714 0.1555938440890402 0.3129532782515391 +51745748 0.1556528185791505 0.31256610782543315 +51844782 0.15607887720842298 0.3125913021989305 +51943816 0.15637734190003455 0.31242187743379085 +52042850 0.15642162748458718 0.312560675917683 +52141884 0.15652860634514332 0.3122994203588769 +52240918 0.15666213954677527 0.3124668512881158 +52339952 0.1571780669514674 0.3122880512652398 +52438986 0.15731534711257875 0.3121271002363965 +52538020 0.15765134507894193 0.31215810243189107 +52637054 0.15788833746864459 0.31222075028238955 +52736088 0.1579419466919516 0.31221612857171904 +52835122 0.15828346691732426 0.3121025836598973 +52934156 0.1583703562889288 0.311923316628465 +53033190 0.15869868888019767 0.31201950277195656 +53132224 0.15888897510541988 0.3119125870748403 +53231258 0.1590583951009396 0.3118456329719456 +53330292 0.15930164409175368 0.3119223184553326 +53429326 0.1594227357432278 0.311761208406418 +53528360 0.1595785894636276 0.3116994910379742 +53627394 0.15973543227863352 0.31177746196098993 +53726428 0.15996323637370366 0.31173952652533604 +53825462 0.16034035360582244 0.311467887582987 +53924496 0.16047525212785058 0.311695318061979 +54023530 0.16071413420876554 0.31139012122434956 +54122564 0.16100801567484194 0.31145213145151057 +54221598 0.16112023561910496 0.3112657837574421 +54320632 0.16133454646845585 0.31115307463549086 +54419666 0.16143770437056829 0.311382233147246 +54518700 0.1615991318159734 0.31143414023164623 +54617734 0.16189290607584192 0.31116239665479084 +54716768 0.16206162351454861 0.3112341664354008 +54815802 0.16223213300617637 0.3111986577388447 +54914836 0.16238140692297678 0.3109150621878733 +55013870 0.1627705861888975 0.3111322909261663 +55112904 0.1627961267199415 0.3108302450363484 +55211938 0.16317023371903833 0.31071771849475127 +55310972 0.16306140719593956 0.31072838656528884 +55410006 0.16333311382382978 0.3108764655009701 +55509040 0.16358442915678784 0.3107981665329385 +55608074 0.163907959352644 0.3107807774596357 +55707108 0.1638824111724166 0.3106352920595226 +55806142 0.1642079129332683 0.3106701582288519 +55905176 0.1643216041442172 0.3106900086248918 +56004210 0.16441306126409788 0.31044733493822235 +56103244 0.1648442714314335 0.31042290677762635 +56202278 0.16505769659617134 0.31044271014320357 +56301312 0.16525205546453847 0.31065588532215355 +56400346 0.16515674305970696 0.31039599358460623 +56499380 0.16540768255609198 0.31030855126753004 +56598414 0.16573938111301686 0.3105030559751977 +56697448 0.16579217947040514 0.3104213037020045 +56796482 0.16584742029621893 0.3101103347043346 +56895516 0.16615792554677733 0.31027771041524876 +56994550 0.1664043573221258 0.31032088098166444 +57093584 0.16654425464884567 0.31022849167868966 +57192618 0.16662918863619117 0.3101027350139228 +57291652 0.16680924376694697 0.3102426014141091 +57390686 0.1671368704323146 0.31001332389672487 +57489720 0.16730654405561668 0.31020687432399374 +57588754 0.1673409634814929 0.30988780176673514 +57687788 0.16753502361397507 0.30981805809887747 +57786822 0.16763741360160478 0.3100324323496896 +57885856 0.16798446078174878 0.31009590961572675 +57984890 0.16799017691063325 0.3098113775346668 +58083924 0.1684778442852969 0.3097250638150387 +58182958 0.16856298841951592 0.3096931240994124 +58281992 0.16859344177430835 0.3097940452342682 +58381026 0.16857923824106266 0.3097381019408305 +58480060 0.16885604507750585 0.3097800414872854 +58579094 0.16911007976756795 0.30968260461511854 +58678128 0.16921090397925162 0.3095987970302391 +58777162 0.16947952871160246 0.3096108454553398 +58876196 0.16968696141324585 0.3095678810807177 +58975230 0.16978692620056038 0.3094814403649139 +59074264 0.16975282543158893 0.3096160275085054 +59173298 0.17008725895574547 0.3093933828796277 +59272332 0.1702723251605447 0.30945482898032645 +59371366 0.17031453047922668 0.3094508562746664 +59470400 0.17047291759349945 0.3093073482864508 +59569434 0.17060518011493989 0.30939624413680117 +59668468 0.170833459634358 0.3092907542899302 +59767502 0.17091945499575348 0.30947050179965313 +59866536 0.17104436757768135 0.3092084467660966 +59965570 0.17113672144390182 0.30932941186063506 +60064604 0.1715368001929616 0.30939658616519433 +60163638 0.17144625231327423 0.3092679985522117 +60262672 0.17135858587284125 0.3093417588436768 +60361706 0.17157318297544194 0.3091341580560436 +60460740 0.17181565860286435 0.3091364953125462 +60559774 0.17216530249306558 0.30905515938784 +60658808 0.17225447589082218 0.309072729918921 +60757842 0.17229340568585697 0.30914533406263645 +60856876 0.17246763809886895 0.3090530350969523 +60955910 0.17272656138227818 0.30896609435936784 +61054944 0.1727201898381766 0.3090705874317308 +61153978 0.1727678987368882 0.3090086951337181 +61253012 0.17308962828948327 0.3088804319867679 +61352046 0.17322919970592132 0.3089798126115834 +61451080 0.1732748684909967 0.30882070940173206 +61550114 0.17344873886094916 0.30900011164949565 +61649148 0.1733924446098022 0.30882184909765964 +61748182 0.17385776208522047 0.30889522712077 +61847216 0.1737823865874299 0.3087475869362238 +61946250 0.17401977153052275 0.30873136347430147 +62045284 0.17407400115373764 0.308695578253214 +62144318 0.17429293624330133 0.30877268348488307 +62243352 0.17465289639410878 0.30886929793863255 +62342386 0.17457232255528368 0.3086604107465373 +62441420 0.17461603197971548 0.3087876555372988 +62540454 0.17484603514055938 0.3087292864150969 +62639488 0.17496286672171857 0.3085924622862007 +62738522 0.1750573831869832 0.3085124811166236 +62837556 0.1751468755547488 0.30862566395616614 +62936590 0.17542570850793496 0.3087954288799767 +63035624 0.17533509283817997 0.30864986812589673 +63134658 0.1757006908280878 0.3087337425638085 +63233692 0.1758663225831687 0.3086072407612527 +63332726 0.17610418787950902 0.3086319042620737 +63431760 0.17612828192177088 0.3087130755151775 +63530794 0.1762962586351906 0.30860305437888913 +63629828 0.17629801953498633 0.3086012072021301 +63728862 0.17656535822030847 0.30832318337354314 +63827896 0.176642293128972 0.30861526627604496 +63926930 0.17654387033151947 0.30851217705251277 +64025964 0.17698870839022687 0.30859308957797454 +64124998 0.17701498155123355 0.30847007549777605 +64224032 0.17707619362707366 0.3086807394341982 +64323066 0.17715766555885715 0.3085357953909775 +64422100 0.17724447778748426 0.308585514081118 +64521134 0.17742187917112606 0.30842211750655135 +64620168 0.1774336430814479 0.30850709843278684 +64719202 0.17766055350233712 0.30854383547180697 +64818236 0.17770157886784543 0.30849612683867644 +64917270 0.17809826985635546 0.3084183028039231 +65016304 0.1780011948171231 0.30853409471202525 +65115338 0.17811705927622346 0.3083860065011049 +65214372 0.17848738030286773 0.308488552507301 +65313406 0.1784375561606622 0.3083836829236302 +65412440 0.17849763648556133 0.3084521961765762 +65511474 0.1787633253870975 0.30844264162998836 +65610508 0.17898624369409366 0.30833648230803995 +65709542 0.17889934110517658 0.30845225925890446 +65808576 0.17907522897674708 0.3085160548419716 +65907610 0.17894202815331786 0.3083898586433227 +66006644 0.17929257967931325 0.3085723061925179 +66105678 0.17932420422258713 0.30846942025370083 +66204712 0.17949793627199936 0.30859710849729693 +66303746 0.17959188814153967 0.30821336877166877 +66402780 0.17946892879091467 0.3083174059733634 +66501814 0.17968377558221926 0.3081694342642826 +66600848 0.179930930962725 0.3083635858497024 +66699882 0.18001368009751115 0.30840028213065707 +66798916 0.18015755288441704 0.3084842059500087 +66897950 0.18034925567035082 0.30810722447038386 +66996984 0.18029767504503594 0.3084074614590325 +67096018 0.18058949782674408 0.30829224439098774 +67195052 0.18071924380743243 0.308222407542712 +67294086 0.18076472770776603 0.30838837466183655 +67393120 0.18085963287963164 0.3084801258827175 +67492154 0.18090258697499642 0.308398750648429 +67591188 0.18094281288012465 0.3084117767988526 +67690222 0.1810854297986769 0.30845041820707997 +67789256 0.18134744006757988 0.30836720853174665 +67888290 0.18146025360196114 0.30861068699881017 +67987324 0.1815281268495168 0.30834537970791653 +68086358 0.1815619436076962 0.30848099172909704 +68185392 0.18150885256206073 0.3086343128745886 +68284426 0.18177786243148286 0.308292945079686 +68383460 0.18179492284299692 0.3083232833100656 +68482494 0.18190681577056486 0.3085141213739566 +68581528 0.18211301276170136 0.3083959377745101 +68680562 0.18224582744527387 0.3083776690011132 +68779596 0.1824395513921501 0.3084350151689915 +68878630 0.18234728606773823 0.3086498605451363 +68977664 0.18255925309228635 0.30837649699599184 +69076698 0.18249613555812952 0.30838844539390353 +69175732 0.18265423535297984 0.3087096479756226 +69274766 0.18283880810465392 0.30840274459496503 +69373800 0.18288666539559317 0.3084692478854444 +69472834 0.18295378875154497 0.308306186445019 +69571868 0.1830977446488129 0.30845950026356245 +69670902 0.18321680218204636 0.30848022214582116 +69769936 0.18334361734835375 0.3084712609469546 +69868970 0.18348957877348684 0.3086736637352356 +69968004 0.1835589862861476 0.30832232624629735 +70067038 0.18354052151211636 0.30807881440117363 +70166072 0.18367789245832095 0.30846792178363014 +70265106 0.18370637654926075 0.30848641101006957 +70364140 0.18381682043203823 0.3083805542701919 +70463174 0.18393647247247844 0.3083404176872244 +70562208 0.1837871392084064 0.3084522025877294 +70661242 0.18385990332751814 0.3082578867450349 +70760276 0.18410991945322433 0.3084688220796349 +70859310 0.18423904183324674 0.30853050972597124 +70958344 0.1843194009024995 0.30838878137945536 +71057378 0.18447771404985724 0.3085914666434999 +71156412 0.18439413366500523 0.30852864714812755 +71255446 0.1846809053638136 0.3082645442465961 +71354480 0.1847466782720936 0.30849846601639985 +71453514 0.18473600274212507 0.3085686961165236 +71552548 0.1847625060355195 0.3085373582399087 +71651582 0.18498937911423555 0.30870240921064596 +71750616 0.1850361034448153 0.30865501644791765 +71849650 0.18533352549814622 0.3085540394803812 +71948684 0.18524481462712503 0.3087210698059344 +72047718 0.18531730736471713 0.30844733388437895 +72146752 0.1855801004885814 0.30872062110947707 +72245786 0.18546431458058027 0.30835715595457164 +72344820 0.18566326230469546 0.30863051107808764 +72443854 0.18563493854889584 0.30870243888831944 +72542888 0.18589230065922144 0.30879349310518694 +72641922 0.18601857683007514 0.308934707994752 +72740956 0.1861202222289395 0.3085987510902608 +72839990 0.1859188885123403 0.30882233360189987 +72939024 0.18617865706083855 0.30882720632112565 +73038058 0.18633779909775497 0.3087771498730219 +73137092 0.18654315410869482 0.30893501241066956 +73236126 0.18657625692970092 0.3085358488596888 +73335160 0.18656844386588273 0.308713406956583 +73434194 0.18683704467378437 0.3089465037365615 +73533228 0.18672052126511166 0.3088942758807212 +73632262 0.1867686522933143 0.3088352648980784 +73731296 0.18681211684087046 0.3088343326565323 +73830330 0.1870491452775024 0.308958761438401 +73929364 0.1869732429706453 0.3089698584898504 +74028398 0.18727102240798926 0.3087137375421093 +74127432 0.18731758152013936 0.3089963516355256 +74226466 0.18743504707114544 0.30887040926896725 +74325500 0.18751675817379354 0.30897535958543537 +74424534 0.18773743999307235 0.30912437540578747 +74523568 0.18773128340125336 0.3092247331377983 +74622602 0.1878249784119037 0.3090632653612701 +74721636 0.18793444550302596 0.3092497355596222 +74820670 0.18794831045339852 0.309192976148384 +74919704 0.18795381565962627 0.3089517576418565 +75018738 0.1879418537042868 0.30938975872583147 +75117772 0.18822957423895165 0.30902307854603817 +75216806 0.18829300313144065 0.3092906884183398 +75315840 0.1884468544358687 0.30910991350430866 +75414874 0.18858777524732476 0.30928472403387086 +75513908 0.1883067616460702 0.30918413200189676 +75612942 0.18891285655369316 0.30933155516794014 +75711976 0.1888773807248431 0.3092060764509312 +75811010 0.18881252250737668 0.30945195443038737 +75910044 0.1888405798392741 0.3093214751018344 +76009078 0.18903746179063755 0.30925912435162883 +76108112 0.18891766503186144 0.30933080449662914 +76207146 0.188956877878554 0.3095201872728567 +76306180 0.18923743348260558 0.30951511039702495 +76405214 0.18934217399549633 0.3095478291757706 +76504248 0.18913315616143764 0.3095173125569668 +76603282 0.18953322983382556 0.3095805053475441 +76702316 0.1895488076933534 0.3097237924735209 +76801350 0.18975998358967336 0.30979647592146653 +76900384 0.18964168303376333 0.30956718285108054 +76999418 0.18973022558912706 0.3097794051542811 +77098452 0.19007981675561153 0.30968203458057375 +77197486 0.19008660784814244 0.3098269120332887 +77296520 0.18992924793717747 0.31003619533728993 +77395554 0.19019133408686748 0.3098960809258987 +77494588 0.19021578913560125 0.3098829055499614 +77593622 0.19026025172028052 0.3099313249957671 +77692656 0.1903013886591063 0.3099430607641531 +77791690 0.19049018069162793 0.309955198276333 +77890724 0.1906147091321361 0.3100127947618237 +77989758 0.19073745510701984 0.3099703356763612 +78088792 0.19066720628613734 0.3098673328261375 +78187826 0.19067090847786408 0.3101190364549947 +78286860 0.190935290623551 0.3101101060975372 +78385894 0.19092693357198146 0.3101258884807881 +78484928 0.19092346531839896 0.3100646687914152 +78583962 0.19104404132890146 0.31024487399260786 +78682996 0.19099315937265393 0.3099747916221135 +78782030 0.19113396264358642 0.31039186569957317 +78881064 0.19133129373427724 0.3101478852296193 +78980098 0.1913115827238283 0.3101222515526264 +79079132 0.19138512689848106 0.31002049335661197 +79178166 0.19136110737685838 0.31032558966780094 +79277200 0.19148013971745098 0.3101348934162002 +79376234 0.19164948609100135 0.31021459733100526 +79475268 0.19147024684345554 0.3100185166420357 +79574302 0.19150938179529128 0.3104692046430712 +79673336 0.19161457526968934 0.31029612350849756 +79772370 0.19164945057742502 0.3101474398839579 +79871404 0.19165841506261325 0.3101466035279082 +79970438 0.19181386427893987 0.3101349025063515 +80069472 0.19183893994348636 0.31048645228170313 +80168506 0.19186826222108946 0.3103217094410273 +80267540 0.19169890612722532 0.3105397545493947 +80366574 0.19178541700730525 0.3103832916658555 +80465608 0.19211323306676412 0.31063460866351367 +80564642 0.19174288566941294 0.3106816692152302 +80663676 0.19207614221683753 0.3104760987669507 +80762710 0.1917327766489125 0.3108288582815747 +80861744 0.19169446524686426 0.3108754077647546 +80960778 0.19191413860902487 0.31078185692399746 +81059812 0.19196137398248048 0.31091785132790356 +81158846 0.19187815377430373 0.3109879020854023 +81257880 0.19197510721790356 0.31107003154148577 +81356914 0.19198991343793637 0.3112253054721423 +81455948 0.19201396576638038 0.3114095632911917 +81554982 0.19210091036457574 0.3114587374882713 +81654016 0.1921321408213904 0.31147018633303336 +81753050 0.19242141032855367 0.31158217689195544 +81852084 0.19229049656053518 0.3115981210883427 +81951118 0.19236533780569215 0.3114828235822559 +82050152 0.19244991724813565 0.3119113883781243 +82149186 0.1923512234070595 0.3115207059485884 +82248220 0.1923412495901925 0.31183384803480213 +82347254 0.19249815605925139 0.31182329283794413 +82446288 0.19255852001916476 0.3120327438902253 +82545322 0.19252983195790802 0.3120317986968006 +82644356 0.19249764738268665 0.31199195976806265 +82743390 0.19261260095457008 0.3122382214271424 +82842424 0.1927655509214206 0.312234365159557 +82941458 0.19293452123103805 0.3121342021692716 +83040492 0.19299152818211546 0.31245468586914693 +83139526 0.1928325469094537 0.31204335560999336 +83238560 0.19305625049089903 0.3125670574380656 +83337594 0.1929304988781434 0.3123450364808102 +83436628 0.19323204487140425 0.31247962855117356 +83535662 0.19310149147233735 0.3127737794302946 +83634696 0.19329117402133186 0.31261298897727896 +83733730 0.19330274217277107 0.3127637761005858 +83832764 0.1931442730611737 0.3127374951814794 +83931798 0.19343611805606672 0.31271315417675283 +84030832 0.19355815865710696 0.31300434135371796 +84129866 0.19340543285085426 0.31288930606200394 +84228900 0.19331006663979788 0.3128950919877205 +84327934 0.19360512299866953 0.3130675569260312 +84426968 0.19361689037206048 0.31305458852410234 +84526002 0.19375163331147044 0.3131825928171815 +84625036 0.19368709176699392 0.312948052741616 +84724070 0.1938450576324477 0.31337408928898236 +84823104 0.19385779751893586 0.3133284048290789 +84922138 0.19387577443587464 0.3131941270199084 +85021172 0.1936071764699224 0.313452090690397 +85120206 0.19402687550164696 0.3134851717039748 +85219240 0.19412537052937545 0.31339344511010475 +85318274 0.19415549044683836 0.3134531038996519 +85417308 0.19404612211120198 0.3135965804175935 +85516342 0.19425470212795967 0.31350690137607806 +85615376 0.19415886688294762 0.3136323282613895 +85714410 0.19422909304013916 0.3133664590489681 +85813444 0.1942019910691302 0.31359535198827554 +85912478 0.19412471914850132 0.3135724434325961 +86011512 0.1943119444557395 0.3137909600205238 +86110546 0.1942187947642547 0.3137892487582337 +86209580 0.19429721007878284 0.3136719459155833 +86308614 0.1943868382430984 0.31390143939206766 +86407648 0.1946145744776743 0.3136381310692751 +86506682 0.1945702107560608 0.3139212180327577 +86605716 0.19436572428095308 0.3140240858499902 +86704750 0.19446677527580639 0.3140452576925941 +86803784 0.1944371352973632 0.31395543964939865 +86902818 0.19426449021737732 0.31410007455767897 +87001852 0.19465244073760657 0.3141948937004559 +87100886 0.19484663098235894 0.31418697987765754 +87199920 0.19445998440924672 0.3140726212966796 +87298954 0.19461980172348034 0.3143221922608356 +87397988 0.1947213070591164 0.3141793812044294 +87497022 0.1943183474131868 0.31431442815705585 +87596056 0.19483277255983017 0.3144389113052571 +87695090 0.19461247125210104 0.3142958963573502 +87794124 0.19480855515548734 0.31431593153781734 +87893158 0.19468095386096854 0.3145569267328845 +87992192 0.19477676051599185 0.31438437877160436 +88091226 0.19468003583666962 0.3146145054830128 +88190260 0.19455834499494642 0.31455716161656566 +88289294 0.1946652623277906 0.31466688378514834 +88388328 0.19466992945768744 0.31466897096121105 +88487362 0.19456641027337232 0.31462140434428637 +88586396 0.19467943665453094 0.31478838773656753 +88685430 0.19449213066454635 0.31472067259124814 +88784464 0.19461730751943682 0.3149527259545603 +88883498 0.19457022449997652 0.315076535957561 +88982532 0.19474005274028036 0.3150077205104766 +89081566 0.19467270716198593 0.3149781953312204 +89180600 0.19455631987452696 0.3152840147959444 +89279634 0.19465273385497844 0.31542206770989 +89378668 0.19455433145734827 0.3152432939706253 +89477702 0.19477735202790605 0.3153063188302533 +89576736 0.19466298976404559 0.3154068136970311 +89675770 0.19451487650601726 0.31536655396958796 +89774804 0.1946946865456373 0.31562356662220614 +89873838 0.19443631737911746 0.31551552665164234 +89972872 0.1947831772501528 0.3158630573636805 +90071906 0.19496878180760124 0.3159691500284188 +90170940 0.19460201145312112 0.3156522046957052 +90269974 0.19466029362882672 0.31584854987592503 +90369008 0.19473656279271195 0.31594313488211556 +90468042 0.19473471234762438 0.3160060396325479 +90567076 0.19475495156271053 0.3160297593481269 +90666110 0.19489639038281514 0.3161281936641405 +90765144 0.19469449296325214 0.3162878053214868 +90864178 0.19452735797266138 0.3162786440601982 +90963212 0.19475351711502764 0.3163244889352771 +91062246 0.19492147875671154 0.31656705257988044 +91161280 0.19471399376416113 0.31651982879057994 +91260314 0.19499054772720276 0.3165127781734675 +91359348 0.19488717597099497 0.31677151985398366 +91458382 0.19470957339940675 0.31667377357896687 +91557416 0.1948794927075204 0.316763211900904 +91656450 0.1947729629957799 0.3167879343081147 +91755484 0.19481833321977124 0.3169353142667953 +91854518 0.19503620703482694 0.31673685725722334 +91953552 0.19502238401180952 0.3170271575730314 +92052586 0.19491550207812627 0.31707259530108073 +92151620 0.19487258077704417 0.317249811985922 +92250654 0.19502982346455658 0.31714978146126316 +92349688 0.19492605560508128 0.31732312768937276 +92448722 0.19479494030940558 0.31753782940958536 +92547756 0.19516866410953623 0.3174405860273435 +92646790 0.1950853548221008 0.31757671650888625 +92745824 0.19516022453881438 0.31768735732528797 +92844858 0.1951271037253865 0.3176786068843224 +92943892 0.19505476243560504 0.3178682482026982 +93042926 0.19526316364007085 0.3177413565413325 +93141960 0.19506532355008221 0.3177927735682827 +93240994 0.19524816525477995 0.31799650893571774 +93340028 0.19523978373987372 0.31810976233863664 +93439062 0.19504359919070713 0.3179959994634464 +93538096 0.19537626974749125 0.31825354562505687 +93637130 0.19531014555672163 0.31816027028059757 +93736164 0.1952107469444191 0.318343035114328 +93835198 0.19520669685272968 0.3185295009902616 +93934232 0.1953236521649978 0.31843460329416007 +94033266 0.19531360443298232 0.31856539784375215 +94132300 0.1951326413351267 0.3186449341091535 +94231334 0.1953567424900229 0.3186924779340768 +94330368 0.19530657904734441 0.3187661968025626 +94429402 0.19532409765399505 0.3186971120416763 +94528436 0.1954617166673392 0.31893892815144126 +94627470 0.19539470015851287 0.3187606143264611 +94726504 0.19545471934638128 0.31903097473870645 +94825538 0.1955056342644955 0.31904205249724665 +94924572 0.19560446790263786 0.3190164100412226 +95023606 0.19563541089707517 0.31924996954675666 +95122640 0.19574260717077271 0.3194328188739793 +95221674 0.19539706930951364 0.3194637056012608 +95320708 0.19570087174964187 0.3193205165857807 +95419742 0.1954592037428612 0.3194224995169882 +95518776 0.19559854984307265 0.3193267438256559 +95617810 0.19571608316669248 0.3196050799629886 +95716844 0.19571471368434543 0.3198249341390752 +95815878 0.19575680361355136 0.31966976920453277 +95914912 0.19564059713832802 0.3197611015833956 +96013946 0.19575828730589423 0.3198470561097983 +96112980 0.19570159926061892 0.3200195008213598 +96212014 0.19578614072219014 0.32003471294996244 +96311048 0.1957450248261278 0.3201761634973837 +96410082 0.19566124340419325 0.3200752707812375 +96509116 0.195708226722023 0.3200073915933972 +96608150 0.19570283938232716 0.32033241234705084 +96707184 0.19566467082606404 0.3204637951917781 +96806218 0.1956837548555119 0.3205434448794061 +96905252 0.19573342195438848 0.32037145192987526 +97004286 0.19601948821797394 0.3205625393751432 +97103320 0.19585858913539028 0.3206827157042717 +97202354 0.1959684718238612 0.3208090422349592 +97301388 0.19579761396835546 0.32073717225830883 +97400422 0.1959414462266562 0.32095600298986937 +97499456 0.19608977721022885 0.32086200051421204 +97598490 0.19584235703435168 0.3209828515081976 +97697524 0.19580692517532206 0.3209990775613248 +97796558 0.1960664512853599 0.32091089037455195 +97895592 0.19599676536612584 0.321132973973165 +97994626 0.19588578643251348 0.3214425465525568 +98093660 0.19602602828350388 0.32122905790073447 +98192694 0.19600793602330785 0.3213762540495028 +98291728 0.1956715988120863 0.32156503318862617 +98390762 0.19609973668095865 0.3216307162994728 +98489796 0.19590354677055624 0.32151264283839737 +98588830 0.1959704816829138 0.3217074611725619 +98687864 0.1959023068730136 0.32158192828379983 +98786898 0.1960368242874243 0.3216468225697088 +98885932 0.1958837448914206 0.3218051166712123 +98984966 0.19591713314304085 0.32188861438641025 +99084000 0.196053532722229 0.32220085873857446 +99183034 0.19588589307013665 0.3220248800309541 +99282068 0.19588152418496088 0.3221274110868624 +99381102 0.19603199069374208 0.3221464162401373 +99480136 0.19612284562199941 0.32211207606211933 +99579170 0.1960239662958441 0.32212002978338056 +99678204 0.1961869443152644 0.32223138026463755 +99777238 0.19615265032719675 0.32253510382529227 +99876272 0.19601692625710157 0.32241863821906613 +99975306 0.19594593079358144 0.32235425248186067 +100074340 0.1961397860375494 0.32238374196078384 +100173374 0.1962810260036597 0.3225998093868947 +100272408 0.1962277832006454 0.3226415864456581 +100371442 0.19635149786011943 0.3227107889174928 +100470476 0.19609143684672226 0.32292363134606594 +100569510 0.19622793653554707 0.32297184868874285 +100668544 0.1963253201296045 0.3231554582076177 +100767578 0.19611083176896207 0.3231444697603188 +100866612 0.19611715306323022 0.3232730344936192 +100965646 0.19633067654944997 0.3232938182880848 +101064680 0.1963615680557811 0.3233464966150485 +101163714 0.19629335058014322 0.32330559884651683 +101262748 0.19634272487306734 0.3235326373031453 +101361782 0.1962509160425985 0.3235218702354365 +101460816 0.19623552972122776 0.3234296747726808 +101559850 0.196285659656151 0.323606025140146 +101658884 0.19610344962068668 0.3237983886752256 +101757918 0.19622731067199134 0.32382321838333983 +101856952 0.19616830544219588 0.3238186275354956 +101955986 0.19624207388092435 0.3240218741627557 +102055020 0.19633813520242643 0.32409339307592067 +102154054 0.19629133738604024 0.3242413137944315 +102253088 0.1962072227508355 0.32410784904359885 +102352122 0.1962641842859166 0.32437531185880897 +102451156 0.19637812531879179 0.32429272731907194 +102550190 0.19641935224352966 0.3243434607788519 +102649224 0.19624133134313881 0.3245125408854854 +102748258 0.19639131075157285 0.32460693875089075 +102847292 0.196315493095757 0.32455252664565015 +102946326 0.19619219287618256 0.32486198961653673 +103045360 0.1961672114677673 0.3248000057833631 +103144394 0.19638636909570178 0.32472139326359806 +103243428 0.19623120065855887 0.32503016192506257 +103342462 0.19633748874748078 0.325077509421586 +103441496 0.19615631483469403 0.3249946779939283 +103540530 0.19629205035977706 0.32520693937635753 +103639564 0.19625517982897775 0.3251137034699711 +103738598 0.1963893923102708 0.32522982107303633 +103837632 0.1964246346848858 0.3253358019326075 +103936666 0.1961126834208647 0.3257272618835337 +104035700 0.1962289550850744 0.32551634407004487 +104134734 0.19646641946939927 0.32549039092050946 +104233768 0.1963633010844418 0.32585709720629014 +104332802 0.19622633613187754 0.3257769257567453 +104431836 0.19623881764035475 0.3260307258644278 +104530870 0.196246880003976 0.3258477378029442 +104629904 0.19640130737201839 0.32601216198187083 +104728938 0.19646406577303527 0.3261609615262673 +104827972 0.19636555946853174 0.3260949562169019 +104927006 0.19643598703043497 0.32641529501963645 +105026040 0.1963482309733226 0.3262592257932336 +105125074 0.19624380830772817 0.32633036871510795 +105224108 0.19613368577398466 0.32642901429902815 +105323142 0.19636186145540602 0.32633408820005516 +105422176 0.19630534374003705 0.32671495108192294 +105521210 0.1962760958989672 0.3269473676453339 +105620244 0.1960693244949678 0.32684778330010794 +105719278 0.19623038623289937 0.3268399164755825 +105818312 0.19618036187414303 0.3267041243230543 +105917346 0.19648722453012302 0.3269153753088649 +106016380 0.19639358721088465 0.3272076233131733 +106115414 0.19633974746398958 0.32706356216649435 +106214448 0.19618876188485337 0.3271843845169827 +106313482 0.19624051303395482 0.32736455035169476 +106412516 0.1960312091034126 0.3273479730122958 +106511550 0.1959763752773775 0.3274487284871639 +106610584 0.19616319467047902 0.3273125368638869 +106709618 0.1961739468198969 0.32746355846956554 +106808652 0.19608588891992698 0.3276910761917371 +106907686 0.19621710660142025 0.328066387062912 +107006720 0.19627894360627296 0.3278150599175337 +107105754 0.19628014439323588 0.3280165334564211 +107204788 0.19647450192886792 0.3278219667279872 +107303822 0.19636560984317863 0.3279525483738302 +107402856 0.19629035899914893 0.32815808857832773 +107501890 0.19623694172704167 0.3281233546185718 +107600924 0.19629835329289877 0.3282947357258185 +107699958 0.19598601745950944 0.32829671176703257 +107798992 0.19619028839258074 0.3285304142522131 +107898026 0.19608596589613395 0.3285227022115943 +107997060 0.19619281201066477 0.32869317752694843 +108096094 0.19593117642902727 0.3285857010617342 +108195128 0.19613764301380926 0.3287135471558772 +108294162 0.19591989680069058 0.32876722175286494 +108393196 0.19611576820783846 0.32902741896601395 +108492230 0.1960363143924566 0.3289293505846242 +108591264 0.19605563358925052 0.32904331464132486 +108690298 0.19588453280410975 0.3289328475079913 +108789332 0.19585273438939943 0.3289871342826588 +108888366 0.1956983963615672 0.32922583788682097 +108987400 0.19584337384863013 0.3293553283648164 +109086434 0.19577338880206036 0.3293480564681509 +109185468 0.19586023798140767 0.3298987587073686 +109284502 0.19576464698165963 0.32955802504019843 +109383536 0.19588903298500143 0.329933221079442 +109482570 0.19563149588452836 0.3298895629480247 +109581604 0.19578566730289268 0.3300201462569077 +109680638 0.1957912462162749 0.3301047094142823 +109779672 0.19578422886283095 0.33036953877186553 +109878706 0.19563669996945038 0.3301937109709939 +109977740 0.1958770818539534 0.3305802131941199 +110076774 0.1958955299539563 0.33021093152267633 +110175808 0.19581951227406993 0.33054268249620217 +110274842 0.19581974392395834 0.3306475555887858 +110373876 0.19605291083834933 0.3307439010180209 +110472910 0.19569844517176224 0.3308139755011277 +110571944 0.19588967487284073 0.3308251532720633 +110670978 0.19583020199214343 0.330933914220878 +110770012 0.19568517017262083 0.3310811202805878 +110869046 0.19570236807088284 0.33118962510544425 +110968080 0.1957900261922454 0.3310923682382039 +111067114 0.19581950025385417 0.3312758379535011 +111166148 0.19565740461049752 0.33137951804394383 +111265182 0.19570726189537327 0.33124508889947246 +111364216 0.19563352436121598 0.331414420935351 +111463250 0.19569860346863838 0.3315842484089246 +111562284 0.19551135902501166 0.3316342484901899 +111661318 0.19563213095206294 0.33171881288785143 +111760352 0.1954436380796242 0.3319308774124123 +111859386 0.19561491766673997 0.33182049407006464 +111958420 0.19574715116894117 0.3319270027895419 +112057454 0.19562857496889313 0.33195458489391105 +112156488 0.19533241768458778 0.3320660783415074 +112255522 0.19567037557231684 0.3321769635726145 +112354556 0.19544284248387628 0.33220011234593394 +112453590 0.19544516411198873 0.332408719093099 +112552624 0.19533399271285484 0.33240593692608494 +112651658 0.19577657253138986 0.33268958420332784 +112750692 0.19568724643522917 0.33269247356420467 +112849726 0.1956758786906492 0.33290003393303297 +112948760 0.19580339371974304 0.33283437820744416 +113047794 0.19520630341566986 0.3329506330679126 +113146828 0.1954170800645215 0.3328563098440153 +113245862 0.1952782571400725 0.3331881987571346 +113344896 0.19542999051891402 0.333090662149759 +113443930 0.19523564424529205 0.33306691064708926 +113542964 0.19507975065569896 0.3333654424187699 +113641998 0.19536133591872568 0.3334713555250122 +113741032 0.19537400271705738 0.3335747960144948 +113840066 0.19526747298699595 0.33397743447474987 +113939100 0.1953352175792136 0.3337891510171362 +114038134 0.1954046990938177 0.3338621758463851 +114137168 0.19518055213958418 0.33386630286598384 +114236202 0.19526895428501656 0.33376868631463413 +114335236 0.19513825149740294 0.3341771622976617 +114434270 0.19524945345011602 0.3341267349101265 +114533304 0.19521855485896616 0.3341876068706394 +114632338 0.19510637337392536 0.3344121940474129 +114731372 0.19509356109226447 0.3342593589786948 +114830406 0.19483672732001062 0.33446993158817917 +114929440 0.194873621394793 0.3345602989033777 +115028474 0.19489262677310604 0.33495259741485933 +115127508 0.19525879688976872 0.33485038192728284 +115226542 0.19503214206378927 0.3349116260280665 +115325576 0.19481209821114082 0.33514084224537843 +115424610 0.19524288126094794 0.33529219993649184 +115523644 0.19482945838143836 0.33534930822267833 +115622678 0.19480800249608948 0.33527125050039785 +115721712 0.19516715155274947 0.3351469551947426 +115820746 0.1950472483555463 0.3356568808998777 +115919780 0.19473898474859586 0.3357596036473241 +116018814 0.19507253295697308 0.3358035703452942 +116117848 0.19483958355313483 0.33566104623759374 +116216882 0.19457071142471957 0.3357278492316211 +116315916 0.1945054386878162 0.3358635646750947 +116414950 0.19472744085135058 0.33594035220240726 +116513984 0.19485009025389366 0.3359024671439096 +116613018 0.19460371984292085 0.336037425391823 +116712052 0.19464919380432458 0.3360653425996501 +116811086 0.1946675405059259 0.33616447344150097 +116910120 0.1944895805829714 0.33624321492918285 +117009154 0.19446825987206326 0.3365958093890603 +117108188 0.1945107124779483 0.33647635441134255 +117207222 0.1943391019609544 0.3367776639731835 +117306256 0.19460994735969037 0.3369375547036471 +117405290 0.19418818254154102 0.33668896797866704 +117504324 0.19440848728691862 0.336750914199124 +117603358 0.19423621478307393 0.3370591500702564 +117702392 0.19425391558272798 0.33693920861286636 +117801426 0.19414784186919604 0.33710574630396745 +117900460 0.19390143781098265 0.3372493091642379 +117999494 0.19411192140475908 0.33724507428611955 +118098528 0.1942964126229824 0.3373564955888579 +118197562 0.19411170740946992 0.337617699133306 +118296596 0.1938816778886829 0.3377828719437272 +118395630 0.1940802731401175 0.3376664808273968 +118494664 0.19397795119691316 0.337664247810383 +118593698 0.19422915517728861 0.33809940977740643 +118692732 0.1936633628503147 0.33786067698410593 +118791766 0.19378804670781466 0.3382291068525192 +118890800 0.1939507917642807 0.33821552448934517 +118989834 0.19385380608139552 0.3383158662123652 +119088868 0.19386363286560784 0.3383657309913343 +119187902 0.19380534620804968 0.3382525301237217 +119286936 0.1937779832743579 0.33875686856193477 +119385970 0.1937691299596349 0.3387019304028824 +119485004 0.194017193256959 0.3389084098622411 +119584038 0.19364360094195948 0.33885516399314347 +119683072 0.19376148576018293 0.3389567411387815 +119782106 0.19374207089108028 0.3390577566365487 +119881140 0.19364722914473392 0.3389468181497423 +119980174 0.19339420823398293 0.33903356719521416 +120079208 0.1943523434317651 0.3397725609594743 +120178242 0.1942946426960076 0.33957123783499343 +120277276 0.19412499301457548 0.3397494454567614 +120376310 0.19414743436166815 0.3400111925102109 +120475344 0.1942592985598793 0.34002091464314 +120574378 0.19390726231975167 0.34041207728219347 +120673412 0.19387023766727712 0.34036673282102203 +120772446 0.19406453053120906 0.34012700632697807 +120871480 0.19402869532715256 0.34065628630621764 +120970514 0.19395782389725097 0.34067350681080616 +121069548 0.19375008015910022 0.3407909072677256 +121168582 0.19377087576882168 0.3408026942348611 +121267616 0.19396072565098949 0.34081330549425165 +121366650 0.19391957368002385 0.34113199529129656 +121465684 0.19375053700049952 0.3409852019207847 +121564718 0.19370160456625643 0.341064890774393 +121663752 0.19349178370893946 0.3412637125325645 +121762786 0.193758256498467 0.3414094016795598 +121861820 0.19342142181197522 0.34156166022245854 +121960854 0.19363862088891498 0.34152300337174957 +122059888 0.1934550203316364 0.34186465783901093 +122158922 0.1933419620267742 0.3415614998032925 +122257956 0.19348063719628916 0.3422551175955476 +122356990 0.19369374325268768 0.342022448169388 +122456024 0.19331789447054634 0.34224789743447015 +122555058 0.19334782412356394 0.34238909376586707 +122654092 0.19334747496906043 0.34216124331743236 +122753126 0.1932108659762469 0.34243278694247575 +122852160 0.19328046495517268 0.3425246163199633 +122951194 0.1931527140742474 0.3424155089381224 +123050228 0.19316598463142054 0.34275677138064253 +123149262 0.1930254154922616 0.3428005989469787 +123248296 0.19320276716355697 0.34277202295328896 +123347330 0.19321567823116534 0.3432225271929936 +123446364 0.19317225255192189 0.34303063077084445 +123545398 0.19284249466518494 0.34314505762068426 +123644432 0.19311533083425453 0.3430278557911865 +123743466 0.1930778644355555 0.34345677172811095 +123842500 0.19267490418683633 0.34339647468330997 +123941534 0.19286126139929088 0.34323899723674073 +124040568 0.19296483236538178 0.3440101937446899 +124139602 0.1928533998375601 0.3438725911402488 +124238636 0.19275653937490755 0.3435783630481198 +124337670 0.19256078082626255 0.34400017881783307 +124436704 0.19300657782705477 0.3439834005219614 +124535738 0.19250702508230477 0.34373983584950085 +124634772 0.19263525854182356 0.3442867944635947 +124733806 0.19261702262747146 0.344433474833476 +124832840 0.19251580668238988 0.344448340170529 +124931874 0.19254449973277443 0.3446904837944223 +125030908 0.19251040778876144 0.3444790743077356 +125129942 0.192411941645135 0.34492015629051226 +125228976 0.19257842171963765 0.3447095314402983 +125328010 0.1923431795624823 0.3449011664771885 +125427044 0.19233445009644565 0.34504837525713544 +125526078 0.19217637039626623 0.34497924801655877 +125625112 0.19219198865722897 0.3453381531530138 +125724146 0.19244791616879722 0.3454614466809204 +125823180 0.1923106643240182 0.3453533868620126 +125922214 0.19205654036053832 0.34549484118374685 +126021248 0.19221714265109816 0.34559301668953185 +126120282 0.19204129984775076 0.34562385020355796 +126219316 0.19201863159466048 0.3459135298536766 +126318350 0.19209255441768022 0.34593736367469 +126417384 0.19201876220568465 0.34591350834776385 +126516418 0.19192575361397773 0.3460149522373591 +126615452 0.191795822596152 0.346415793449146 +126714486 0.19183441244611438 0.3463090657615739 +126813520 0.19199887433585416 0.3461534810304289 +126912554 0.19164105652016675 0.34625912480883797 +127011588 0.1918449503587823 0.3465607464252008 +127110622 0.19169635114556716 0.34649855301608584 +127209656 0.1915277766166222 0.3466368824130133 +127308690 0.1917295175048316 0.34689254780599105 +127407724 0.19155716827777391 0.3470040154588144 +127506758 0.19166945497520513 0.347142640668813 +127605792 0.19153225082186082 0.3472269480166311 +127704826 0.19133825244536418 0.3469417412219457 +127803860 0.1915352890558858 0.34732692216238165 +127902894 0.19135065145263933 0.34749957467726905 +128001928 0.19148142708605753 0.34751908425200273 +128100962 0.19145255382498547 0.34800228415174594 +128199996 0.19111359673772665 0.34770112994331315 +128299030 0.19140362774104275 0.34779049596009554 +128398064 0.19121013201247786 0.34807856352602806 +128497098 0.1911503827777167 0.348246610745205 +128596132 0.19141062041375678 0.3481930091172836 +128695166 0.19106635491470061 0.3485512562688286 +128794200 0.1909239431950177 0.34834942604211966 +128893234 0.19111432665875144 0.34836686181284693 +128992268 0.19104310841329944 0.34860317748906827 +129091302 0.19100437592691955 0.34886534883637754 +129190336 0.19083478940409712 0.34888887091265214 +129289370 0.19102971035280653 0.34888371412535035 +129388404 0.19064990586504443 0.3489304249356903 +129487438 0.19089573155236986 0.3490011934522866 +129586472 0.1908813406033753 0.34909373625894824 +129685506 0.1908748467457187 0.3490654106923934 +129784540 0.1907988314036514 0.3493714199146784 +129883574 0.19048286824246813 0.3495822072671722 +129982608 0.19058191000434868 0.3496820824265072 +130081642 0.1905204476399259 0.3494693489456053 +130180676 0.1907063718627091 0.3500672212627 +130279710 0.1906215263839341 0.3500440377715457 +130378744 0.1903983344465392 0.35005331302869513 +130477778 0.19044291830782117 0.3499163502907786 +130576812 0.19054856737451809 0.350094788868816 +130675846 0.1903826838216799 0.3504578277316762 +130774880 0.19046626556704244 0.350377314185954 +130873914 0.19015577260956776 0.35051866089713135 +130972948 0.19016028899286594 0.3507331933969936 +131071982 0.19010646489022942 0.35078743998658557 +131171016 0.19017158321759967 0.3510372384782558 +131270050 0.1901332305231146 0.35080712684429377 +131369084 0.18988094221093546 0.3511080244792155 +131468118 0.19025581492483962 0.3510956960297686 +131567152 0.18994896839310615 0.35149569003229736 +131666186 0.18994329544570757 0.3515064702869233 +131765220 0.18985808563095222 0.3515982403094734 +131864254 0.1897739031431662 0.35154196943881966 +131963288 0.18982095702667798 0.35152406063576125 +132062322 0.18980637296283231 0.35165134813758414 +132161356 0.1897020507995876 0.35185517780995845 +132260390 0.18976205404333868 0.3519429288933312 +132359424 0.1898356580258734 0.35213330202221865 +132458458 0.18962242438134466 0.35231703972656303 +132557492 0.18954848523546183 0.3520940919847881 +132656526 0.18971918046926647 0.35232033659023143 +132755560 0.18939828899249334 0.3526529536160806 +132854594 0.1892958279044974 0.35251815509858575 +132953628 0.1891000422998207 0.3523463204463642 +133052662 0.18940375959331557 0.3526532291102463 +133151696 0.18918399880878556 0.3524781339151672 +133250730 0.1893493259974382 0.3528181314744563 +133349764 0.18923188709681932 0.35313012363119284 +133448798 0.18903071012262115 0.3532972586007677 +133547832 0.18917592728285915 0.3531036478409291 +133646866 0.1889311800627885 0.35333475549317866 +133745900 0.1887173361873705 0.3532482698809726 +133844934 0.1888374028307299 0.353227325227596 +133943968 0.1889420644939345 0.3531613527109894 +134043002 0.1884408295243903 0.35354736146970367 +134142036 0.18861636921358696 0.3534714310385409 +134241070 0.18868719804631157 0.3539472104544752 +134340104 0.18864018746154348 0.35401917872643224 +134439138 0.18837075299527126 0.3539061574152958 +134538172 0.18834319133920382 0.35385969026473557 +134637206 0.1883918761054749 0.35392667466746347 +134736240 0.18825643663475994 0.35436913299220363 +134835274 0.18818385413399705 0.3542051295480003 +134934308 0.18818237698535356 0.35433711727763717 +135033342 0.18790999924274113 0.3546187929059599 +135132376 0.1879762591843255 0.3546683516761667 +135231410 0.18793329047309856 0.35487816372205383 +135330444 0.18780360788918793 0.3548745384003039 +135429478 0.1882965341841264 0.3549405274089256 +135528512 0.1877978775806527 0.355054638834349 +135627546 0.18768911292056706 0.3552492048272304 +135726580 0.187344796666609 0.35541501182500124 +135825614 0.18759700085148515 0.35553321626555845 +135924648 0.18737982821522267 0.35568798070203084 +136023682 0.18754782923675103 0.35564981444393196 +136122716 0.18730871314148728 0.3560684671852514 +136221750 0.18744622876965547 0.35591073359519215 +136320784 0.18726597540959716 0.35629652136240747 +136419818 0.1871975591191668 0.3563846521967867 +136518852 0.18708717559552338 0.3563060075705835 +136617886 0.18722350884937877 0.3563577880268282 +136716920 0.18699466650982327 0.35669137000572704 +136815954 0.18700885525086186 0.3563741708004839 +136914988 0.18682219369791828 0.35696351643456314 +137014022 0.18669594582083243 0.356769779254764 +137113056 0.18690000135100315 0.35684618331949464 +137212090 0.18683359012531608 0.3569856499841107 +137311124 0.18664225928959016 0.35684564988470135 +137410158 0.18665612706377493 0.35701650019332826 +137509192 0.1866903932554165 0.3573602138655581 +137608226 0.18661071980305238 0.3573372945652124 +137707260 0.186375577186822 0.35763061005837243 +137806294 0.1863567716501876 0.3576891665527875 +137905328 0.1866808671705714 0.35773217264499435 +138004362 0.18616889305562706 0.35766280819468144 +138103396 0.18623246656989786 0.35787890142231255 +138202430 0.18608920111263227 0.3580146983586484 +138301464 0.1859085954285362 0.3580419689780472 +138400498 0.1858151719239465 0.35816066923065115 +138499532 0.1859863698837186 0.3583032106718517 +138598566 0.18592414183454808 0.3584298852425725 +138697600 0.18574575947728406 0.3584567569036109 +138796634 0.1856366824434665 0.35855628457599487 +138895668 0.18559703275921063 0.3586899053646492 +138994702 0.18580250721513622 0.35871693006327415 +139093736 0.1859007231394729 0.3584769043076056 +139192770 0.18592434922818638 0.35914439682259464 +139291804 0.1857552068594009 0.35894192549038284 +139390838 0.18562486468099085 0.3590947665114622 +139489872 0.18536434991040807 0.3590821668787864 +139588906 0.18538516582567946 0.3593082916280647 +139687940 0.1853108030791543 0.35934910826400596 +139786974 0.1853392745010806 0.3595485681565667 +139886008 0.18504952182970616 0.3597568723115563 +139985042 0.1852205280284322 0.3598593282201312 +140084076 0.1850986036198456 0.36018150907461555 +140183110 0.1848845596325288 0.35994851502609654 +140282144 0.18481079749865761 0.36032031277319154 +140381178 0.18466407958824455 0.360291987759212 +140480212 0.18458375688309014 0.3602511874231245 +140579246 0.1846701252013723 0.3601579347911772 +140678280 0.18472931188673633 0.3604006887406019 +140777314 0.184446263554401 0.36076647656869937 +140876348 0.1846230450860029 0.360871702765926 +140975382 0.18446070880177656 0.3611417724102899 +141074416 0.18461004087744137 0.36092091956865335 +141173450 0.18430567911628853 0.3612817017715909 +141272484 0.18430724897360107 0.36118024321826736 +141371518 0.18400605672318235 0.3613132965425719 +141470552 0.18428510349459937 0.36172957260086674 +141569586 0.1842589409802393 0.36147736351616105 +141668620 0.18422171385387362 0.36129326157750535 +141767654 0.1841587933142608 0.361488728603078 +141866688 0.18394522179371223 0.3621462222399877 +141965722 0.18395766942656935 0.36219045492514396 +142064756 0.18380820151137464 0.36216241137972127 +142163790 0.18401329637288472 0.36227999989316556 +142262824 0.18395487297699495 0.362448950846271 +142361858 0.18386602477833844 0.36258134405295367 +142460892 0.18364019030262524 0.3626324536845237 +142559926 0.18338763705278568 0.36268173982706126 +142658960 0.18363052132522523 0.36271449008451256 +142757994 0.18334255959713935 0.3629005154329452 +142857028 0.18313827316243897 0.3631793816780938 +142956062 0.18361060732048065 0.36332781769129063 +143055096 0.18331113730739637 0.3631027418716778 +143154130 0.1833736334104371 0.3632507462994208 +143253164 0.18325932040217682 0.36334180107690994 +143352198 0.18325068502710587 0.3634665714272804 +143451232 0.18291488998945793 0.3635788173671004 +143550266 0.1827990628926817 0.36377274517832425 +143649300 0.18280635644123425 0.3638828482108854 +143748334 0.18285640216243335 0.3638819946061461 +143847368 0.18274724890044558 0.3641326478359545 +143946402 0.18246959503122637 0.3640092710176077 +144045436 0.18240161852873946 0.3642500573505044 +144144470 0.18276099353728728 0.36440004832309514 +144243504 0.18241954522737713 0.3644659700374117 +144342538 0.18222316550553633 0.3648777178934252 +144441572 0.1823851830592884 0.364634924368721 +144540606 0.18212847877130442 0.36445986001779973 +144639640 0.18210115985201933 0.3645008674505741 +144738674 0.1821634125955238 0.3648359914480903 +144837708 0.18163936107505105 0.3646611952386646 +144936742 0.18185777524630492 0.3648289662175112 +145035776 0.1816977618754028 0.3650094103078079 +145134810 0.18156228165446167 0.36509127851116924 +145233844 0.18160325413536324 0.365408767020322 +145332878 0.18151729087219273 0.3651992401623852 +145431912 0.18132797979820744 0.3654566103819036 +145530946 0.18134861666781504 0.36550640963658876 +145629980 0.18141239153542024 0.3659715729368555 +145729014 0.1811809536575473 0.36581692829325807 +145828048 0.1812410149666919 0.366074713921361 +145927082 0.18134010194804262 0.36588330896401394 +146026116 0.1810775919612535 0.3659886206250947 +146125150 0.18087672884123077 0.3662845697795748 +146224184 0.18092408928592726 0.36655877737541187 +146323218 0.18116299151846385 0.3666289641289418 +146422252 0.18107880066355037 0.36680860129314563 +146521286 0.18090937097484833 0.367003508694348 +146620320 0.18070993987466072 0.36673742543147025 +146719354 0.18060497829599523 0.36670729872993285 +146818388 0.18074087806002204 0.3675064026095991 +146917422 0.18083797832462511 0.3674155634664587 +147016456 0.18084249205987962 0.36772983830398104 +147115490 0.18105759879747993 0.36777367166239283 +147214524 0.18070721399490677 0.367939617197719 +147313558 0.18042977457097215 0.3677602062119324 +147412592 0.18041504441160217 0.3680318315582515 +147511626 0.1804365322058839 0.36802779927633017 +147610660 0.18033228949624555 0.36834368595455863 +147709694 0.1802134121315829 0.36858478068157663 +147808728 0.1802283024806447 0.3685470767477355 +147907762 0.18024841454117665 0.3683499215978357 +148006796 0.1798901279753764 0.3687017066453789 +148105830 0.1802031381939678 0.36857992179952254 +148204864 0.17969746052611488 0.3688536780051392 +148303898 0.17996441199940405 0.3689369705980702 +148402932 0.1799170875556747 0.3690502912887293 +148501966 0.17997864784175685 0.3691974090209986 +148601000 0.17950478390427796 0.3692928562567896 +148700034 0.17955286671614418 0.3691315045224233 +148799068 0.17954768784302577 0.3694454331042871 +148898102 0.179478305374868 0.36949808641295234 +148997136 0.1792934397342979 0.3696519545799275 +149096170 0.17900791583041442 0.36985266134418415 +149195204 0.17888158105448781 0.3699310005577959 +149294238 0.17947387789980906 0.36995550395810417 +149393272 0.17917475971222258 0.37017843248101184 +149492306 0.17924081599319308 0.3701072690250871 +149591340 0.17908709193495176 0.37043881110688615 +149690374 0.17892365806069443 0.37055107621704236 +149789408 0.17877902497838943 0.37054474855338465 +149888442 0.17876597945477027 0.37052648212346295 +149987476 0.1787636762363938 0.37102110527066207 +150086510 0.17871238145910162 0.3708630280205588 +150185544 0.17869242154016232 0.3710066833776006 +150284578 0.17872305087361076 0.37125308654664524 +150383612 0.17869827425213894 0.37135860641791957 +150482646 0.17837569686719826 0.37178457933438175 +150581680 0.17837006875429773 0.371333713993087 +150680714 0.17841906780834835 0.3719644260343 +150779748 0.17816233260539394 0.37194675266198607 +150878782 0.17844203862531766 0.3720275144573134 +150977816 0.17832409707767705 0.3719945152534835 +151076850 0.17801505456267933 0.3723282461367621 +151175884 0.17778842573676093 0.37228045175341046 +151274918 0.17804914580013234 0.37249469191648965 +151373952 0.1778296071762815 0.37258719793805994 +151472986 0.17782494933448492 0.37289658618788013 +151572020 0.1776117804399633 0.3728681335923318 +151671054 0.17769343077965072 0.372860162142081 +151770088 0.1774873437405453 0.37292912854836235 +151869122 0.17724360845466913 0.3729245946463238 +151968156 0.17733455809958795 0.3729114523936698 +152067190 0.17761761805505202 0.37318181244011045 +152166224 0.17731751274939112 0.37306261273677765 +152265258 0.17729812795526215 0.3733251247121701 +152364292 0.17733506267037474 0.37345200046186566 +152463326 0.17701105615671162 0.37375090748161766 +152562360 0.17680756379663992 0.37359313543279354 +152661394 0.17671969345937177 0.3738102288228032 +152760428 0.17643754516905635 0.3736539596431989 +152859462 0.17660962382517895 0.3738198975792025 +152958496 0.1766337123478937 0.3740199843525206 +153057530 0.17637399130274667 0.37414168724764424 +153156564 0.17636904430874173 0.37426452830882156 +153255598 0.17616875048266362 0.37426355173520665 +153354632 0.17650338153154294 0.374493188721493 +153453666 0.1765291270710905 0.3743378345813941 +153552700 0.17654206020854682 0.37478405196491293 +153651734 0.1761868028830018 0.3748120559691135 +153750768 0.17583038229689654 0.37493967450195426 +153849802 0.1762422525383649 0.37506735546844305 +153948836 0.17598505657288094 0.3749601565772778 +154047870 0.1761852072839449 0.3752221980955541 +154146904 0.17598004020981423 0.3754339784369582 +154245938 0.17571647892987488 0.37565182500268646 +154344972 0.17589548063160504 0.3756125390606146 +154444006 0.17577003199575136 0.37556443190053723 +154543040 0.1756103448987806 0.37582341770396727 +154642074 0.17562377473223595 0.37588195604897473 +154741108 0.17547007258066816 0.37619742691238 +154840142 0.17525983965647174 0.37627452725645155 +154939176 0.1751022538872034 0.37605202506035557 +155038210 0.17522284408666525 0.37656685948850116 +155137244 0.17506704399013046 0.3765402214579569 +155236278 0.1748747888129539 0.37633922818576754 +155335312 0.17477367406517236 0.3767300932742041 +155434346 0.17505344243053636 0.37663746828131706 +155533380 0.1747608221798137 0.37694923906125594 +155632414 0.17469106548810376 0.3770872210501848 +155731448 0.17482951908921543 0.3773244817581974 +155830482 0.17480586500687598 0.3771203916770377 +155929516 0.17438172228717255 0.37753165690119805 +156028550 0.17426018424492187 0.3773842435612683 +156127584 0.1742916382151757 0.3774992767116389 +156226618 0.17432140797680076 0.3777991675981728 +156325652 0.17425443994408632 0.3779009468892263 +156424686 0.17379431807052334 0.3781031346489717 +156523720 0.1739457310215303 0.37806996860981323 +156622754 0.17407513193205842 0.3781101887171157 +156721788 0.17411198325932528 0.378181448734229 +156820822 0.1738465420099399 0.37821041398204946 +156919856 0.17363479684949218 0.37837844899639395 +157018890 0.17374879519848238 0.37871118506124757 +157117924 0.17382527375620013 0.3783780659413451 +157216958 0.1735531875334246 0.37888582731357473 +157315992 0.17318774498817224 0.3787035475490745 +157415026 0.17318285430350622 0.37901128467254414 +157514060 0.17342680362366805 0.3790827634175839 +157613094 0.17332917817025603 0.37913510526253635 +157712128 0.17326898665685583 0.37920421911420693 +157811162 0.17303021706562627 0.37935234440303556 +157910196 0.17334801437487263 0.37920378262720883 +158009230 0.1727830385737355 0.37968683583077756 +158108264 0.1730791065416823 0.37981122349796864 +158207298 0.17276070919690273 0.37970200052664344 +158306332 0.17284045880322432 0.3802994277341476 +158405366 0.17281308871427478 0.3802625348334041 +158504400 0.17264517805831847 0.3802284632107423 +158603434 0.17256216114662382 0.38035706833910304 +158702468 0.17252782573343395 0.3806442012995065 +158801502 0.1723732567182902 0.3803777909288966 +158900536 0.17221194128320014 0.3806405580267598 +158999570 0.1718765152033813 0.38040074390700906 +159098604 0.1724091486582776 0.3811750560181154 +159197638 0.1717370256708825 0.3808796210554756 +159296672 0.17219831950473521 0.38104155876396517 +159395706 0.17213044623605425 0.381313770994719 +159494740 0.17178734087507716 0.3813501818227208 +159593774 0.1717215473054759 0.38151319401915534 +159692808 0.17199984003825786 0.38174523351697365 +159791842 0.17170718653254477 0.3816687575756857 +159890876 0.1714648673584595 0.3816968361267209 +159989910 0.17111830139741216 0.38190352757937546 +160088944 0.17142928366648372 0.38239588908677546 +160187978 0.1715406322686448 0.381930197249036 +160287012 0.17116241639169158 0.3823530021340302 +160386046 0.17100156085900597 0.38239459365826106 +160485080 0.17103623287204392 0.38235037981585457 +160584114 0.17085725313805472 0.3821465622305535 +160683148 0.1714597576281517 0.3826180517354958 +160782182 0.17079197509291444 0.3828320046747262 +160881216 0.1709187538946648 0.38250953286135253 +160980250 0.17066648664795342 0.3832675545354801 +161079284 0.17062362876655943 0.38307337386632734 +161178318 0.17047311159109085 0.383143734154192 +161277352 0.17034178656929336 0.3832909887425476 +161376386 0.1700291531764863 0.3832452396402778 +161475420 0.17029126541380382 0.3835564224814944 +161574454 0.17000088147780618 0.38350925123551477 +161673488 0.17006081434004647 0.3835594698377219 +161772522 0.16996427239251685 0.38384661230864375 +161871556 0.16991706671915666 0.3841884193861548 +161970590 0.1699496686634684 0.383896461200884 +162069624 0.1697756979097601 0.3842967559290549 +162168658 0.16957937314230984 0.38415702497801113 +162267692 0.16912660696186052 0.3844939811849896 +162366726 0.16983342801798668 0.3845955103694774 +162465760 0.16963631710573338 0.3847247908763084 +162564794 0.1693933104569157 0.3848810745855368 +162663828 0.1694989573172116 0.3844616905037692 +162762862 0.16903363269398924 0.38514572877675485 +162861896 0.16909014255116742 0.3849790293980411 +162960930 0.16911456179965872 0.3851111492981061 +163059964 0.16885481117804532 0.38524845819581405 +163158998 0.1687427891903465 0.38522064764598307 +163258032 0.16898776129526974 0.38531000372620555 +163357066 0.169013911192389 0.3856927839503679 +163456100 0.16855674081845282 0.385869191051671 +163555134 0.16820649299717022 0.3860620512256951 +163654168 0.16833668408507171 0.3858716904867785 +163753202 0.1684915943654423 0.386090559850515 +163852236 0.16828354823050626 0.38625354262236855 +163951270 0.16801860006742764 0.38638529403434946 +164050304 0.168225330240768 0.3864096279724459 +164149338 0.16832413675709681 0.3864826752001386 +164248372 0.1683060045555922 0.38658707410759224 +164347406 0.16803588741191744 0.3868607342941686 +164446440 0.16756627493210127 0.3870361993212117 +164545474 0.16777087047120032 0.38700530725272375 +164644508 0.16720053744397292 0.38697263514438157 +164743542 0.16777550966456184 0.3869886863212394 +164842576 0.1673127821674917 0.387402125781478 +164941610 0.1673777606745769 0.3871515365984378 +165040644 0.16731369738020455 0.38749946383307865 +165139678 0.16727158528133637 0.3879138920837325 +165238712 0.16727638241657872 0.38742673076437933 +165337746 0.16710728401843405 0.3878896229189195 +165436780 0.1668594942906335 0.3881459254137634 +165535814 0.1668193881253491 0.38792965477668184 +165634848 0.16686710899370008 0.3880997518175746 +165733882 0.16687643161943957 0.3883279183090525 +165832916 0.16685837069446552 0.3882832653192966 +165931950 0.16647021554381455 0.3885000339120622 +166030984 0.16637298816596438 0.3884856731121906 +166130018 0.16644913494577454 0.3887145428225564 +166229052 0.16609559250692438 0.388912894878205 +166328086 0.16616269305919273 0.388886151208575 +166427120 0.16584425419479357 0.3886288172221885 +166526154 0.16593266594311498 0.3891316058632574 +166625188 0.16547213867954408 0.3891133536329249 +166724222 0.16592115956631343 0.3894089307376809 +166823256 0.16593349473622435 0.38940457884216667 +166922290 0.16551564816167946 0.3895845632454928 +167021324 0.1653388026556029 0.3893729287090471 +167120358 0.16542866618398278 0.3896797810998754 +167219392 0.16576795476038345 0.3899596822848986 +167318426 0.16534163972784857 0.3900531065478724 +167417460 0.16516392199514562 0.390134337302807 +167516494 0.16495509866870975 0.3902772057666276 +167615528 0.16538557124635953 0.39026181371788765 +167714562 0.16494194658324104 0.3903439705620865 +167813596 0.1648743948038161 0.39055891988061114 +167912630 0.16515595011380305 0.3906895631268386 +168011664 0.16520434488905536 0.3907284585578044 +168110698 0.16478966676638848 0.3908871430751239 +168209732 0.16489721368291055 0.3911459341429673 +168308766 0.16461341251907963 0.3909860332441357 +168407800 0.16409960162160517 0.39146411969606326 +168506834 0.16455937159342962 0.3913131552015285 +168605868 0.16415125435057915 0.3911779313680168 +168704902 0.1641173147114141 0.3917490661019452 +168803936 0.16423497638098183 0.39169963673499375 +168902970 0.16427372307589364 0.39179409390563524 +169002004 0.1635879006577855 0.3920660293327261 +169101038 0.16361220001967192 0.39217273597179564 +169200072 0.16357447313259899 0.39224335816267597 +169299106 0.16364336385945988 0.39206277358866903 +169398140 0.16370941548970946 0.39231509430599104 +169497174 0.1630429212484171 0.392424727712726 +169596208 0.16338223624780115 0.39244517895947284 +169695242 0.16350450330866712 0.3926506511760147 +169794276 0.16329204384734036 0.3926625282774725 +169893310 0.1630586941785168 0.3929921720113429 +169992344 0.1627774967010655 0.392970193972787 +170091378 0.1624080895552603 0.39305210395480533 +170190412 0.16280953482688013 0.39298393288484634 +170289446 0.16260774303311407 0.3932520216485826 +170388480 0.1624867451693106 0.39348453496569796 +170487514 0.16252798276924987 0.3933945069265263 +170586548 0.16233643588774374 0.3938956449349375 +170685582 0.1621256688074348 0.39405219928648577 +170784616 0.16227742749800764 0.39390841395003895 +170883650 0.16214938018522607 0.394018681106788 +170982684 0.16173601147832445 0.39432593521620685 +171081718 0.16206959163684373 0.3942638170413298 +171180752 0.16200135286564152 0.3942509957211414 +171279786 0.16180237418244536 0.39440534667405436 +171378820 0.16169263106073603 0.3948157532059177 +171477854 0.161614062174767 0.39469545372948767 +171576888 0.16123235837380556 0.3945060420488429 +171675922 0.1610653627645669 0.3947041428162339 +171774956 0.16134934904116588 0.39471791206863843 +171873990 0.1612674134593445 0.39515928572559567 +171973024 0.16090733416998454 0.3955428571456396 +172072058 0.160929278036938 0.39521641016398573 +172171092 0.1608425820524274 0.39575658068013475 +172270126 0.16097818345903622 0.3958520811092199 +172369160 0.16044125080480529 0.39576537101000786 +172468194 0.16049775654435883 0.39610651230413085 +172567228 0.1606106536852997 0.3960806944437716 +172666262 0.16022393870198065 0.39571761939040906 +172765296 0.16041908125876161 0.39622039445620555 +172864330 0.16021690599954522 0.39626746506093014 +172963364 0.16023723840689597 0.3964075213077735 +173062398 0.16003336447429659 0.3965921659097529 +173161432 0.1600354788586057 0.39670108230791157 +173260466 0.15996302133726328 0.3967795822885067 +173359500 0.15952472224557349 0.3965196680315082 +173458534 0.15986799491064055 0.3969050411518787 +173557568 0.15961060772964786 0.39708566785339144 +173656602 0.15958335425902972 0.39681340723534986 +173755636 0.159220060526337 0.39710911827281287 +173854670 0.15950646962729823 0.39775942760289923 +173953704 0.15951457484127615 0.39764756781788085 +174052738 0.15951907480726266 0.39747241514887416 +174151772 0.15917077762369908 0.39770436234101925 +174250806 0.15907599304118494 0.39802734671944934 +174349840 0.1587953056945016 0.3980028498383468 +174448874 0.15864597948271258 0.3980343113651487 +174547908 0.1587564885049299 0.39788598681968346 +174646942 0.15866431669565467 0.3984815521500238 +174745976 0.15874852022386512 0.3984506837578455 +174845010 0.15844689317032426 0.3984172528809258 +174944044 0.1581582974411407 0.39849514679030695 +175043078 0.15820254239597423 0.3988603165977249 +175142112 0.15811969368940568 0.3990892949622818 +175241146 0.15819060834948548 0.39914276568377843 +175340180 0.15787841382582848 0.3992027377270644 +175439214 0.15792492534568595 0.39940433821605215 +175538248 0.15774252007996728 0.39921318878018064 +175637282 0.15748892682486854 0.39925652581469107 +175736316 0.15755410611541537 0.3995120906537315 +175835350 0.15743934014175173 0.3997410201699015 +175934384 0.15727759872027433 0.3996971751853763 +176033418 0.15738923438252383 0.3998599661983001 +176132452 0.1571603684789859 0.4002813809136781 +176231486 0.15729814097227507 0.40013584457215545 +176330520 0.15718706022890597 0.4003114099504555 +176429554 0.1569024802689282 0.40009849309317974 +176528588 0.15660963319492496 0.40037616191235526 +176627622 0.15664361404063568 0.40060993746763585 +176726656 0.1567643325872404 0.40064997810858805 +176825690 0.1564115574984341 0.4005446713203013 +176924724 0.15624624503459444 0.4011475696481196 +177023758 0.1561477664698685 0.40083918558676374 +177122792 0.15597398887834604 0.4012374241728038 +177221826 0.15614663288318342 0.40115598592273216 +177320860 0.15584360264442668 0.4011990407098863 +177419894 0.15593280833756726 0.4013450555610015 +177518928 0.15588324328908182 0.4014949240096797 +177617962 0.15560484063764385 0.4020532950494137 +177716996 0.15561466431029336 0.40163333553074276 +177816030 0.15540986060913586 0.40157438261515477 +177915064 0.15554827635262916 0.4021341310205585 +178014098 0.15504950506310736 0.4020606044740019 +178113132 0.1550633459315851 0.4022632085034396 +178212166 0.15498071987608866 0.4024082930232483 +178311200 0.15503914106830102 0.40235047744514524 +178410234 0.1550070103772656 0.40263365266364937 +178509268 0.154885003776947 0.40264188263508927 +178608302 0.15485122619219113 0.40307122110793936 +178707336 0.15478556422841405 0.40293238364654815 +178806370 0.15453098289676945 0.40318535924521604 +178905404 0.15475472569314255 0.4032566070663842 +179004438 0.15437851111432585 0.40315079665153375 +179103472 0.1541978411396742 0.40335751161342415 +179202506 0.15401047223870892 0.4036328340728662 +179301540 0.15413931873974027 0.40320810289708264 +179400574 0.1540141712393669 0.4037669964743399 +179499608 0.15385032367470808 0.4039356557215839 +179598642 0.1537963903582955 0.4043578087690025 +179697676 0.15382005469874743 0.40395508364130533 +179796710 0.15336987950222297 0.4041445844161215 +179895744 0.15335740218848073 0.40444247393525734 +179994778 0.1532346110279667 0.4044440367437256 +180093812 0.1532575828838954 0.40465325631911614 +180192846 0.1533259590312063 0.40447177355769115 +180291880 0.15331184824159608 0.4050182844010998 +180390914 0.15315374746862903 0.4050071680950564 +180489948 0.15302009445324813 0.4051411028825221 +180588982 0.15264605244390306 0.405769604470247 +180688016 0.15249212508276688 0.4052485638943144 +180787050 0.15285389129494817 0.4056590732173589 +180886084 0.1526020512434956 0.4055609693081067 +180985118 0.15216961010306088 0.40561691619524787 +181084152 0.15261459131722122 0.4055592161921029 +181183186 0.15247870490792334 0.4056583554942544 +181282220 0.15225144644391692 0.40615632933085893 +181381254 0.15215335868893295 0.4060291552149155 +181480288 0.1522124769033838 0.4062042423475208 +181579322 0.15189557785084803 0.4068605046919135 +181678356 0.15198981333835798 0.4066886881864404 +181777390 0.15176717777901616 0.4066741065560728 +181876424 0.15151482692764978 0.4070454083055051 +181975458 0.15155883730260175 0.4069942402694285 +182074492 0.15139799952988445 0.4072193781911274 +182173526 0.15118829334631959 0.40711889832689807 +182272560 0.15126405667091944 0.4074035887725615 +182371594 0.15118396626096964 0.4073821337678123 +182470628 0.15100707148290946 0.4073271100190484 +182569662 0.15094730712572862 0.40764636308906554 +182668696 0.15088077857021365 0.40762656052752116 +182767730 0.1508974720324109 0.4076171947455751 +182866764 0.15066143105225863 0.4078371687168602 +182965798 0.15053069989906273 0.4081203749425412 +183064832 0.15036202810783308 0.4083515152174479 +183163866 0.15015416594361822 0.40829332884003283 +183262900 0.1502620019182581 0.40827715933720615 +183361934 0.15021777511757528 0.40829998011903035 +183460968 0.15010269216242875 0.40870905425998755 +183560002 0.14972673166121647 0.40862339370446005 +183659036 0.1498724780734997 0.40919274114445114 +183758070 0.14950258486646223 0.40857440456655986 +183857104 0.14953253111852957 0.4091539669214118 +183956138 0.14946566644465348 0.40906230548562733 +184055172 0.1494369769882999 0.40940475429702344 +184154206 0.14912067284876424 0.4096407990382345 +184253240 0.14891037067783505 0.40953918705898784 +184352274 0.1491922745426519 0.40977510218680435 +184451308 0.14911194438620057 0.409563972187374 +184550342 0.14889307740500826 0.40994278032747083 +184649376 0.14898639253377097 0.4099480636233435 +184748410 0.14862088996255313 0.4099543410844779 +184847444 0.14860780984002464 0.4101730813057124 +184946478 0.14863239487042285 0.41040236909617195 +185045512 0.14842733721688062 0.4101988058999021 +185144546 0.14820032920960524 0.41073186425715663 +185243580 0.14824057779476524 0.41055425614311436 +185342614 0.14823048758468294 0.41067890562416925 +185441648 0.14803802886770046 0.41086785501213546 +185540682 0.1478751389086092 0.4110627835725259 +185639716 0.14776639340474348 0.41061208000641325 +185738750 0.14773454834939623 0.41122093492530565 +185837784 0.14725947900278716 0.4111044026879816 +185936818 0.14734686789397175 0.41154131017581086 +186035852 0.1474509701056568 0.4114372418272398 +186134886 0.14739757405703774 0.41174345644096944 +186233920 0.1471786708823876 0.41209321026015744 +186332954 0.14687578181411184 0.41171624412484537 +186431988 0.1469161880894998 0.41198363328910814 +186531022 0.14701848819573138 0.4119704670720097 +186630056 0.14649994461461135 0.4118841638391588 +186729090 0.14650253694719556 0.4124311528669256 +186828124 0.14686981213656064 0.41245505292631485 +186927158 0.14647633957329806 0.4123347355196372 +187026192 0.14621274209970178 0.4130604882172275 +187125226 0.14651288929000975 0.4125245481681104 +187224260 0.14602538257292194 0.4126928743844943 +187323294 0.14590475258532676 0.4129013167592477 +187422328 0.14614959786819776 0.41289660484229707 +187521362 0.14579142636057058 0.4132991468639423 +187620396 0.14565613620271844 0.413301612958762 +187719430 0.1454138999863333 0.4133023099205736 +187818464 0.14527367045797918 0.4134536290706285 +187917498 0.1452196700498205 0.4134451878422925 +188016532 0.1453192734165691 0.41361670259521355 +188115566 0.14513159545378165 0.4138899515127764 +188214600 0.14491665781355206 0.41405193043318556 +188313634 0.1447423151386024 0.41396701529047647 +188412668 0.14488205184540265 0.41419558081665664 +188511702 0.14473750372564806 0.4142743319634537 +188610736 0.1445690678926074 0.4144731110049421 +188709770 0.14424227707430712 0.41464448980556434 +188808804 0.14437036270786302 0.41444669846482846 +188907838 0.14426682899516335 0.41479280906220184 +189006872 0.14392145846009202 0.414877831544981 +189105906 0.14416295704784446 0.41536573731582205 +189204940 0.14406073399616198 0.41503808544854054 +189303974 0.14407135230050935 0.4150201473398903 +189403008 0.14374934011568882 0.41530593104918 +189502042 0.14378057859446772 0.41554059615761363 +189601076 0.1434232675249787 0.41578800807739735 +189700110 0.14322046125145538 0.4161097297786191 +189799144 0.14317034724400923 0.4157392820206712 +189898178 0.14329778806901175 0.41579499639073103 +189997212 0.14314807095633814 0.4159014079236744 +190096246 0.14332906824777092 0.41654901555068463 +190195280 0.1433878617857742 0.416267884742786 +190294314 0.14329583882915808 0.4164730417911839 +190393348 0.14266709697089652 0.4166148372180548 +190492382 0.1429109276757009 0.41671481976147595 +190591416 0.1428526680176257 0.4170246486337383 +190690450 0.14289421421138965 0.417088815154661 +190789484 0.14257799512238345 0.41689284378833064 +190888518 0.14238209769056534 0.41710921195244627 +190987552 0.14197491079768831 0.4173467603524629 +191086586 0.14234511506511535 0.4174517955594586 +191185620 0.14217653071104966 0.4174820697883232 +191284654 0.14188955332273315 0.4175536076962317 +191383688 0.1419555376898774 0.4179708934388642 +191482722 0.1414106108906664 0.4181758668545236 +191581756 0.1416972644512109 0.41819102603171204 +191680790 0.1416797872234208 0.41822500994454204 +191779824 0.14165717305146877 0.4181104091067723 +191878858 0.14114740932883044 0.41842041525545914 +191977892 0.1413512601918031 0.4183889771032038 +192076926 0.14103647266283215 0.4186500596736239 +192175960 0.14134078591525245 0.41878416454865397 +192274994 0.14099234668840047 0.4188423916966435 +192374028 0.14080226929422227 0.41904803830117615 +192473062 0.1403500815148266 0.41893914551056566 +192572096 0.14046622966397732 0.4191877126685954 +192671130 0.14034479901682154 0.41930085703572284 +192770164 0.14035198229357002 0.41940506077106043 +192869198 0.14023976933340127 0.4194140591002685 +192968232 0.1401299496943792 0.41978082767414643 +193067266 0.14023733700799235 0.41960633405182435 +193166300 0.14008220360544335 0.4200949076499998 +193265334 0.1398020148178829 0.41994896884519406 +193364368 0.13964250002011444 0.4197913656703781 +193463402 0.13958106686812916 0.42004988286530903 +193562436 0.1396810336569519 0.42005942573029037 +193661470 0.13952891828369465 0.4204778018572249 +193760504 0.13913661567258498 0.4206920935659629 +193859538 0.1391445462897334 0.4207775666305571 +193958572 0.13920996449103004 0.42097994164598124 +194057606 0.13889069072167962 0.4209270266476613 +194156640 0.13885360998786864 0.4208536257729164 +194255674 0.13881901651529882 0.4209962697709639 +194354708 0.1387432506228338 0.42128372914557966 +194453742 0.13876434457530604 0.42140451088088465 +194552776 0.13838555858782556 0.42163459295250355 +194651810 0.13855114187698245 0.42184778097919085 +194750844 0.1383537312946368 0.42142786260617215 +194849878 0.13813362286752096 0.422019647183455 +194948912 0.13832890658001576 0.4220470034916318 +195047946 0.1378440376872905 0.4222298514565506 +195146980 0.13821449656210338 0.42250567407234924 +195246014 0.13770105337411712 0.42218601391437005 +195345048 0.13746330220014724 0.42253754797770376 +195444082 0.13754326621217466 0.4226062244194654 +195543116 0.13701850069327198 0.42268461506287697 +195642150 0.13707280189301257 0.42284916949538986 +195741184 0.13720267192401428 0.4228278897129342 +195840218 0.13700227555262634 0.42316324673988864 +195939252 0.1366890820430805 0.42322122085346986 +196038286 0.13678962774888376 0.4229744098835971 +196137320 0.136847565455407 0.4232675324310744 +196236354 0.13635965045133608 0.423376982517945 +196335388 0.1363107228991007 0.42332000953267096 +196434422 0.1364958457299513 0.4236313122027245 +196533456 0.13615107070926508 0.4235199398528883 +196632490 0.13644252988397673 0.4238729676509986 +196731524 0.13578977857419564 0.42391868311028674 +196830558 0.13600123990048088 0.42424690904770695 +196929592 0.1359448326492317 0.4245124600177534 +197028626 0.13584274345620373 0.4241110760281926 +197127660 0.13576687347393032 0.42451532812312437 +197226694 0.13584346960067478 0.42473299060593095 +197325728 0.13555477743288394 0.42469537348377434 +197424762 0.1351720205353612 0.4248937672966619 +197523796 0.13525959558407852 0.4244790617883551 +197622830 0.13480759339550955 0.42503058932022453 +197721864 0.13491211288226806 0.4254020763181194 +197820898 0.13514747695103216 0.425082649540459 +197919932 0.13429435404826268 0.4249949252620509 +198018966 0.13444819610660075 0.42540795105109075 +198118000 0.13452082927788098 0.42502094571428517 +198217034 0.13463178825338026 0.42556531548432597 +198316068 0.13434766046365887 0.4258497435592025 +198415102 0.1341861099898892 0.4258130369348596 +198514136 0.13427983281291614 0.42624899148290096 +198613170 0.13378745759326463 0.42616571003395076 +198712204 0.13375709555333262 0.42645220136615886 +198811238 0.13396739644746672 0.4264053561527298 +198910272 0.1338214808453904 0.4262379315614749 +199009306 0.13378397864002586 0.42639871223018894 +199108340 0.13324210976724934 0.4266715343414341 +199207374 0.13329882033719423 0.4270209229249888 +199306408 0.1331405857918023 0.42707171730333227 +199405442 0.13330614185491263 0.4270375400226716 +199504476 0.13296228745501354 0.42702459542447685 +199603510 0.13291235826879996 0.42732604896291215 +199702544 0.13288592447429193 0.4275049125961961 +199801578 0.1329774288930896 0.42777978096649205 +199900612 0.13246782967224358 0.42743550730120183 +199999646 0.13213678251106709 0.42752343587533653 diff --git a/test/data/t130-2.s1p b/test/data/t130-2.s1p new file mode 100644 index 00000000..17f63b60 --- /dev/null +++ b/test/data/t130-2.s1p @@ -0,0 +1,2021 @@ +# HZ S RI R 50 +50000 -1.0065668550744071 -5.21847118919505e-05 +149034 -1.0066705898801684 0.00017913690339119525 +248068 -1.0067465358167211 0.0003192657007548011 +347102 -1.0068191718433088 0.0004281496189623205 +446136 -1.0065324277806702 0.0004370824506697228 +545170 -1.0069024978595014 0.0008467657185666356 +644204 -1.0068559537998123 0.0009661336630044716 +743238 -1.007036169057217 0.001320173164345892 +842272 -1.0069437110855814 0.001501879660377009 +941306 -1.007053517634155 0.0016942492259571075 +1040340 -1.0070309339917745 0.0017267723292485634 +1139374 -1.0069831415745816 0.0020713995657102788 +1238408 -1.0070382788759202 0.0023568142302916803 +1337442 -1.0071791182583378 0.0024601009047245208 +1436476 -1.007185770073967 0.002744092764231976 +1535510 -1.0069639958847563 0.002886615098440211 +1634544 -1.007135599340358 0.002990373375181818 +1733578 -1.0071906431521296 0.003402191459515937 +1832612 -1.0071004200750917 0.00366307484008775 +1931646 -1.0072864596349638 0.003694829357133327 +2030680 -1.0072223321586296 0.004018313544478848 +2129714 -1.0072476702974622 0.004181225553262037 +2228748 -1.0073014738030124 0.00436206771679868 +2327782 -1.0073956143358505 0.004712196456083312 +2426816 -1.0074773354416429 0.004796858892226288 +2525850 -1.007523922908287 0.004977309787127393 +2624884 -1.0073320727603272 0.005289969206601126 +2723918 -1.0075861590832595 0.005600307105965544 +2822952 -1.0075536972844898 0.005757540360779306 +2921986 -1.0075319323040721 0.005841182870720604 +3021020 -1.0075301695752916 0.00615584081205365 +3120054 -1.0076614316019583 0.006357823958579462 +3219088 -1.0075064123786772 0.006607611842001179 +3318122 -1.00759719855141 0.006718346859290074 +3417156 -1.0076252294616816 0.007067202103357582 +3516190 -1.007697149437979 0.007209155489619754 +3615224 -1.0077372050267246 0.007408543844291445 +3714258 -1.0076399518558745 0.007536613455660791 +3813292 -1.007933018385103 0.007793450203543804 +3912326 -1.0077692547524355 0.008090500358417508 +4011360 -1.0076862888186782 0.008286834825717538 +4110394 -1.0078186581085713 0.008617645073253272 +4209428 -1.0079253064512799 0.008712865187057491 +4308462 -1.0078190944298087 0.00872362453242541 +4407496 -1.0076428422207007 0.009089486525820843 +4506530 -1.0078579311168498 0.009318760688373619 +4605564 -1.0078505850757924 0.00959557726165594 +4704598 -1.0077232612867424 0.009787532629874406 +4803632 -1.0077683793013927 0.010072821910307497 +4902666 -1.0076831907816324 0.010204939419592123 +5001700 -1.007797710488794 0.010336209583120481 +5100734 -1.0077821051745246 0.010634189743553544 +5199768 -1.0078106568535528 0.010797871916087925 +5298802 -1.007827494396194 0.011120391649640756 +5397836 -1.0079662603417885 0.011411431068234246 +5496870 -1.007914094443245 0.011718016164078313 +5595904 -1.0078080176383553 0.011871942034229092 +5694938 -1.0078228668729552 0.011966868648793948 +5793972 -1.0078448891897527 0.012288029602743493 +5893006 -1.0078685924438375 0.01244692769366668 +5992040 -1.0078652611573387 0.012618656633310871 +6091074 -1.0078618827597459 0.01276901748871633 +6190108 -1.007938048682702 0.01304661643845366 +6289142 -1.0080078497270146 0.013275910048124064 +6388176 -1.0079618275070505 0.013435238886366984 +6487210 -1.0080564694058074 0.01364741032011286 +6586244 -1.0079015336342434 0.013962089832800891 +6685278 -1.0079012040116442 0.014298233307640656 +6784312 -1.007954703764716 0.014484818289696241 +6883346 -1.007858891786778 0.014732617260100188 +6982380 -1.0078787312938002 0.01479935164720461 +7081414 -1.0083222031663546 0.015051355139813096 +7180448 -1.0081731260695077 0.015260806595802126 +7279482 -1.0081236289666462 0.015412763718780096 +7378516 -1.008075175007506 0.015700521531907506 +7477550 -1.0082127817571696 0.015873862796949266 +7576584 -1.0080716899025228 0.016050158756237325 +7675618 -1.0081648973249926 0.016420726993847976 +7774652 -1.0080955966767902 0.016462902053208068 +7873686 -1.0081713178681402 0.01673079571859329 +7972720 -1.0081637064509086 0.017085584376372365 +8071754 -1.0082919707959186 0.017211233773304295 +8170788 -1.0081250030868039 0.017543795885015395 +8269822 -1.0082557857769956 0.017751223482696973 +8368856 -1.0080619272784868 0.017887868328909203 +8467890 -1.0083813896467162 0.01815272178330156 +8566924 -1.0084517834565367 0.018333286442355195 +8665958 -1.0083591230443298 0.01840502187587741 +8764992 -1.0083095067739047 0.018908781947598576 +8864026 -1.0084186316278085 0.01889009798046868 +8963060 -1.0083581236547603 0.019221711799568383 +9062094 -1.008296573212954 0.019437364874121833 +9161128 -1.0083664249697724 0.01971172248933552 +9260162 -1.0083889663908299 0.01985349204609129 +9359196 -1.008340262164755 0.020186124248718695 +9458230 -1.0083838085824433 0.020373329260504242 +9557264 -1.0085627452308192 0.020468012629481967 +9656298 -1.008679410935151 0.020685827957425594 +9755332 -1.0087201356318904 0.020907754804674637 +9854366 -1.0087315409591737 0.021153337616485232 +9953400 -1.0088308958863672 0.02136541387193382 +10052434 -1.0081389131511698 0.02200816701644593 +10151468 -1.0081402248625324 0.021717148492085933 +10250502 -1.008112948345486 0.02223713288588422 +10349536 -1.008267124497113 0.022254911328401766 +10448570 -1.0081236028318077 0.022528434338273572 +10547604 -1.008132896195514 0.022715087996868503 +10646638 -1.0082654509893578 0.022869149281963916 +10745672 -1.0082460576327708 0.023165153149516064 +10844706 -1.0082757045740525 0.023407588536500566 +10943740 -1.0083054207949274 0.0236616266831292 +11042774 -1.0082398154084398 0.02378988402164129 +11141808 -1.0082845773945328 0.024008355532086297 +11240842 -1.008267221862981 0.02428883071289177 +11339876 -1.0081647259062492 0.024545619916611465 +11438910 -1.0082780952091537 0.02462229541192619 +11537944 -1.0084517876921513 0.024930107946539305 +11636978 -1.0083269555161225 0.02517242782358692 +11736012 -1.0085108689275415 0.025284771746371747 +11835046 -1.0083017017001183 0.025548866707084367 +11934080 -1.008402681491382 0.025813650572479693 +12033114 -1.0084164335594552 0.02595659507658974 +12132148 -1.0084744755087531 0.026312485845287382 +12231182 -1.008531459049904 0.026529716579307097 +12330216 -1.0083732087267239 0.0267615400959567 +12429250 -1.0085637824145424 0.02701339943729648 +12528284 -1.008619142089878 0.027096094651414982 +12627318 -1.008717856846068 0.027189807632115225 +12726352 -1.0087827240285536 0.027559895562796886 +12825386 -1.0087113391498679 0.027663172988449017 +12924420 -1.0088180471546837 0.027859850325995476 +13023454 -1.0085651995246425 0.028188197270252353 +13122488 -1.0088117974133186 0.028416988429493424 +13221522 -1.0089225727063047 0.028680860572933575 +13320556 -1.008671487046823 0.028861967221281957 +13419590 -1.0087482308793885 0.029068813451958907 +13518624 -1.009072580724447 0.029335023213339383 +13617658 -1.0086986465597834 0.02948797334677913 +13716692 -1.0090170403792742 0.029671853518641204 +13815726 -1.0090631696698684 0.029908946139710436 +13914760 -1.0088510938975306 0.030255754690341297 +14013794 -1.009111252908356 0.030519674696991825 +14112828 -1.0091086329847105 0.03059557743434554 +14211862 -1.008890192033299 0.030791669453362557 +14310896 -1.0089362387919036 0.031081163467258543 +14409930 -1.0088316587103827 0.03123314887726794 +14508964 -1.0089935164640318 0.0314020235065027 +14607998 -1.008817908078855 0.03170815994865987 +14707032 -1.009130357124469 0.031875471886579416 +14806066 -1.0091231469890143 0.03217211557732348 +14905100 -1.009122028412549 0.0321830318480143 +15004134 -1.0091017320695557 0.03240771339681421 +15103168 -1.009339753856298 0.03270843953477111 +15202202 -1.0092553484953675 0.03309274277758294 +15301236 -1.0091528484235779 0.033312220525098406 +15400270 -1.009264319323459 0.03358911619014232 +15499304 -1.0092479015788598 0.03356412645401185 +15598338 -1.0093483758169175 0.033985565512229865 +15697372 -1.009304593802786 0.03411471462010791 +15796406 -1.0092306669179176 0.03445752912270703 +15895440 -1.009412942393386 0.034546159366715117 +15994474 -1.0094456197427961 0.03468704565771518 +16093508 -1.0093744958920328 0.03517679309085436 +16192542 -1.0094766024889872 0.03515497600361746 +16291576 -1.009467604005381 0.035544836453139356 +16390610 -1.0095052844573276 0.035473200987307794 +16489644 -1.0096589214661058 0.035779697733068404 +16588678 -1.0095954725219631 0.036002555633793565 +16687712 -1.009618414683904 0.03629727777266808 +16786746 -1.0096844533604348 0.03652993992144163 +16885780 -1.0095656399204127 0.036658033515767255 +16984814 -1.0097865260920134 0.03711185949531441 +17083848 -1.009832717564232 0.03729820545798012 +17182882 -1.0097511618289101 0.037489021219853084 +17281916 -1.009781169929733 0.03766867622491327 +17380950 -1.0098545455525112 0.037728408472553134 +17479984 -1.0100994021228917 0.038087505942478256 +17579018 -1.0100430581473863 0.03836851428285147 +17678052 -1.0100436252034761 0.038607038456801565 +17777086 -1.0102719054256828 0.03873502670741921 +17876120 -1.0100096390820061 0.03902076229333377 +17975154 -1.0100938285637369 0.03912919646944673 +18074188 -1.0100082129512096 0.03931347460587307 +18173222 -1.0100678611169942 0.03971971361788405 +18272256 -1.0099524069243637 0.0399062801616269 +18371290 -1.0101849338675482 0.04003642022035899 +18470324 -1.0100322564953192 0.04024798068316691 +18569358 -1.010416581877768 0.04056118532018202 +18668392 -1.0102096050791693 0.0406220242577916 +18767426 -1.0102554939109818 0.04076615355923924 +18866460 -1.0099341725825761 0.04103330487959658 +18965494 -1.0101892270432866 0.04150921619627379 +19064528 -1.0102843417208962 0.04168646844065196 +19163562 -1.010571034561453 0.04196199874277709 +19262596 -1.0104440799839065 0.041904988066167076 +19361630 -1.0102715694372282 0.04215966600342724 +19460664 -1.0102332359468842 0.04252326139999206 +19559698 -1.0104447188952335 0.04266772538757455 +19658732 -1.0103510114842733 0.042771671961896435 +19757766 -1.0104090458910733 0.043138453873060645 +19856800 -1.010643757115823 0.04336899421884947 +19955834 -1.0105138449283881 0.043573187997875434 +20054868 -1.0103837340572805 0.04372319066784308 +20153902 -1.0104904729848447 0.044080820680296826 +20252936 -1.0105350690222235 0.044156211815226644 +20351970 -1.0105148115199216 0.044397104789507244 +20451004 -1.0105361533029278 0.04450669113203455 +20550038 -1.010657648061225 0.04488466170388793 +20649072 -1.0105934223756452 0.04506149230632015 +20748106 -1.0107947266740125 0.0453515212134097 +20847140 -1.010837321722036 0.045547140941148166 +20946174 -1.0106158386401105 0.04588453548523358 +21045208 -1.0108134138483629 0.04608792995704593 +21144242 -1.0107904098499554 0.04632889630741858 +21243276 -1.0108601267876591 0.04649938710719939 +21342310 -1.0110501503233889 0.04660754869230478 +21441344 -1.0107337954570044 0.04689402758498151 +21540378 -1.0109338547062956 0.047157578917083574 +21639412 -1.0109621326275195 0.04733380201872117 +21738446 -1.0111087321779086 0.04746654789272162 +21837480 -1.010841945101279 0.04794642245700833 +21936514 -1.011155358028393 0.048006843163150695 +22035548 -1.0112255385984918 0.04843694589757936 +22134582 -1.011118844804392 0.048275944935917706 +22233616 -1.0111818127153873 0.0487140143919105 +22332650 -1.0111063435470238 0.048820097838159424 +22431684 -1.0112439504945552 0.048943086468810404 +22530718 -1.011489571650751 0.049363338717948346 +22629752 -1.011336698105104 0.049545413224020035 +22728786 -1.011382928247136 0.04967914997219731 +22827820 -1.0112248877592065 0.04998080872392873 +22926854 -1.0111360458106038 0.05021665513274986 +23025888 -1.0112977403773078 0.05036011981205628 +23124922 -1.0113964527508035 0.050473155013916256 +23223956 -1.0113799846067 0.05092963354432354 +23322990 -1.011382288267127 0.050829532051334385 +23422024 -1.011419079934983 0.051367780302022166 +23521058 -1.0114728607617254 0.051515852536570275 +23620092 -1.011611425527169 0.051608183962143735 +23719126 -1.0117552162685646 0.052084355900067075 +23818160 -1.0115603242950513 0.052061442596535466 +23917194 -1.0116239784895857 0.05228337749505382 +24016228 -1.0117591889568605 0.05269649704469543 +24115262 -1.0116687596093052 0.052835072571836006 +24214296 -1.0115663335809453 0.052902641299149206 +24313330 -1.0119651247190224 0.053137195638217394 +24412364 -1.0115664767799346 0.05362928817558876 +24511398 -1.0118978954007005 0.053702637286176524 +24610432 -1.011888118903424 0.053952478071441934 +24709466 -1.0120028704104798 0.05412257520028623 +24808500 -1.0120945873353189 0.05426588144079387 +24907534 -1.0118941155398116 0.05468316045787422 +25006568 -1.012153908905683 0.05475312999175587 +25105602 -1.0120579762433697 0.05507498361403067 +25204636 -1.012316742152849 0.05534262535264794 +25303670 -1.0120717990032502 0.055405875676440904 +25402704 -1.0121472714849193 0.05575095472138598 +25501738 -1.0122362471802078 0.05591339308265271 +25600772 -1.0123328513830039 0.05604647148705389 +25699806 -1.0126966622000646 0.056345253398211684 +25798840 -1.0125164766211463 0.056641163065207605 +25897874 -1.0122648595545736 0.05668413215930032 +25996908 -1.0125622489735522 0.057001847000259454 +26095942 -1.012528240080318 0.0571259914925498 +26194976 -1.0126437090857334 0.057437673400313104 +26294010 -1.0124865496506514 0.057572004216415444 +26393044 -1.0126522693165936 0.057998871503085685 +26492078 -1.0127305173045218 0.05828996734080325 +26591112 -1.0126292032552513 0.058295814031812845 +26690146 -1.0127755549196966 0.058576757087304504 +26789180 -1.0127233223858079 0.05887018249521838 +26888214 -1.0128067438100974 0.05892811921654538 +26987248 -1.0130060042278337 0.059125394626381896 +27086282 -1.0130414969451895 0.05959282139591294 +27185316 -1.0127318517171333 0.05971808208436928 +27284350 -1.0131641738781818 0.059740808786746924 +27383384 -1.0129143619791765 0.06010821657469152 +27482418 -1.0128827475240152 0.06018146039646091 +27581452 -1.0131845733567715 0.06039117873326944 +27680486 -1.0130718808424581 0.060599859111779515 +27779520 -1.0131361985469696 0.06085146306122865 +27878554 -1.013150876216248 0.061164185846562004 +27977588 -1.0133899887713738 0.06155594988766473 +28076622 -1.0134559558501972 0.0616128443540888 +28175656 -1.0131899534315458 0.06183644189695098 +28274690 -1.0132480784113451 0.06185244210447203 +28373724 -1.0132831688752495 0.062446396824215294 +28472758 -1.013363323466993 0.06253042388040428 +28571792 -1.013745161198183 0.06271358266298296 +28670826 -1.0135406501138033 0.06290020391844912 +28769860 -1.013714616671308 0.06290606905855363 +28868894 -1.013641381692414 0.06353859818276385 +28967928 -1.0135934059495202 0.06342098472705454 +29066962 -1.0137725333399978 0.0637691088521619 +29165996 -1.0138185503417132 0.06399969757375064 +29265030 -1.0139265430028956 0.0641476721253355 +29364064 -1.013978225153033 0.06425793855668017 +29463098 -1.0138815534130925 0.06473764250565098 +29562132 -1.0140766403584047 0.06485049122435695 +29661166 -1.013811417309569 0.06485475209055078 +29760200 -1.0139827158143848 0.06514219367935478 +29859234 -1.014227797060686 0.06553380254418989 +29958268 -1.0139265857371957 0.0657080122379182 +30057302 -1.0140314199661422 0.0658569085726844 +30156336 -1.0138191301720074 0.06595474618586904 +30255370 -1.0143522211159748 0.06629867287007102 +30354404 -1.0142464456447104 0.06653187009190566 +30453438 -1.0141905139450549 0.06676915528675635 +30552472 -1.0143481709870827 0.06682965234430775 +30651506 -1.0142750684215895 0.06696730761496876 +30750540 -1.014127936088108 0.06732394899655407 +30849574 -1.0143316613652285 0.06742149804466067 +30948608 -1.0141863879418285 0.06781636677487189 +31047642 -1.0145250901762095 0.067796217274548 +31146676 -1.0144625441580162 0.06821199469529168 +31245710 -1.0144773103355968 0.06826119128924635 +31344744 -1.014600472373469 0.06856598671969369 +31443778 -1.0145736542649815 0.06899584542701453 +31542812 -1.014570319236602 0.06909810784234623 +31641846 -1.0145482208552141 0.06959372532264725 +31740880 -1.014756119347479 0.0695676613544406 +31839914 -1.0146631192385633 0.06963213999633314 +31938948 -1.014805567671227 0.06976704141413602 +32037982 -1.0148545116552157 0.07029236163416426 +32137016 -1.0148351212131692 0.07045523234172604 +32236050 -1.0148507010224608 0.07066846658698882 +32335084 -1.0150689490389044 0.070993574840795 +32434118 -1.0149694745802122 0.07114295596686762 +32533152 -1.0150846267700895 0.07126010513543186 +32632186 -1.015236469715417 0.07151918367556148 +32731220 -1.0153491222523998 0.07169772720031602 +32830254 -1.015233680931848 0.07215456103093591 +32929288 -1.0150832176866216 0.07224106988232823 +33028322 -1.015221602701122 0.07220917359392602 +33127356 -1.015090440889947 0.07252584737742426 +33226390 -1.0154832260610946 0.07275811316859929 +33325424 -1.0154032769879289 0.07316862737976156 +33424458 -1.0153730486055268 0.07336056298760905 +33523492 -1.0152642577979867 0.07339889848757773 +33622526 -1.0157310479547055 0.07350799965530298 +33721560 -1.0155976671037594 0.07390647987566709 +33820594 -1.0154980886086766 0.07403778821399411 +33919628 -1.015656456768307 0.07419774303754866 +34018662 -1.0157778309632515 0.07451572596327673 +34117696 -1.0158929347320003 0.0747317122270796 +34216730 -1.0157698741239087 0.07497891289722124 +34315764 -1.015834095517183 0.0752003862063351 +34414798 -1.0158645761429141 0.07533464005654283 +34513832 -1.0159043143242927 0.07552501087064498 +34612866 -1.0159551873312054 0.07611279289529553 +34711900 -1.0157803780157946 0.07593457833418112 +34810934 -1.0159704051225225 0.07621750738736113 +34909968 -1.0160852411563726 0.07648549909389506 +35009002 -1.016248522136367 0.07671667232301796 +35108036 -1.0160913918972863 0.07667026344222566 +35207070 -1.0162896882244083 0.07710351653331768 +35306104 -1.0162488049478446 0.07734185217458962 +35405138 -1.016346585558452 0.07754057443037556 +35504172 -1.0163140589201616 0.07789983641151942 +35603206 -1.0163670916893202 0.07781945475183343 +35702240 -1.0163257993987174 0.07814946645059383 +35801274 -1.0166140960322692 0.078251395485 +35900308 -1.0164126340174593 0.07852599384112918 +35999342 -1.0165977769710037 0.07892832500028728 +36098376 -1.0165159091510465 0.07905421273340296 +36197410 -1.0168094242937231 0.07914399389302831 +36296444 -1.0167441174556202 0.07951243216066824 +36395478 -1.0167748788064939 0.07957313584262111 +36494512 -1.0168451906255045 0.08007416818188791 +36593546 -1.0167763187256371 0.07997586407398191 +36692580 -1.0170578641857562 0.08018401769224731 +36791614 -1.0169221276667593 0.08059162749627137 +36890648 -1.0168542663607916 0.08074626486557389 +36989682 -1.0168694172746258 0.08092076408934996 +37088716 -1.0168911375385854 0.0811819286698096 +37187750 -1.0171655822039385 0.08157515730624666 +37286784 -1.0170973575715399 0.081562274814948 +37385818 -1.0173549716754744 0.08192235925995278 +37484852 -1.0174473699306905 0.08222891834600915 +37583886 -1.0172762662050425 0.08225050865036942 +37682920 -1.0174312276244448 0.08238682389217342 +37781954 -1.0174734640677172 0.08279413122908147 +37880988 -1.0176393474502514 0.0830861563643806 +37980022 -1.0176599235655874 0.08297350458334107 +38079056 -1.0175043449120054 0.08310781236745787 +38178090 -1.017738453470431 0.08349037883242148 +38277124 -1.017639844320575 0.08383070734290714 +38376158 -1.017852154264567 0.08415208501110112 +38475192 -1.0179802074839304 0.0842607482632615 +38574226 -1.0178648662286593 0.08435814824169237 +38673260 -1.0180083300451666 0.08440464525157694 +38772294 -1.0181196578168796 0.0846171860821836 +38871328 -1.0180709746713257 0.08519684214208272 +38970362 -1.0181850386743079 0.08520640473655054 +39069396 -1.0181308838431924 0.08542864112648149 +39168430 -1.0180846019372802 0.08570369114895399 +39267464 -1.0181413835881983 0.08594325540501314 +39366498 -1.0182116594022144 0.08610057897749795 +39465532 -1.018350183305776 0.08645381569740564 +39564566 -1.0185708891444651 0.08633377251804424 +39663600 -1.0185235990398644 0.08666430830742852 +39762634 -1.0184515463500854 0.08704253418372174 +39861668 -1.0187802297763502 0.08717694180970048 +39960702 -1.018577186906943 0.08741524601281077 +40059736 -1.0186661700335844 0.08746909320971138 +40158770 -1.0186616687173575 0.08800334243923652 +40257804 -1.0186260421614222 0.08799887894157657 +40356838 -1.01882978453537 0.08834137261155053 +40455872 -1.0189607538267038 0.08853323864552422 +40554906 -1.0187063944367736 0.0886664373132278 +40653940 -1.0189534886035694 0.088752622899483 +40752974 -1.0188040211148548 0.08915634250562088 +40852008 -1.01919603808637 0.08924185183466303 +40951042 -1.0190453854343344 0.08943563527046115 +41050076 -1.0191268996733165 0.08991703049564684 +41149110 -1.0193006059920888 0.09003151633236749 +41248144 -1.0190998585735935 0.09017531559930157 +41347178 -1.0192266847002196 0.09017291261194324 +41446212 -1.0193502548125846 0.09037943993075123 +41545246 -1.0194835589279883 0.09098214789715944 +41644280 -1.019288046926667 0.09111099702613265 +41743314 -1.0195943644883017 0.091341066353624 +41842348 -1.0195549704218985 0.09116281596345098 +41941382 -1.0195887447920724 0.09173546310871904 +42040416 -1.0196007315352693 0.09188815155777826 +42139450 -1.0194444882737177 0.09195156695822654 +42238484 -1.0196721101442745 0.09244043097014322 +42337518 -1.0196045688484705 0.0923931197969422 +42436552 -1.0195713787801546 0.09272753674688654 +42535586 -1.0199060671081128 0.09274726417854937 +42634620 -1.019628673254141 0.09313629596636619 +42733654 -1.0201188564266586 0.09330988161453219 +42832688 -1.0200872982869762 0.09343935848000542 +42931722 -1.0202500582908587 0.09389419548156386 +43030756 -1.0203637546429727 0.09387158664518115 +43129790 -1.0199071876446213 0.09409770877025322 +43228824 -1.0200784078922893 0.09448296742680021 +43327858 -1.0201905239570135 0.09461020804693071 +43426892 -1.0203625217453545 0.0948635366588901 +43525926 -1.0203364375635937 0.09497780663303346 +43624960 -1.0202808867643798 0.0950706904037349 +43723994 -1.0206248653169057 0.09541207354505231 +43823028 -1.0203722346125168 0.09541966887532495 +43922062 -1.0206239600692615 0.09587847965767425 +44021096 -1.0204211558626957 0.0961685252099509 +44120130 -1.0208605677122031 0.096469443798029 +44219164 -1.0207021127814433 0.09619263416926856 +44318198 -1.02095962254222 0.09639341205205429 +44417232 -1.0209960975303354 0.09677463594844402 +44516266 -1.0208748267584957 0.09680076445393268 +44615300 -1.0211276268910265 0.09722681316627639 +44714334 -1.0212710466472057 0.09732385710551206 +44813368 -1.0211119717476973 0.09768777223755436 +44912402 -1.0213131497163679 0.09781404840945579 +45011436 -1.0211298865941478 0.09798778726694075 +45110470 -1.0212158718123137 0.09818112922376201 +45209504 -1.0215265004269989 0.09848206342468435 +45308538 -1.0215702270376725 0.09874210321408053 +45407572 -1.0218130388968782 0.09892307686210075 +45506606 -1.0214578636964406 0.09924156443594703 +45605640 -1.021801310301428 0.09944591233412965 +45704674 -1.0217054735507307 0.09952083443001297 +45803708 -1.0218276683063388 0.09990528419460414 +45902742 -1.021815523370801 0.10004378039367377 +46001776 -1.0218558782372107 0.10029178640827936 +46100810 -1.022088722546954 0.10041093582798555 +46199844 -1.0219967281118596 0.10059757480066382 +46298878 -1.0223081615571181 0.10083905475596293 +46397912 -1.0218408955833629 0.10091496190808717 +46496946 -1.022188808928597 0.1010088716367956 +46595980 -1.0223541011672874 0.101398196432296 +46695014 -1.0221411363635449 0.101459383565168 +46794048 -1.0223855473099082 0.1018710753744255 +46893082 -1.0224151102898198 0.1023069736732318 +46992116 -1.0223059097250466 0.10225338008460556 +47091150 -1.0223150553655043 0.10273534061955733 +47190184 -1.0227468320800894 0.10271007326785189 +47289218 -1.0226173113573758 0.10283089695574522 +47388252 -1.0224597700708626 0.10301677804643226 +47487286 -1.0226683026368395 0.10340559636047028 +47586320 -1.022874942514845 0.1034153479074488 +47685354 -1.0226747525931328 0.10370078121115489 +47784388 -1.0230872256476038 0.10413611717251464 +47883422 -1.02300242534672 0.10427846592516424 +47982456 -1.0233383171244952 0.10415010645473041 +48081490 -1.0227836692278442 0.10450244281748101 +48180524 -1.0233014598241725 0.10460742188831135 +48279558 -1.0231720019254245 0.10481560186874463 +48378592 -1.0233343937269905 0.10483883744057076 +48477626 -1.023443420696266 0.10549919440185324 +48576660 -1.0235106516675312 0.10519347738789221 +48675694 -1.0233834509443078 0.10537420376884099 +48774728 -1.0233351509477828 0.1060052211640477 +48873762 -1.0234550802173408 0.10622744512398499 +48972796 -1.0236245471918624 0.10652034078248498 +49071830 -1.0237127810551152 0.10648443452735051 +49170864 -1.0237574345276685 0.10689915173002272 +49269898 -1.0237636416097875 0.10702051474428032 +49368932 -1.0234509489240942 0.10736113741974666 +49467966 -1.0233467056733714 0.10744652307802792 +49567000 -1.023607770512492 0.10766593928184964 +49666034 -1.0242118260141837 0.10764637080161052 +49765068 -1.024127761419358 0.10792480081575626 +49864102 -1.0239449522018975 0.10833641031424661 +49963136 -1.0241364370958277 0.10857785579220959 +50062170 -1.0227280666599583 0.10865451848979049 +50161204 -1.022865287547065 0.1086930553696125 +50260238 -1.0228326818873983 0.1093373029031921 +50359272 -1.0241342261758275 0.10892678517798436 +50458306 -1.023602690218962 0.1093941713710488 +50557340 -1.0236917539254018 0.109833708515048 +50656374 -1.0234705477056647 0.10974935706469736 +50755408 -1.0238548995907613 0.11015706942288817 +50854442 -1.0244793010234914 0.1100194931257207 +50953476 -1.024596064140775 0.11041621098343274 +51052510 -1.0244668419554246 0.11073804589223314 +51151544 -1.0247757406371363 0.11052867544495876 +51250578 -1.0246360124644853 0.11070654112953607 +51349612 -1.0246272019942406 0.11092405670447983 +51448646 -1.0240829341898452 0.11156035906442366 +51547680 -1.0249781249126595 0.11173086231305265 +51646714 -1.0241830325903596 0.11209098834927979 +51745748 -1.0242725850078969 0.11199292124193713 +51844782 -1.0253144746853604 0.11213950640990158 +51943816 -1.0246530721205758 0.11254868429437175 +52042850 -1.0246726243160102 0.11254603277986708 +52141884 -1.025011264862116 0.11305082360063189 +52240918 -1.0244641487044495 0.11333162232232141 +52339952 -1.0247517189860158 0.1133826876979449 +52438986 -1.0245360702332456 0.11361516210906819 +52538020 -1.0249699211405852 0.11365081475287378 +52637054 -1.0256383368267377 0.11359830970362311 +52736088 -1.025763302230922 0.11406331519519455 +52835122 -1.0257738181234124 0.1142522450764346 +52934156 -1.0246675744646976 0.11493636515408162 +53033190 -1.0241641322521104 0.11502468529908672 +53132224 -1.0243838813430142 0.1147827781683413 +53231258 -1.0244763545312294 0.11549671835756886 +53330292 -1.0243947588475133 0.11579182168631842 +53429326 -1.0252534099983102 0.11542792815121504 +53528360 -1.0253522082552975 0.11559499956283832 +53627394 -1.0256533748241028 0.11581676266786381 +53726428 -1.0257203990009798 0.11617150833399796 +53825462 -1.0252813206574511 0.11636978460467362 +53924496 -1.0259807956068068 0.11655720086492168 +54023530 -1.0256755759371983 0.11693841285223437 +54122564 -1.0268428755435226 0.11676066312920695 +54221598 -1.0266863411091856 0.11710306783862669 +54320632 -1.0267885937949934 0.11726265374606333 +54419666 -1.0253317932394794 0.11730535553484801 +54518700 -1.0255538150305583 0.11786136424329205 +54617734 -1.0259567890857704 0.11779440164008734 +54716768 -1.0260744308410659 0.11819118744097304 +54815802 -1.0258563551639295 0.11822310145103126 +54914836 -1.0259125105204094 0.11844375477350248 +55013870 -1.0261846551723683 0.11854830962532714 +55112904 -1.0259737632637371 0.1188209296234453 +55211938 -1.0261191744278737 0.11908775058114082 +55310972 -1.0262231007244416 0.11913368343271263 +55410006 -1.0265378598610895 0.11960645675835788 +55509040 -1.0262432879741046 0.11962588516445688 +55608074 -1.0266518575570285 0.11981564770338562 +55707108 -1.026446626525053 0.12012680203157863 +55806142 -1.02672751168399 0.12020261899937452 +55905176 -1.0264465074933165 0.12059602562716192 +56004210 -1.0268041782930022 0.12061479871810536 +56103244 -1.0271198928436325 0.12079849248858437 +56202278 -1.0269380752195174 0.12075145859121342 +56301312 -1.026787599650369 0.12149329723416198 +56400346 -1.0266845893633807 0.12148438250281685 +56499380 -1.0270318498471491 0.12158222649014931 +56598414 -1.0268384513314752 0.1217947450248094 +56697448 -1.0267569948024462 0.12214521596736432 +56796482 -1.0270815450090731 0.12229425117888133 +56895516 -1.0271776361635516 0.12237354386975326 +56994550 -1.0275109355745533 0.12252339909935293 +57093584 -1.027049698350165 0.12272579909868303 +57192618 -1.0277921002566461 0.12282020401770885 +57291652 -1.0276282646728008 0.12283286292103655 +57390686 -1.0275974109032928 0.12353989131984836 +57489720 -1.027445820945532 0.12345968793822956 +57588754 -1.0274409262816357 0.1239485158351014 +57687788 -1.027898035858876 0.12387641064441553 +57786822 -1.0278537395404943 0.12419343469073528 +57885856 -1.0279804104276555 0.12425452920197012 +57984890 -1.0274632657934504 0.12472851998313819 +58083924 -1.0275548518974933 0.12507538746096364 +58182958 -1.0276993143984976 0.12507755983349064 +58281992 -1.02756409426542 0.12531261802660842 +58381026 -1.027681383445674 0.12570095255102298 +58480060 -1.02753542506202 0.1257366766406497 +58579094 -1.028106631038858 0.12572042146378767 +58678128 -1.0281725361969103 0.12617203427199633 +58777162 -1.0282536006027545 0.12596901423783755 +58876196 -1.028103528176948 0.12667986285818852 +58975230 -1.028329013836402 0.12657510728956917 +59074264 -1.0283609996485166 0.1267821663935971 +59173298 -1.0286923831422292 0.1270228904316477 +59272332 -1.0284929512761931 0.12741315276417606 +59371366 -1.028380820360821 0.12746513824111222 +59470400 -1.0288658863112463 0.1274753956553955 +59569434 -1.028662603299429 0.12789575563190797 +59668468 -1.0287290891317882 0.1283048159277468 +59767502 -1.0286510879445914 0.12822601811912812 +59866536 -1.0289789467411272 0.1284064653179665 +59965570 -1.029170485482013 0.12856980897130801 +60064604 -1.0281663383899473 0.12991829369271238 +60163638 -1.0284485287826708 0.1297016266844527 +60262672 -1.028720656003903 0.129973894520158 +60361706 -1.0281719265310818 0.13033689148522437 +60460740 -1.0285706293495105 0.1305066782697603 +60559774 -1.0286568549269648 0.1304128380269547 +60658808 -1.0286053661233199 0.1305747210147699 +60757842 -1.0284895346899805 0.13098351185999996 +60856876 -1.0287646335193295 0.13125629836659514 +60955910 -1.0287698390046067 0.1314474863749222 +61054944 -1.0290322701513415 0.1315415657139156 +61153978 -1.0290676797087648 0.13191321234327216 +61253012 -1.0292619279286266 0.1319963870688644 +61352046 -1.0291732103801527 0.1320687380982286 +61451080 -1.0291385827790467 0.1322761152688498 +61550114 -1.0292178150245292 0.13254255750198896 +61649148 -1.0288285745956156 0.13293046077824847 +61748182 -1.029406156340966 0.1331709940378641 +61847216 -1.0290592602532043 0.1333099300345541 +61946250 -1.029350199020622 0.1332540908008276 +62045284 -1.0296414524613453 0.13357602019488754 +62144318 -1.029575090713194 0.1335102344386629 +62243352 -1.029633176295587 0.13405502663766583 +62342386 -1.0297718433122778 0.13422124427283572 +62441420 -1.0299194419993816 0.13447165002644787 +62540454 -1.0298369554386397 0.13447902148279928 +62639488 -1.0299405897158842 0.13463925272829988 +62738522 -1.0299453293247118 0.1348240566918091 +62837556 -1.029898808068013 0.13507692392292509 +62936590 -1.0300523866448577 0.13517589028608692 +63035624 -1.0300532721965616 0.13523500758387869 +63134658 -1.0300458595284434 0.13570994879181528 +63233692 -1.0299042612149032 0.13566961509935832 +63332726 -1.0303101558728058 0.13582181804257704 +63431760 -1.0305463024897918 0.1363012058489685 +63530794 -1.0304648268543657 0.136247863834043 +63629828 -1.0301740826257657 0.13654098514953897 +63728862 -1.029983305016515 0.13682998005993388 +63827896 -1.0309106745455514 0.13677766859044949 +63926930 -1.0303488007957544 0.13689736582790116 +64025964 -1.0307460331659777 0.13710870297591926 +64124998 -1.030829474449588 0.1371713066834418 +64224032 -1.030828597446385 0.13747275656767816 +64323066 -1.0309708468082457 0.13771114845180357 +64422100 -1.0311573468733377 0.1381292559826887 +64521134 -1.0309435391248352 0.13830646129227978 +64620168 -1.0310165014648776 0.13825598230137137 +64719202 -1.0314494116027677 0.1383802368893403 +64818236 -1.0314181246119687 0.13865441402144216 +64917270 -1.0316544018446105 0.13889526954936074 +65016304 -1.0315066390714211 0.13891867144895018 +65115338 -1.0316887473441885 0.13926653631457495 +65214372 -1.0317771115994214 0.1394413647906478 +65313406 -1.0316861622462337 0.13962238318708114 +65412440 -1.0317415761616826 0.1399572298166962 +65511474 -1.0319188401703367 0.14016014619895692 +65610508 -1.0320875946444235 0.14008169742166146 +65709542 -1.032047947472981 0.14018382117408948 +65808576 -1.032008431789605 0.1408461276989911 +65907610 -1.0322228507212334 0.14076348478547213 +66006644 -1.0321251546970838 0.14090799866199472 +66105678 -1.0320835172371432 0.1410209192406348 +66204712 -1.0322024184670382 0.1412698989997364 +66303746 -1.0324439218153172 0.14131125261181599 +66402780 -1.0322779563725704 0.14176077184569222 +66501814 -1.032327430805827 0.1420042684794463 +66600848 -1.0327314278637427 0.14213423997314995 +66699882 -1.0328518508396896 0.14207764107921486 +66798916 -1.0328108546168773 0.14241827297954876 +66897950 -1.0327633276024224 0.14272031586228043 +66996984 -1.032938045811623 0.14256399781978865 +67096018 -1.0328989019539414 0.1431193789920339 +67195052 -1.0329668391032905 0.14327887267105693 +67294086 -1.0332078835234546 0.14327483449865688 +67393120 -1.03338015027201 0.14335177056613452 +67492154 -1.0331963410956015 0.1436603994911782 +67591188 -1.033457292093641 0.14411527859849732 +67690222 -1.0334896833737681 0.14398357178989185 +67789256 -1.0336064138913237 0.14420455972597104 +67888290 -1.0336223090102457 0.14441145959824767 +67987324 -1.0337809072093316 0.14476072028553172 +68086358 -1.0336761768556626 0.14508747317787185 +68185392 -1.0338014727285834 0.14511428977945934 +68284426 -1.033923115925973 0.14561135349015258 +68383460 -1.0339054703032209 0.1455068479833833 +68482494 -1.0340985059271528 0.14580700679019304 +68581528 -1.0339494339939757 0.14598651646521849 +68680562 -1.0343486863767999 0.14614522282280856 +68779596 -1.0340344786885947 0.14628313577518612 +68878630 -1.0342555250787322 0.14678043155248646 +68977664 -1.034150664250881 0.14663873598923946 +69076698 -1.0343638185980522 0.1471091305756661 +69175732 -1.034387559628551 0.14721083338873675 +69274766 -1.034482363147169 0.14741437489769876 +69373800 -1.0345337370244727 0.1475819855038001 +69472834 -1.0347219015846603 0.14760663558595358 +69571868 -1.0345728333298831 0.1477002776565861 +69670902 -1.0348968279699502 0.14798652466798073 +69769936 -1.0349906249389293 0.1481677394698192 +69868970 -1.0349691342953335 0.14875221580292533 +69968004 -1.0347131688083198 0.14881397603325092 +70067038 -1.0349801897555657 0.1488509994484993 +70166072 -1.0351155828734293 0.1489573723579169 +70265106 -1.0349450319904014 0.14929952179785655 +70364140 -1.0353538759577436 0.14936509655667973 +70463174 -1.035436086726856 0.14977293276524115 +70562208 -1.0353586068284863 0.14975910925965594 +70661242 -1.0352326189693084 0.15019097584039026 +70760276 -1.0356394342773483 0.15007828729639855 +70859310 -1.0354873580585997 0.1505181145096023 +70958344 -1.0354175128393366 0.15058226614165565 +71057378 -1.0355830000337551 0.15066610763082353 +71156412 -1.0357263638343752 0.1509136191579045 +71255446 -1.0357643049825904 0.15115878496862728 +71354480 -1.0358321827703207 0.1514123242365243 +71453514 -1.035620144824117 0.1516085971234561 +71552548 -1.0358719154809841 0.15175794468819023 +71651582 -1.0358309186040155 0.151958114719361 +71750616 -1.0361257892343567 0.15214419947403654 +71849650 -1.0360377984087812 0.15224696699037724 +71948684 -1.0361769151737052 0.1525742928497016 +72047718 -1.0362175640694304 0.15264959048368237 +72146752 -1.0364854159918857 0.15313611203081512 +72245786 -1.0364093915610606 0.1531726560665499 +72344820 -1.036468171140518 0.15314629566120563 +72443854 -1.0366986178792195 0.1534006663950356 +72542888 -1.0367229446014152 0.15394207176209315 +72641922 -1.036774066019679 0.15365117077549073 +72740956 -1.0365475310801648 0.1541854189422284 +72839990 -1.0367773477871098 0.15428861333776162 +72939024 -1.0369013066126047 0.15432636704651262 +73038058 -1.0370509563487968 0.1545762671844014 +73137092 -1.0370324874081476 0.15491308238053966 +73236126 -1.0371086710698285 0.15498559469077688 +73335160 -1.0371830340081358 0.15505410603392852 +73434194 -1.037317813620495 0.15538476530948778 +73533228 -1.0372043448165935 0.15526288994068646 +73632262 -1.0374133267572503 0.15596224033238357 +73731296 -1.037324302243262 0.15605341320624522 +73830330 -1.0373199887162494 0.15604965050672795 +73929364 -1.0374772124703915 0.1563354828002392 +74028398 -1.0376247088882657 0.1564365659820614 +74127432 -1.0377783293436138 0.15664265090171878 +74226466 -1.0379480418966394 0.15677285092530985 +74325500 -1.0376305630569465 0.15715264885464947 +74424534 -1.0377326193207723 0.15711708762842103 +74523568 -1.0382100985378082 0.15728089069714776 +74622602 -1.038054129137446 0.1575200356493587 +74721636 -1.0380552217793961 0.15794626435071923 +74820670 -1.0380950407022316 0.15788077674490994 +74919704 -1.0380804360368912 0.15809046900624993 +75018738 -1.0383065517306325 0.15819930897751883 +75117772 -1.0385638885178536 0.15851685796905285 +75216806 -1.0382869255514553 0.1587993588240569 +75315840 -1.038590080374384 0.15879278621738924 +75414874 -1.0386564676186714 0.159142953296263 +75513908 -1.0385057417960728 0.1594096488456408 +75612942 -1.038733097041371 0.15945910284485695 +75711976 -1.0389430128573602 0.1597660449415584 +75811010 -1.038784394565292 0.15991312338358596 +75910044 -1.0390050043063392 0.16005870966136831 +76009078 -1.0389533509829671 0.16025130036587165 +76108112 -1.0392960391179582 0.16040825087411043 +76207146 -1.039003001978662 0.16067165473891587 +76306180 -1.039382733787268 0.16077350751114838 +76405214 -1.0394495189729895 0.16085928107502653 +76504248 -1.0396206087394901 0.16127438438991504 +76603282 -1.039255472335759 0.1612984758114215 +76702316 -1.0394682910459632 0.16160934620880782 +76801350 -1.0395144931735203 0.16188378580235283 +76900384 -1.039672364669968 0.16211368101321333 +76999418 -1.039533151265614 0.16224555857781214 +77098452 -1.0398400538686228 0.16250411320690947 +77197486 -1.0398653610754116 0.1626000603358517 +77296520 -1.0398324055863384 0.16278711884161137 +77395554 -1.0399744394809125 0.16312450010530177 +77494588 -1.0400849312441043 0.16298541535630987 +77593622 -1.0402107548118948 0.16362589596277527 +77692656 -1.0398380889140282 0.1635999294270473 +77791690 -1.0403456166017246 0.1637567017648827 +77890724 -1.0405674702844747 0.16402164013348206 +77989758 -1.0407640861528813 0.1639725490525859 +78088792 -1.040333656722936 0.16411360615790102 +78187826 -1.0406201929595076 0.16472592978621328 +78286860 -1.0407366785156023 0.16491611188654667 +78385894 -1.0406586208448225 0.16499463732815334 +78484928 -1.0406222693742293 0.16500727027042622 +78583962 -1.040814842760789 0.16508891827414685 +78682996 -1.0406949841351385 0.16563227271337233 +78782030 -1.0408532211318207 0.16540161605469356 +78881064 -1.0410126923620553 0.165678540229083 +78980098 -1.0409891060859358 0.16616665824846913 +79079132 -1.0408958347460953 0.16624949695645447 +79178166 -1.0410879793415082 0.1663357825930982 +79277200 -1.0409936168903993 0.1665626978842612 +79376234 -1.0410287202386799 0.16681850898941272 +79475268 -1.0415003990834069 0.16670721887576606 +79574302 -1.0412262638742271 0.16706740725388236 +79673336 -1.0414521211991943 0.1669143151008053 +79772370 -1.0414234075961768 0.16710622726820187 +79871404 -1.0413582670574206 0.16701418272261953 +79970438 -1.0415597164473387 0.16733000599631812 +80069472 -1.0418193219745473 0.16715036699915214 +80168506 -1.0417206729722588 0.16717306051145994 +80267540 -1.0421481039626257 0.16770913370050092 +80366574 -1.042107641928515 0.16760829760403895 +80465608 -1.0424912864803417 0.16762055701653208 +80564642 -1.0424662909764422 0.1676767914310134 +80663676 -1.042676884035901 0.16769551826275114 +80762710 -1.0428503619456722 0.1681234113324795 +80861744 -1.043180148802043 0.16828627228239804 +80960778 -1.0431161657375712 0.1683756211969105 +81059812 -1.0436010439221421 0.16860342331118258 +81158846 -1.0438634121522685 0.16861042298338683 +81257880 -1.0436671054148055 0.16901428442485775 +81356914 -1.0442002267839514 0.16933087386519194 +81455948 -1.0439020797699132 0.1699235171243004 +81554982 -1.0445369055739633 0.16988687369373814 +81654016 -1.0447962722677402 0.17036178314755693 +81753050 -1.0450019867312526 0.1707580720268007 +81852084 -1.0449702933869922 0.1709014874829908 +81951118 -1.044768761346666 0.17108678270350106 +82050152 -1.0449432255922084 0.17147087294454547 +82149186 -1.0450000753016637 0.17170787981163194 +82248220 -1.0450285827167898 0.1718852867575036 +82347254 -1.045088932366908 0.17252215289980127 +82446288 -1.0447823019927323 0.1725581075452226 +82545322 -1.0449490963524628 0.1723859135009632 +82644356 -1.0451744868275386 0.17291651666804844 +82743390 -1.0448575800427862 0.1732327724950734 +82842424 -1.0452911548474055 0.17302301525845246 +82941458 -1.0450658885444877 0.17328802723655146 +83040492 -1.0454378208493487 0.17347770714936253 +83139526 -1.0452347450866 0.17403479249665588 +83238560 -1.045305826801016 0.1740030866219209 +83337594 -1.0455266575461626 0.1741960401792613 +83436628 -1.0458488890061735 0.17443785175068083 +83535662 -1.0456474591424745 0.17471878239633812 +83634696 -1.045624084783922 0.17437802979044215 +83733730 -1.0457875700925086 0.17464834025403242 +83832764 -1.0457143770124306 0.17500547661038923 +83931798 -1.0457836812300068 0.1753737302405624 +84030832 -1.0456443310818109 0.1754799203702256 +84129866 -1.045881612201641 0.17564419060104325 +84228900 -1.0456328945795144 0.1756606753722061 +84327934 -1.0459819370791943 0.1758766364631679 +84426968 -1.045918467147933 0.17602041871637084 +84526002 -1.0459583164648094 0.17607974861038594 +84625036 -1.046138269030658 0.17609606485827797 +84724070 -1.0462798364300856 0.1766019631594761 +84823104 -1.0461641730235776 0.17666746288769752 +84922138 -1.046554278578438 0.17696487736618033 +85021172 -1.0465193845564849 0.1768037971349549 +85120206 -1.0465563024104019 0.177272446735495 +85219240 -1.046116889536911 0.17747042194915777 +85318274 -1.046865566854937 0.17764536415028145 +85417308 -1.0465733184876616 0.17777866045080182 +85516342 -1.0465863557219282 0.1778496511984686 +85615376 -1.0466526677698895 0.17780904486950813 +85714410 -1.0467933245939316 0.17824818720438051 +85813444 -1.0469462567419008 0.17831491148020318 +85912478 -1.0471727916524605 0.17854040608889674 +86011512 -1.0470578875674692 0.1787243824766952 +86110546 -1.047162612166818 0.1787011812532133 +86209580 -1.0471176149000316 0.17900624597596201 +86308614 -1.0474741313380507 0.17911399816220477 +86407648 -1.0476183342681051 0.17931844702505811 +86506682 -1.0474172709488172 0.17940870865809422 +86605716 -1.0477090815912309 0.17949429201841224 +86704750 -1.047726889149411 0.17977768307745373 +86803784 -1.0479765050523906 0.17995687476613906 +86902818 -1.0479833174095885 0.18009193728544504 +87001852 -1.0479528257265736 0.18004268177596106 +87100886 -1.0478301876571663 0.18040687760194674 +87199920 -1.0481477675819073 0.18061199168756936 +87298954 -1.0481902675964685 0.18096865465714065 +87397988 -1.0482023059095875 0.18072242167134617 +87497022 -1.048385675885992 0.18086741058078937 +87596056 -1.0486109905624401 0.1813379898632266 +87695090 -1.048671926441164 0.18131367181476996 +87794124 -1.0485794466187701 0.1814942178126936 +87893158 -1.0487537260426723 0.1816976976720251 +87992192 -1.0486448515053053 0.18198414240941307 +88091226 -1.0489643824008217 0.18187072528307424 +88190260 -1.0487153319910731 0.1820831195730696 +88289294 -1.0489839225137243 0.18242019134258985 +88388328 -1.0492284658318374 0.18247833733720592 +88487362 -1.0490732880453768 0.1829547540845894 +88586396 -1.0499168620798625 0.18290292081812182 +88685430 -1.049428966199857 0.18283660357267276 +88784464 -1.0495769300815834 0.1830536592436353 +88883498 -1.0495326774946387 0.18367328705738592 +88982532 -1.0495529198956344 0.1834774148478492 +89081566 -1.050005590648382 0.18310966084374208 +89180600 -1.049855520532623 0.18420243293824826 +89279634 -1.0500700070163262 0.18431407095611024 +89378668 -1.049796165326143 0.18450881844481756 +89477702 -1.0501127092113196 0.1842732364375115 +89576736 -1.0504157379754855 0.184717886681777 +89675770 -1.05018580630865 0.18455068783227918 +89774804 -1.0504405681791147 0.18482490054018194 +89873838 -1.050584382772843 0.18514718116456275 +89972872 -1.0504345628370697 0.18521584969508834 +90071906 -1.0508465051524862 0.18498891540284595 +90170940 -1.0506935012100576 0.1855777788945422 +90269974 -1.0509038323815374 0.186018605722356 +90369008 -1.0507838568422634 0.18616979675760267 +90468042 -1.0511332631195478 0.18621600503539748 +90567076 -1.0511710111778687 0.18642274935666747 +90666110 -1.0510072067041076 0.18647837278254856 +90765144 -1.0513381225024194 0.1865023300648445 +90864178 -1.0514738305669917 0.1867385219939102 +90963212 -1.0513667401449092 0.18726548430652531 +91062246 -1.0510752780835841 0.18696522510592217 +91161280 -1.0513743725085283 0.1870235010843863 +91260314 -1.051359168331004 0.18745635154797508 +91359348 -1.05200721593346 0.18737407678343132 +91458382 -1.051803974583575 0.18751130919508388 +91557416 -1.0518217557406428 0.18764184763277658 +91656450 -1.0520131299774962 0.18794216559045065 +91755484 -1.0518161406505298 0.18819575099445798 +91854518 -1.0521472455461154 0.1882259746755056 +91953552 -1.0521798366447008 0.18865357413463102 +92052586 -1.0525330705929703 0.18842035888437303 +92151620 -1.0523320494112038 0.1887972211947934 +92250654 -1.0522294816194973 0.1890391215357192 +92349688 -1.052544471381314 0.1893534213539313 +92448722 -1.052418417022554 0.18944626751484134 +92547756 -1.0527168887030638 0.18928299033085522 +92646790 -1.053228378327836 0.18932966907640594 +92745824 -1.0525243194662268 0.18959393260384372 +92844858 -1.0526474436086233 0.1900279583429968 +92943892 -1.0528893100037373 0.1897954647009534 +93042926 -1.0532404471422745 0.189543706235109 +93141960 -1.05315069060686 0.18994680139461617 +93240994 -1.05338739265195 0.18997708045388004 +93340028 -1.052969579179098 0.19064085289559937 +93439062 -1.05367489042043 0.1902375724579427 +93538096 -1.053391026798305 0.19061130012622016 +93637130 -1.0533813425045406 0.19042785858499522 +93736164 -1.0537840241770944 0.19086176199410226 +93835198 -1.0536219698342209 0.19092908810907722 +93934232 -1.054135832193481 0.19118572261075376 +94033266 -1.0542619422215345 0.19081477882216913 +94132300 -1.0542223669300608 0.19121627627856141 +94231334 -1.0547271194985601 0.19124999036767512 +94330368 -1.055270732873586 0.1914468493153511 +94429402 -1.0552536180801328 0.19151168219142836 +94528436 -1.055894220313701 0.19176527949298605 +94627470 -1.0558496252125622 0.1915433814966702 +94726504 -1.0559790654888697 0.19192496632046488 +94825538 -1.056609989239957 0.19221910056136776 +94924572 -1.0567469962861042 0.19220161600348598 +95023606 -1.0568590266928797 0.19271870467211294 +95122640 -1.0570705721431741 0.19340933125268886 +95221674 -1.0573597139486561 0.19346834721386663 +95320708 -1.0574499760164435 0.19362627855394404 +95419742 -1.0575565122464492 0.19404174078160863 +95518776 -1.0580795304785298 0.19469017459678328 +95617810 -1.058327281080278 0.19487804690443356 +95716844 -1.0580967581998362 0.19516600460440234 +95815878 -1.0586444530214887 0.19539828737123277 +95914912 -1.0586560615656653 0.19587296601090848 +96013946 -1.0586817795273207 0.19621863929211844 +96112980 -1.0580631831395197 0.19694642087242514 +96212014 -1.058366095866788 0.1974019662993645 +96311048 -1.0584601428348337 0.19742027378593244 +96410082 -1.0583428741234773 0.19786866637112907 +96509116 -1.057999121458795 0.198225167567966 +96608150 -1.0578153389066498 0.19865083997336674 +96707184 -1.0580592037650935 0.19890297465026752 +96806218 -1.0582761661345386 0.19890625291164618 +96905252 -1.0580936578330862 0.19894267671465024 +97004286 -1.057882316591407 0.19969036992459327 +97103320 -1.057803467407093 0.1998455122247097 +97202354 -1.0576387937725393 0.1996464082976004 +97301388 -1.057679305751949 0.20019903700900446 +97400422 -1.0577360967349554 0.2002975227003503 +97499456 -1.057628212382835 0.20020671013349933 +97598490 -1.0576559595230213 0.20053042811181565 +97697524 -1.0574032207881265 0.20063361084507186 +97796558 -1.057721951305301 0.2006288793755518 +97895592 -1.057576759945112 0.2008926948610179 +97994626 -1.0572627403203205 0.20128714714518126 +98093660 -1.0574490460874555 0.20115284664028227 +98192694 -1.0573389931307504 0.20140800878238685 +98291728 -1.0574411309657943 0.2015848698705167 +98390762 -1.0575630621386338 0.201699486227093 +98489796 -1.057691777282292 0.20193482440735483 +98588830 -1.0576983974242922 0.20176051729815744 +98687864 -1.057208131547397 0.20162990815832493 +98786898 -1.0577119009646891 0.20195492454225464 +98885932 -1.0582125293529183 0.20203601225143983 +98984966 -1.0574430976265985 0.20220919938898396 +99084000 -1.0579107675876727 0.20268042574605952 +99183034 -1.0573378974083558 0.20302976046334256 +99282068 -1.0580993684249096 0.20280564288238997 +99381102 -1.0583991851796308 0.20272170499494238 +99480136 -1.0583519574964695 0.2026598906133066 +99579170 -1.0578849508614194 0.20324996968222128 +99678204 -1.058232629941955 0.20274040499842108 +99777238 -1.0580416455763044 0.20309060074791588 +99876272 -1.0581979411634133 0.2033746699490521 +99975306 -1.057950631475943 0.20363744996227603 +100074340 -1.0578433975660533 0.20418448900244285 +100173374 -1.058348377291142 0.20378597139634627 +100272408 -1.0584584472028025 0.20386508575367948 +100371442 -1.0585777259662645 0.20436932822644063 +100470476 -1.0587800119093442 0.20401285241398096 +100569510 -1.0589377285711439 0.20456357961864788 +100668544 -1.0591354207090926 0.2046266104547932 +100767578 -1.0590753266835373 0.20469106630490175 +100866612 -1.05892382092059 0.20456537429452348 +100965646 -1.058958291784481 0.20492069099705099 +101064680 -1.0588278305532868 0.2053111249214292 +101163714 -1.059189456754283 0.2051918167710904 +101262748 -1.0592607718715674 0.20535403425809878 +101361782 -1.0592379365757458 0.20571570425471866 +101460816 -1.0594307382064947 0.2056396171437031 +101559850 -1.059629475794208 0.20587181444438357 +101658884 -1.0595854798889677 0.2060749479431602 +101757918 -1.0596394891230756 0.2060006998426898 +101856952 -1.0589330960030476 0.20666804192098825 +101955986 -1.060469046679331 0.20597481709835413 +102055020 -1.0591917868500376 0.20667929319926173 +102154054 -1.059724996888481 0.20659161249912827 +102253088 -1.0599955402137229 0.2068161089223667 +102352122 -1.0597898840981428 0.20654993001321917 +102451156 -1.0596927611583393 0.2071159165759614 +102550190 -1.0607379059177222 0.20785035091037476 +102649224 -1.060922498574818 0.20719910862639562 +102748258 -1.0607553009861905 0.20795634033782814 +102847292 -1.0605037870835723 0.20799498475134817 +102946326 -1.060395230230129 0.20839132302919705 +103045360 -1.0606131241002297 0.208200393785974 +103144394 -1.0612026835681236 0.20841202523945238 +103243428 -1.0602212945886045 0.20862817419693408 +103342462 -1.0606532541500109 0.209114552509745 +103441496 -1.0610812585652416 0.20847472141058193 +103540530 -1.061288314900824 0.2088210918225968 +103639564 -1.0612711933347663 0.20915988097397598 +103738598 -1.0612358866902118 0.20898590165217565 +103837632 -1.0615672348897236 0.20922759146732792 +103936666 -1.0614917812940068 0.20961091717074465 +104035700 -1.0615796609368104 0.20944570747912178 +104134734 -1.0612062323425213 0.20979789047230557 +104233768 -1.0616218511744968 0.21027911012517267 +104332802 -1.0613984854239364 0.2096500438874343 +104431836 -1.0619695839941536 0.21020458133517098 +104530870 -1.0615822407834516 0.21028701266040986 +104629904 -1.0617547489188268 0.2102998981548177 +104728938 -1.0614943157798882 0.21002465543099005 +104827972 -1.0621678845634897 0.21087351029992837 +104927006 -1.0617450734270955 0.21048178508372803 +105026040 -1.0623274346450449 0.21088800362755775 +105125074 -1.0626751368006546 0.21149329246664217 +105224108 -1.0623400203077835 0.21169106383857877 +105323142 -1.0625367643737307 0.21104775193259065 +105422176 -1.0624620073336994 0.21213212627885064 +105521210 -1.0623774112396442 0.2121095069129322 +105620244 -1.0626294971280708 0.2118649987075478 +105719278 -1.0626053552788717 0.21211447453508797 +105818312 -1.062424783195532 0.21239590687868676 +105917346 -1.06288687410697 0.2126528373215423 +106016380 -1.0628934234450789 0.21244088197045327 +106115414 -1.0632650976419904 0.21251780888185018 +106214448 -1.0633219226715942 0.21290895397640364 +106313482 -1.0632822597474108 0.21269633199763163 +106412516 -1.0633049194745157 0.2131583193953317 +106511550 -1.0632633387223582 0.21306621965380432 +106610584 -1.0636435882412834 0.21280582803390646 +106709618 -1.063498872495511 0.21344950151862943 +106808652 -1.0635830846371281 0.2136601519443192 +106907686 -1.0635078178590431 0.2142794327520827 +107006720 -1.0634593008039857 0.2138047887836259 +107105754 -1.0637703049427787 0.2143680550255129 +107204788 -1.0644122559880231 0.21425182716623478 +107303822 -1.0641282274857233 0.21446446286376808 +107402856 -1.0644927624167349 0.21488081040124993 +107501890 -1.0641851481204876 0.21479756656763072 +107600924 -1.0639075932975546 0.21490918439743095 +107699958 -1.064249486189092 0.21489179911387674 +107798992 -1.0646470368180523 0.2151387946197168 +107898026 -1.0643285404021352 0.2153934971852462 +107997060 -1.0645717582745875 0.21537241404001675 +108096094 -1.064335531484884 0.2160572405500234 +108195128 -1.0643453411427446 0.21581323873067795 +108294162 -1.0646824237299368 0.21583368825820223 +108393196 -1.0649448745824772 0.21584607213319096 +108492230 -1.0651846224131774 0.21622214345731403 +108591264 -1.0653278938936472 0.21631033448032447 +108690298 -1.065502553149561 0.2162564457068175 +108789332 -1.065457077633588 0.21678797533267194 +108888366 -1.0653104344230828 0.21694149364800178 +108987400 -1.065112524351347 0.2167141172583691 +109086434 -1.0651420188336647 0.21695833774676648 +109185468 -1.0655529696123893 0.217345546502207 +109284502 -1.065703032265948 0.2173156209266522 +109383536 -1.0660481962979382 0.2176281454144985 +109482570 -1.065785441280904 0.21773359857313657 +109581604 -1.065866996689736 0.21817086933728444 +109680638 -1.0660942271510234 0.21809696876170143 +109779672 -1.0661677639008076 0.21824089321659512 +109878706 -1.0661699058736827 0.21810950460612477 +109977740 -1.0665141583850943 0.21872577753583103 +110076774 -1.0665075879439074 0.21884788944881622 +110175808 -1.0665077905358966 0.21916583895799135 +110274842 -1.0670074093069988 0.21931868465953946 +110373876 -1.0662329888533626 0.21881263937724146 +110472910 -1.0667199496626183 0.2192012248620091 +110571944 -1.0670435866570298 0.2193004323203871 +110670978 -1.067303001281471 0.21976222674849102 +110770012 -1.0668814353493463 0.21881235556183148 +110869046 -1.0667011099260604 0.22007794141652856 +110968080 -1.0669181721156917 0.21974598666898817 +111067114 -1.0675264806730935 0.22011991228799796 +111166148 -1.0671764915357738 0.2202328064240152 +111265182 -1.0675923306751052 0.22066668095429953 +111364216 -1.0674653917590293 0.22064742661623607 +111463250 -1.0679587126239731 0.22065175963949482 +111562284 -1.0680599578685444 0.2210602936234089 +111661318 -1.0676471784488537 0.2208564439772596 +111760352 -1.0674112786639927 0.22077632136997974 +111859386 -1.0674125993634178 0.2209133606132892 +111958420 -1.0678646536496004 0.22148912770518225 +112057454 -1.0681670792425635 0.22115475354627012 +112156488 -1.0679063882909414 0.22181393245285946 +112255522 -1.0685785083781056 0.22209787823987226 +112354556 -1.0684150766441554 0.22212623655325253 +112453590 -1.0683726783931058 0.22238000485453635 +112552624 -1.0686826450084126 0.2222763408691804 +112651658 -1.0686466786544264 0.22238261517975436 +112750692 -1.0684215724352133 0.22262093344109257 +112849726 -1.0687864585472855 0.22323886691818637 +112948760 -1.0688813388668539 0.22235883806873227 +113047794 -1.068995738491165 0.22287188507314903 +113146828 -1.0691664613530814 0.22342760773854095 +113245862 -1.0692316633784327 0.22345313392287722 +113344896 -1.0692452294145833 0.22352904888116587 +113443930 -1.069049697808486 0.22346081383378544 +113542964 -1.0691364510667245 0.2237895840978518 +113641998 -1.0691535039341316 0.22420690000150126 +113741032 -1.0696855116638906 0.2244375887097815 +113840066 -1.0696150621405607 0.224192864088723 +113939100 -1.0695519947209577 0.2242992334707473 +114038134 -1.0696847757240304 0.2245483837382534 +114137168 -1.0700685836718924 0.22452019043269103 +114236202 -1.0701212019438808 0.22460080864502704 +114335236 -1.070354902466465 0.22501110621291476 +114434270 -1.07024094105661 0.22489823698692069 +114533304 -1.0699573785995198 0.225409236863685 +114632338 -1.0702355783519644 0.22539014865483337 +114731372 -1.0703444906443496 0.22539477016069487 +114830406 -1.0703910802708845 0.225755943505616 +114929440 -1.0705423562307257 0.22596370631641313 +115028474 -1.0706302111616148 0.22579348296719853 +115127508 -1.0711399858853266 0.2256569960494834 +115226542 -1.0709632778501696 0.2264128253653536 +115325576 -1.071217535009359 0.2261767831655556 +115424610 -1.0708995456311858 0.22639601408299465 +115523644 -1.07097494764194 0.22714174877130539 +115622678 -1.0710150615553138 0.22666795274720536 +115721712 -1.0716691802768157 0.22700208844291644 +115820746 -1.0713724752515517 0.22757816834476244 +115919780 -1.071601124522205 0.22734724184226787 +116018814 -1.071334377403693 0.22749536465465614 +116117848 -1.0715065302879756 0.22760113612684937 +116216882 -1.0715284466559458 0.22777537817668744 +116315916 -1.0718327220614001 0.22741637921208582 +116414950 -1.0717766166151237 0.22780341842702548 +116513984 -1.0723262224312087 0.22789215295456758 +116613018 -1.0724878555765676 0.2280649801399729 +116712052 -1.072241465528973 0.2283945124778173 +116811086 -1.0719614858049804 0.2284309125863387 +116910120 -1.0726461853808493 0.22887635789510943 +117009154 -1.0723976506957067 0.22872422407629223 +117108188 -1.072691090563179 0.22900795082404396 +117207222 -1.0725929693201375 0.22933108142566735 +117306256 -1.072651835375254 0.22886216201230022 +117405290 -1.0727421274783162 0.22950142946648455 +117504324 -1.072953965851628 0.2292704416534895 +117603358 -1.0735044972131567 0.2298769051289896 +117702392 -1.0735851727494587 0.22967022588995864 +117801426 -1.0731881022264496 0.22992853458215876 +117900460 -1.073255163891431 0.23005746094548163 +117999494 -1.0736808449954423 0.23011130963183007 +118098528 -1.0733799670186084 0.2304157844630041 +118197562 -1.0735123637253463 0.23045605476534803 +118296596 -1.073679138399264 0.23045196755312988 +118395630 -1.0735110328932547 0.23077493365835844 +118494664 -1.0739649564060223 0.2310814729848427 +118593698 -1.0737433589926928 0.23113586937703762 +118692732 -1.0742103881911331 0.23134951141404567 +118791766 -1.074031787786197 0.23156438408427127 +118890800 -1.0741602470518954 0.231811477541758 +118989834 -1.0744216697134616 0.23151755915454628 +119088868 -1.074233291236199 0.23210447400655382 +119187902 -1.0742896638985768 0.23247837625241227 +119286936 -1.0742792170952116 0.2321327082316474 +119385970 -1.0744217636714595 0.2322312717472552 +119485004 -1.0748468160910594 0.2320583263572286 +119584038 -1.0747247685155643 0.23241246101130397 +119683072 -1.0747772737633774 0.23253050847438325 +119782106 -1.0754670066905399 0.23277012888714713 +119881140 -1.075125270358296 0.23263923987034157 +119980174 -1.0751882547419396 0.23301200593608262 +120079208 -1.075158443462879 0.2333721149505361 +120178242 -1.075540905896265 0.23336737452163228 +120277276 -1.0756750177879235 0.23373628383688855 +120376310 -1.0762822985189602 0.23378576282698454 +120475344 -1.0758100919817892 0.23413110255842295 +120574378 -1.075036122371567 0.2338296183568311 +120673412 -1.0759886401194787 0.234287316468458 +120772446 -1.0753229486435862 0.23405179789718764 +120871480 -1.0757352546687347 0.23446648222136787 +120970514 -1.0755886818137923 0.2343691994553109 +121069548 -1.075885452751619 0.23417955676076743 +121168582 -1.0762828358015397 0.2349868950671075 +121267616 -1.0760006050998998 0.23520569604735803 +121366650 -1.0760466202931638 0.2352392344407119 +121465684 -1.0768547327119828 0.23561211591865341 +121564718 -1.0765754020414668 0.23569708043994123 +121663752 -1.0768185887235249 0.2356941030105609 +121762786 -1.0770692078104103 0.23561184752794653 +121861820 -1.076575813646981 0.23516920928153406 +121960854 -1.07695001241495 0.23641707540585993 +122059888 -1.0763457195340536 0.2362159565658212 +122158922 -1.0765291029516326 0.23606013863269126 +122257956 -1.0773097715337667 0.2362251484280284 +122356990 -1.0775708412132796 0.23672251210714865 +122456024 -1.0774691258341986 0.23680951519666807 +122555058 -1.0769218445040447 0.23692422524226414 +122654092 -1.077848212991119 0.23722053740405574 +122753126 -1.077457873777342 0.23719613119332802 +122852160 -1.0778472566385815 0.23710884117184172 +122951194 -1.077907285049871 0.2371215138062474 +123050228 -1.0772297311445587 0.23709612592660373 +123149262 -1.0774866583569762 0.23724053741874684 +123248296 -1.0776165033224112 0.23822146085952983 +123347330 -1.0778744242856808 0.2385640566365962 +123446364 -1.0783589370648476 0.23761022630207043 +123545398 -1.078318066049644 0.23824814793831445 +123644432 -1.0786867296902305 0.23837846474329483 +123743466 -1.077792828385799 0.23857293015876754 +123842500 -1.0779599137846065 0.2382095689500548 +123941534 -1.0781923835613825 0.23894444887821864 +124040568 -1.0784190919735375 0.23843846726944593 +124139602 -1.07871798035057 0.2390680504312646 +124238636 -1.0788271996525982 0.23925659735082133 +124337670 -1.0790458873979498 0.23952494830363633 +124436704 -1.078987555302971 0.2397787329185582 +124535738 -1.0789044640189436 0.23979913521229784 +124634772 -1.079043315392714 0.23970678591312602 +124733806 -1.0792777009615098 0.23978076866682083 +124832840 -1.079253660779165 0.2398622931856358 +124931874 -1.079707355552329 0.24026086808642957 +125030908 -1.0793029097841857 0.24026802666795546 +125129942 -1.0795444202417877 0.24076451147503636 +125228976 -1.0794530986650734 0.24032756512983253 +125328010 -1.0800722505611602 0.2404904780380635 +125427044 -1.0799110047897487 0.24058477127342467 +125526078 -1.0797006666410642 0.24069009632188554 +125625112 -1.0803476557142686 0.24126456897237658 +125724146 -1.0802304990786964 0.24140289882679145 +125823180 -1.0796985975725701 0.24143928657228458 +125922214 -1.0802306251289777 0.24138288296106397 +126021248 -1.0806666438848527 0.24184330441157706 +126120282 -1.080723750644152 0.24191935905745796 +126219316 -1.080681749108679 0.2419230013931039 +126318350 -1.0808029420507843 0.24188344467355902 +126417384 -1.0806352565463742 0.2419947672047908 +126516418 -1.0813848559594035 0.2423015521462145 +126615452 -1.0811901386000815 0.2424185553448789 +126714486 -1.0812291851623714 0.24259204254703198 +126813520 -1.0811909297000866 0.24255886232224097 +126912554 -1.081252048516285 0.2428453009708059 +127011588 -1.0816409251745855 0.2432239899161732 +127110622 -1.0815473425238613 0.24285503923530283 +127209656 -1.08155694471374 0.24316197555854663 +127308690 -1.0814954671872947 0.24330466164382492 +127407724 -1.0821415578586238 0.2435642589930121 +127506758 -1.082126905813569 0.24341723800517595 +127605792 -1.082345133660212 0.24353487038190227 +127704826 -1.0820696726022316 0.24356018347969585 +127803860 -1.0824277778283244 0.2438167957390526 +127902894 -1.0823173800414194 0.24387776435329825 +128001928 -1.0823158540592903 0.24455573666542707 +128100962 -1.0824066180807808 0.24441617155819342 +128199996 -1.0829137608798052 0.24453435467537638 +128299030 -1.0825641984683905 0.24464802828822202 +128398064 -1.0826311393735515 0.24427768242522296 +128497098 -1.0829932945353111 0.24453697272580985 +128596132 -1.082776266625498 0.24520947611660787 +128695166 -1.0830289805050874 0.24513856187676888 +128794200 -1.0831995293389858 0.24460257938355043 +128893234 -1.0832097835572694 0.24537881124668168 +128992268 -1.083332376557234 0.24530660352594086 +129091302 -1.0834402111090444 0.24557147810323618 +129190336 -1.083617723259322 0.2457303685911568 +129289370 -1.0841369025551504 0.2458035436405522 +129388404 -1.0831272388087618 0.24591998227769685 +129487438 -1.0837814100663823 0.24592544341009778 +129586472 -1.0840128235678377 0.24667291893887364 +129685506 -1.083932438264904 0.24644106357874462 +129784540 -1.084137590764717 0.24649421658910017 +129883574 -1.0842057671694763 0.24674600010472503 +129982608 -1.0842122783684625 0.24632099606058722 +130081642 -1.0849597424854183 0.24659499143246988 +130180676 -1.0850400615826457 0.24704835590919977 +130279710 -1.0851173280554125 0.24739752775809537 +130378744 -1.0849210642813858 0.24739439780440173 +130477778 -1.0850147457219719 0.24700956352124717 +130576812 -1.0853246415850362 0.2476983107790411 +130675846 -1.0849088439681347 0.24746862006805703 +130774880 -1.0853271165539569 0.24777910581325321 +130873914 -1.085535752476383 0.24782691080213795 +130972948 -1.085736808485786 0.2476643625422023 +131071982 -1.0856092383809821 0.24807338726512748 +131171016 -1.085685730601805 0.24885672923777333 +131270050 -1.0858977898036766 0.24836314886242547 +131369084 -1.0861526054578114 0.24873647947144928 +131468118 -1.086124753098987 0.24889069265648295 +131567152 -1.0861945466538847 0.249007163060843 +131666186 -1.0862534220914264 0.24898237702810386 +131765220 -1.0871347524421768 0.24944753960327581 +131864254 -1.0871000598092455 0.24940366821988327 +131963288 -1.08686180782872 0.24937666492072333 +132062322 -1.0872185794029623 0.2495832972756577 +132161356 -1.0869733636593204 0.2497621488841653 +132260390 -1.087178900210573 0.2500115885558411 +132359424 -1.087646282190716 0.24997228936996338 +132458458 -1.0872230552240332 0.2506194779876106 +132557492 -1.0880220278899757 0.2502965460166642 +132656526 -1.0876726331379654 0.2506105866425045 +132755560 -1.0880122575887936 0.2510150101723604 +132854594 -1.0877268768186692 0.25131842900670925 +132953628 -1.0884656965558863 0.25131824574219686 +133052662 -1.0879255590203203 0.2515401947922164 +133151696 -1.0882224115734538 0.25206346753000275 +133250730 -1.0884393515498891 0.25180310227378483 +133349764 -1.0884246952422576 0.2521477342417096 +133448798 -1.0886128610745747 0.25279850953967387 +133547832 -1.0885871571991876 0.2526068690925144 +133646866 -1.08869147872486 0.2523121454423121 +133745900 -1.0887256696532195 0.2534369725920296 +133844934 -1.088427075696916 0.2529771504727866 +133943968 -1.0890545866011985 0.2534612421667415 +134043002 -1.0892485700460548 0.25334883126001095 +134142036 -1.0884954431235616 0.2536840853138066 +134241070 -1.0889903625136887 0.25360189267825844 +134340104 -1.088948758008594 0.25363073178926543 +134439138 -1.0887624873538997 0.2538830567756931 +134538172 -1.0885949055558042 0.2537135263485431 +134637206 -1.0889677044151516 0.25410535289163805 +134736240 -1.0891078196968247 0.25416448386590473 +134835274 -1.0891716356061238 0.2543924707590634 +134934308 -1.0895208808084909 0.25490996352747913 +135033342 -1.0886880627659734 0.2548921880836516 +135132376 -1.0894384343552836 0.25482114464911465 +135231410 -1.0890581634350134 0.25506313198664465 +135330444 -1.0894472544624654 0.2549829166904478 +135429478 -1.0892791909802149 0.25536722293850167 +135528512 -1.0896604226457145 0.2550530559853103 +135627546 -1.08939539139418 0.2554374228314511 +135726580 -1.0894793000126366 0.25559320572905747 +135825614 -1.089724749815313 0.2558029694345293 +135924648 -1.0900760755093573 0.25602468027753184 +136023682 -1.0895139730970655 0.25600967954729126 +136122716 -1.0898019265937935 0.2562095472075173 +136221750 -1.0901795984747282 0.2564137730939922 +136320784 -1.0901038507219891 0.25608916445483504 +136419818 -1.0901526891652864 0.25646788278279486 +136518852 -1.0902377808809318 0.2565033655535513 +136617886 -1.0902969192322314 0.25677012267077937 +136716920 -1.0903604520421613 0.25665747584645004 +136815954 -1.0903949317899049 0.2569105417932966 +136914988 -1.0905055073532262 0.25700834792311816 +137014022 -1.0908118465799137 0.2566819828912328 +137113056 -1.0906508874314738 0.2568886840903761 +137212090 -1.090772087116757 0.25742515036592617 +137311124 -1.0907905138464526 0.2573067792831316 +137410158 -1.0907977715053432 0.25721568900491315 +137509192 -1.0911461721155242 0.2574760738577351 +137608226 -1.091333877536669 0.257618018401751 +137707260 -1.0910447960994176 0.2576489438207527 +137806294 -1.091490901540531 0.2578086869344372 +137905328 -1.0910095028312439 0.2576421555820629 +138004362 -1.091328386023552 0.258333233432631 +138103396 -1.091302055584148 0.25860569391728755 +138202430 -1.0909761655564356 0.25841994904088106 +138301464 -1.0916859024217693 0.25849644024424784 +138400498 -1.0918118935734966 0.25853996494807074 +138499532 -1.0922758127447427 0.25926260654716365 +138598566 -1.0919849108137663 0.25870209654512816 +138697600 -1.0919204328188343 0.2591369401700882 +138796634 -1.092030136191573 0.25887271818571217 +138895668 -1.0921570201927382 0.25934448504360924 +138994702 -1.091865765149955 0.2594348790692555 +139093736 -1.0924450714240512 0.25963500793633126 +139192770 -1.0926487648123997 0.2594506986303066 +139291804 -1.0923632772853553 0.2596737425139538 +139390838 -1.092528742686212 0.25990572772797194 +139489872 -1.0922509871989183 0.2603786974242444 +139588906 -1.0926967126178349 0.2601735871896251 +139687940 -1.0924975169433773 0.2600138488133297 +139786974 -1.0933036200340813 0.2600404372905358 +139886008 -1.092936772580514 0.26026895328395155 +139985042 -1.0932898614697708 0.26013529987710965 +140084076 -1.0928429146281176 0.2610558537423987 +140183110 -1.0926230900537968 0.26042874929401255 +140282144 -1.0932678982084485 0.261003452660345 +140381178 -1.0931508691939993 0.260889880162951 +140480212 -1.0933626275048451 0.26103459350563585 +140579246 -1.0934266098346122 0.2610940257533921 +140678280 -1.0936119475459136 0.261777029760267 +140777314 -1.0934248457946818 0.2612688116640346 +140876348 -1.0934965965513301 0.2618327534709479 +140975382 -1.093483587353143 0.26160466452987363 +141074416 -1.0939148849680347 0.2617463978469663 +141173450 -1.0937785645230813 0.26200498833324687 +141272484 -1.0941133140769992 0.26237916866914296 +141371518 -1.0940332990054262 0.26192833772518587 +141470552 -1.094330994151667 0.2621120980920328 +141569586 -1.0943163690023663 0.26244476460852556 +141668620 -1.0942851710497064 0.2626066594425985 +141767654 -1.0944987351791815 0.26267450310849605 +141866688 -1.0943855594257828 0.2626588251418038 +141965722 -1.0947279130777139 0.2631561206549621 +142064756 -1.094879241800203 0.262983979172651 +142163790 -1.0952011016109209 0.262908449902534 +142262824 -1.0949394112013433 0.2632894726823671 +142361858 -1.09504941369096 0.26302276072267605 +142460892 -1.0948873252644797 0.2627978704084063 +142559926 -1.0952053169529674 0.26354518725755877 +142658960 -1.0957307758253751 0.2638263838351226 +142757994 -1.0953383899783704 0.2634692248127482 +142857028 -1.0956204173613389 0.2643019795112052 +142956062 -1.0959276543644763 0.2640132402497369 +143055096 -1.0955392154127597 0.2639130186907991 +143154130 -1.0956572046558488 0.26424218318414017 +143253164 -1.0960265622125618 0.2641832214504725 +143352198 -1.0960150949466907 0.2646068992929944 +143451232 -1.0961250109970666 0.2643457235374666 +143550266 -1.0958686351402263 0.26495534361217116 +143649300 -1.0960574623468378 0.2648213466995888 +143748334 -1.096138742402503 0.26457409369907947 +143847368 -1.096502831741177 0.2650699013791715 +143946402 -1.0965328325932562 0.26495125854741375 +144045436 -1.0964433326351581 0.2648735629363469 +144144470 -1.0970097903661875 0.26561942868098176 +144243504 -1.0969910770017928 0.2658374257795 +144342538 -1.0970559777985995 0.2658252049446146 +144441572 -1.0968128405031008 0.26550529501017184 +144540606 -1.0968516831891262 0.26590863925649877 +144639640 -1.0970707842955851 0.2655384296230246 +144738674 -1.097043933752725 0.2658396477721866 +144837708 -1.097292523998471 0.26597572847931644 +144936742 -1.0972018795371716 0.26634079513649883 +145035776 -1.0976577569132828 0.2665238733712727 +145134810 -1.0972379837129786 0.26688969751113173 +145233844 -1.0979974432461668 0.26677591102295695 +145332878 -1.097575185621892 0.2666655348826894 +145431912 -1.0977861308294432 0.2671595304672374 +145530946 -1.098403756715557 0.2669461699952465 +145629980 -1.0984135774108839 0.266904777177397 +145729014 -1.098232421919104 0.26753681908021737 +145828048 -1.0983299569516243 0.26748084327431987 +145927082 -1.0981183403984092 0.26730537633909 +146026116 -1.0980878182477443 0.26799691276403664 +146125150 -1.098672388334976 0.2675759398266947 +146224184 -1.0987393331203812 0.2677537446615717 +146323218 -1.098678360686321 0.2675045645073858 +146422252 -1.0988785807431656 0.2675425659793348 +146521286 -1.0985024462901478 0.2676375464611503 +146620320 -1.0990906462241814 0.26830732678679675 +146719354 -1.0985989093757267 0.2681097427396463 +146818388 -1.0992087410211868 0.2684056550630874 +146917422 -1.0996019428234696 0.26817849086283085 +147016456 -1.0992281950471667 0.26867080321108044 +147115490 -1.099197758381978 0.2688997337451219 +147214524 -1.0993447639713942 0.26887422594210475 +147313558 -1.0996163628779778 0.2690436654012626 +147412592 -1.099873663199738 0.2693217166449054 +147511626 -1.099604275060612 0.26921871542172904 +147610660 -1.1000257457831364 0.2694321654548351 +147709694 -1.100028744742843 0.26929080347496653 +147808728 -1.099949895506928 0.2694315818344799 +147907762 -1.0999743820763594 0.26969677914685664 +148006796 -1.1000985679839086 0.2696640221675932 +148105830 -1.101005008837766 0.26970061241600607 +148204864 -1.1001027494895386 0.26993375282166493 +148303898 -1.1005488088138422 0.27036229340589546 +148402932 -1.1005365083323997 0.26993634686191764 +148501966 -1.1007483329642764 0.2706193044371305 +148601000 -1.1005985907603602 0.2706230511361099 +148700034 -1.1009430560728217 0.2705672316436669 +148799068 -1.1009507678079318 0.2707962728779179 +148898102 -1.1009525920071803 0.27103515330713535 +148997136 -1.1011199078625384 0.2709683193170659 +149096170 -1.1007912782562843 0.2708538929802446 +149195204 -1.10112008260733 0.2708272156958521 +149294238 -1.1013491114405802 0.27104686050571764 +149393272 -1.101549469154086 0.27166562221674256 +149492306 -1.1013564627646577 0.2717380097722005 +149591340 -1.101410835222421 0.2715651727595615 +149690374 -1.1017536507868697 0.2716507296226188 +149789408 -1.1021239737738788 0.2717807677323637 +149888442 -1.101821907600936 0.2719074844983472 +149987476 -1.1020170025193932 0.2719380333775972 +150086510 -1.1022706827349091 0.27133474203650026 +150185544 -1.1021086109096332 0.272160298100223 +150284578 -1.1026769666156258 0.27216039587104457 +150383612 -1.1026160505042617 0.2725491090268489 +150482646 -1.102529007622767 0.27233525541121434 +150581680 -1.1026153231231954 0.27270594487614047 +150680714 -1.103030992530955 0.27268562813018077 +150779748 -1.1028360574271139 0.27281715931165806 +150878782 -1.1025643760429318 0.27305357119506396 +150977816 -1.1029190196808176 0.2735152272802434 +151076850 -1.1030429136707498 0.27309201143194856 +151175884 -1.1030934591621386 0.27339076212794217 +151274918 -1.1032550050646104 0.273461008396507 +151373952 -1.1037174397978178 0.2732579964057658 +151472986 -1.1035687339095033 0.27355833789062695 +151572020 -1.103429269615397 0.27347526013897705 +151671054 -1.1038113937191842 0.27361029196654485 +151770088 -1.1042840540237182 0.2735588974509724 +151869122 -1.103958675376054 0.2743159353487839 +151968156 -1.1038207634398862 0.27381232700803754 +152067190 -1.104027065311978 0.2747309676595426 +152166224 -1.1039629657488963 0.2745495238716122 +152265258 -1.1041638323971614 0.2746713452758187 +152364292 -1.1043507674208533 0.2746261039156397 +152463326 -1.104340991244078 0.27478615495742303 +152562360 -1.1046882052364824 0.27456280139401434 +152661394 -1.1043122087469 0.2748967813537559 +152760428 -1.104586490177077 0.2749001475786895 +152859462 -1.104965972311483 0.27493471290974697 +152958496 -1.104780654133554 0.2753535612605242 +153057530 -1.1047516180390469 0.2758600206659691 +153156564 -1.104652179085977 0.2756431509884239 +153255598 -1.1050917493194026 0.2756341409671919 +153354632 -1.1050669386442675 0.27574056313809786 +153453666 -1.1051600956862218 0.275959775409341 +153552700 -1.1054072199457357 0.2758676938576699 +153651734 -1.1053570486026578 0.27620877851738496 +153750768 -1.1057946441744817 0.27585785252820777 +153849802 -1.10572479745844 0.27610309398155336 +153948836 -1.1055523875039683 0.2766954635420022 +154047870 -1.1058405534172917 0.2766667104681193 +154146904 -1.1058681201675173 0.27679142456083067 +154245938 -1.105586703892866 0.2763873966028149 +154344972 -1.1061934658091102 0.2767098572615787 +154444006 -1.1063633147107896 0.2768530378090537 +154543040 -1.1066855550620305 0.2771374116144739 +154642074 -1.1065728150457828 0.27711310079547263 +154741108 -1.1068940297960366 0.2772226990653155 +154840142 -1.1064042912038963 0.2771100331865771 +154939176 -1.1068403995984875 0.2770737154736309 +155038210 -1.1067403728291447 0.2774349996211297 +155137244 -1.1068512398380166 0.2771302642114549 +155236278 -1.1068338842945635 0.27731976864140934 +155335312 -1.1069279348830878 0.27741946411174995 +155434346 -1.10709487041733 0.2784797022967073 +155533380 -1.107091859776733 0.27773171865837915 +155632414 -1.1072128851772705 0.2781902686193327 +155731448 -1.1079550205146955 0.2785518888762619 +155830482 -1.1071606364381879 0.2783570836737419 +155929516 -1.1073536112046503 0.2784307948296219 +156028550 -1.1072481349855656 0.2785449672241636 +156127584 -1.1076001913740292 0.2791694243811819 +156226618 -1.107458551258002 0.27891121799265656 +156325652 -1.1082116812201672 0.27878688024192144 +156424686 -1.1077143287598343 0.2795120736427584 +156523720 -1.1085917482657297 0.27899881332603044 +156622754 -1.1081117611146782 0.27895447785889793 +156721788 -1.1076977162853383 0.2794878737555745 +156820822 -1.1084033526771047 0.2792392959563152 +156919856 -1.1083619891326617 0.27948562726421444 +157018890 -1.1086485867793279 0.27974648033295385 +157117924 -1.1085319879977553 0.27954187316039814 +157216958 -1.1087388178043955 0.27932344197725173 +157315992 -1.1094773181150164 0.2805021468896305 +157415026 -1.1092396765165882 0.28027506687789994 +157514060 -1.1092086156907166 0.2802075854617894 +157613094 -1.1088901078176787 0.2803949347695768 +157712128 -1.1089930949748363 0.27984583531044227 +157811162 -1.1089992041970187 0.280327839043867 +157910196 -1.109358497980124 0.28046286553651856 +158009230 -1.1094246625169366 0.28037496788289357 +158108264 -1.1098093971886185 0.2811017135692029 +158207298 -1.109289075872749 0.2804519661718578 +158306332 -1.1098211110238267 0.2813548871617465 +158405366 -1.1099397199520624 0.2811219049142321 +158504400 -1.1100976624283394 0.281100347248518 +158603434 -1.109728666087915 0.28098809207324926 +158702468 -1.1096486785684745 0.28120692216424276 +158801502 -1.1102328161186328 0.2816720484566634 +158900536 -1.1106460240711908 0.28161813577711614 +158999570 -1.1098948480373974 0.28121807582975394 +159098604 -1.110690573011739 0.28142191661835503 +159197638 -1.1110325106555214 0.2818901439457541 +159296672 -1.1103855170306625 0.2817920630290097 +159395706 -1.1106402136143516 0.282087198187914 +159494740 -1.1107015849905755 0.2824947594375689 +159593774 -1.1109719032608134 0.28255111615489703 +159692808 -1.1108508234844077 0.2827744593768462 +159791842 -1.110629629913074 0.2816638384749928 +159890876 -1.1109638218027347 0.28269576040749655 +159989910 -1.1106708995165542 0.28254163757407486 +160088944 -1.1113721884995125 0.282613719213749 +160187978 -1.111694661763684 0.2830422996685382 +160287012 -1.111705800027349 0.2831612539461503 +160386046 -1.1120440381970695 0.28302965725925155 +160485080 -1.1119013055350897 0.28305959310355616 +160584114 -1.1125021007705835 0.2832231832923634 +160683148 -1.1115292184874617 0.2825692107197231 +160782182 -1.1113755659266946 0.28300729550081405 +160881216 -1.1116946639761782 0.28399197990752345 +160980250 -1.1122620952482818 0.2836318326009523 +161079284 -1.1124198441470448 0.2835989744564308 +161178318 -1.112545341181477 0.28373663835812946 +161277352 -1.112634778979919 0.28457249346911384 +161376386 -1.1131091412289078 0.2840185445449697 +161475420 -1.1124132157968323 0.28371674594126073 +161574454 -1.1126343696137226 0.2840275883718255 +161673488 -1.1127407929074167 0.28433037369646463 +161772522 -1.112043669246326 0.2841497297702984 +161871556 -1.1131610314084444 0.28450574092903735 +161970590 -1.1122538503267811 0.28464548167140497 +162069624 -1.1129389338305886 0.2846920165706528 +162168658 -1.1124974127864458 0.2848087462755673 +162267692 -1.1134425235350103 0.28463753973896755 +162366726 -1.1137599955751163 0.2856278122337017 +162465760 -1.1139717061783632 0.28547628291911176 +162564794 -1.1129636976400348 0.28539615529856405 +162663828 -1.1134848567518085 0.28545784921897754 +162762862 -1.1135151467644633 0.28516993401973567 +162861896 -1.1136957692599578 0.2857938141158081 +162960930 -1.1135014100523701 0.2860160968097318 +163059964 -1.1142379935700935 0.2855939726450496 +163158998 -1.114077895496844 0.28557277585359314 +163258032 -1.1140611251484878 0.28563846575496715 +163357066 -1.11465977248481 0.28613993581148744 +163456100 -1.1138936652813605 0.2860119725070745 +163555134 -1.1142945902838493 0.2859719015748927 +163654168 -1.1146526632649125 0.2861339246260018 +163753202 -1.1145065088086041 0.286592158155787 +163852236 -1.1143602209252526 0.2862712754001456 +163951270 -1.1144321148357526 0.2868833107704147 +164050304 -1.1150267328819434 0.28657540102761214 +164149338 -1.1150894652330738 0.2863301883935099 +164248372 -1.114972536586033 0.28668847898544403 +164347406 -1.1152034676546523 0.2875233681996255 +164446440 -1.1155217805133886 0.28743996794002435 +164545474 -1.115489010627505 0.28678161759274234 +164644508 -1.11538703484122 0.2869819965687825 +164743542 -1.1153382716877713 0.2871334376287785 +164842576 -1.1160683676487693 0.287656420023086 +164941610 -1.116005672984957 0.2877482439847291 +165040644 -1.115831310350846 0.2876592169064161 +165139678 -1.1157572076800522 0.2879809912193313 +165238712 -1.1163572158177828 0.2876057175611725 +165337746 -1.1159013428680578 0.2882669304485134 +165436780 -1.1162318787377878 0.28757645814440386 +165535814 -1.1160081800733257 0.2879722641866796 +165634848 -1.1166100607937048 0.28808370438929887 +165733882 -1.1168382603429294 0.28853114912513333 +165832916 -1.116026430200644 0.28837597425553474 +165931950 -1.1168966904042983 0.2888177799774004 +166030984 -1.1173640081657727 0.2890451373354331 +166130018 -1.1167513585631699 0.28898224720507654 +166229052 -1.1169082292144732 0.2886976168443265 +166328086 -1.1169535978906544 0.28877865965953126 +166427120 -1.1169484318688865 0.2886301218479799 +166526154 -1.1172717793935807 0.28865513815293276 +166625188 -1.1170248867123356 0.28911650320979165 +166724222 -1.117297873505457 0.28884253007280963 +166823256 -1.117146589557088 0.28926134680926346 +166922290 -1.1174249052392906 0.2897350085312347 +167021324 -1.1175541709733765 0.2899958598352369 +167120358 -1.118051322307829 0.2899408434750229 +167219392 -1.118254305638266 0.2898663041403726 +167318426 -1.117986994875 0.2891178373886358 +167417460 -1.1178813962016838 0.2900055148689466 +167516494 -1.1179735205205334 0.2900459016716628 +167615528 -1.1181244447136585 0.2903012953861953 +167714562 -1.1184438704643274 0.2906586508957937 +167813596 -1.1179246889638108 0.2897560278393028 +167912630 -1.118824909030792 0.29085464147962226 +168011664 -1.1188504344714187 0.29084204092812566 +168110698 -1.1179872068727152 0.29145604264503405 +168209732 -1.1184930967987914 0.2903206041470585 +168308766 -1.1188626201063039 0.2912811041997735 +168407800 -1.118415569807825 0.29065392920693844 +168506834 -1.1189224852739525 0.2905555469874269 +168605868 -1.1187017529260963 0.2914475377300552 +168704902 -1.1182741099120332 0.2910158446640769 +168803936 -1.11994520300074 0.2915166643009378 +168902970 -1.119201547667262 0.29099352729303063 +169002004 -1.1191384088663459 0.2913772133496614 +169101038 -1.120288049926435 0.29119080108975715 +169200072 -1.1203639237384118 0.2914613575831161 +169299106 -1.1194483071532828 0.2923769174799494 +169398140 -1.119026177575871 0.2919279941773219 +169497174 -1.1196888598499313 0.2918635775161674 +169596208 -1.1202747724783841 0.2921488043944391 +169695242 -1.1204135822223094 0.2912483639949614 +169794276 -1.120601471978484 0.29240682921443584 +169893310 -1.1202292566002474 0.29249323065485916 +169992344 -1.1202726513263985 0.2919862745220331 +170091378 -1.1199946551728475 0.29226303710347934 +170190412 -1.1206549225485627 0.29285094247947424 +170289446 -1.1207223039000416 0.2928752189990259 +170388480 -1.1202028337324226 0.29274867427335366 +170487514 -1.1206621480400778 0.29276847122105865 +170586548 -1.1207064317746986 0.2934242108011977 +170685582 -1.1211123345208838 0.29291437764968015 +170784616 -1.121215224952147 0.2932905827363671 +170883650 -1.1211023376715044 0.2928196110955396 +170982684 -1.1214274964961901 0.2929170053842555 +171081718 -1.1216506032289113 0.2929835680989561 +171180752 -1.121742161985881 0.2932943851397643 +171279786 -1.1210272090595348 0.29358186577453843 +171378820 -1.1217522986567552 0.2937037484400883 +171477854 -1.121570025018921 0.2936345983056953 +171576888 -1.121924195179635 0.29365355618646627 +171675922 -1.1220172753380018 0.29356616163214494 +171774956 -1.1214613389300656 0.2937808328881465 +171873990 -1.1219005501938735 0.2939438579608581 +171973024 -1.1222442274131248 0.2935353575782235 +172072058 -1.121786257138475 0.29395042028461327 +172171092 -1.1226755034472695 0.2945284234290935 +172270126 -1.1223054280281242 0.2946047210334939 +172369160 -1.1225443537045645 0.2943115591181774 +172468194 -1.1221756711261277 0.2951591047915032 +172567228 -1.12253604528725 0.2944864374582865 +172666262 -1.1223239515700487 0.2952450750647709 +172765296 -1.122895560192586 0.2949873913811133 +172864330 -1.1224645710644252 0.2946133706389781 +172963364 -1.1224428063041652 0.29459780745254194 +173062398 -1.1227070729149489 0.29443214081823815 +173161432 -1.1230683577328435 0.2955291023467005 +173260466 -1.1232334841968903 0.29512955826232806 +173359500 -1.122559991011217 0.2955819289970757 +173458534 -1.1226645564044364 0.294993105265526 +173557568 -1.1231639972724015 0.2958852369417611 +173656602 -1.1237278389864664 0.2954259953630152 +173755636 -1.1231349298774147 0.29571746319704384 +173854670 -1.1237165433713259 0.29606299257314334 +173953704 -1.1239142134020799 0.2956965910932977 +174052738 -1.1237076475799777 0.295763402988605 +174151772 -1.123375134624516 0.29615727224135185 +174250806 -1.1246405839724 0.29694151663852125 +174349840 -1.1240458442150107 0.29678780593642734 +174448874 -1.1242881291955384 0.2966057700469284 +174547908 -1.1241975901380643 0.2975165727185441 +174646942 -1.1255699495940532 0.2963270678819262 +174745976 -1.1243870005999141 0.29662698957838973 +174845010 -1.1242735824026744 0.2963375723917603 +174944044 -1.1244891201115221 0.2968110490534526 +175043078 -1.1248056284829933 0.2968763589350464 +175142112 -1.1243957230950827 0.2965096027880551 +175241146 -1.125392842609262 0.29671362707962684 +175340180 -1.1253527730659265 0.29695174042383155 +175439214 -1.1249685807977508 0.29732517944577574 +175538248 -1.1252290274873082 0.2969778930532628 +175637282 -1.124545833463361 0.2972301217795531 +175736316 -1.125375187734289 0.2974181138311134 +175835350 -1.1249400942204095 0.2975492023996863 +175934384 -1.1251774542521167 0.2975792183739165 +176033418 -1.1254580788457427 0.2977785350980145 +176132452 -1.1254636715073765 0.29767584008097747 +176231486 -1.1256054051045186 0.2982016293351003 +176330520 -1.1260280669592961 0.2977514489726406 +176429554 -1.1262177583980664 0.298060440876072 +176528588 -1.1260067756421113 0.29794102200975575 +176627622 -1.1258403839368076 0.29767060552685576 +176726656 -1.1259515816120953 0.2983494495584792 +176825690 -1.1263596150776847 0.29867066166413575 +176924724 -1.1265479120572677 0.29845001081111855 +177023758 -1.125988827105286 0.2985193785863546 +177122792 -1.1263792078701074 0.2989777219000169 +177221826 -1.1266401297150543 0.29887864949281434 +177320860 -1.1269851085819267 0.299039498622123 +177419894 -1.1255433945624598 0.2987058542362215 +177518928 -1.1268628598491284 0.2987106195208795 +177617962 -1.1269144278329837 0.29907062108777205 +177716996 -1.1266731627563995 0.29905105707325563 +177816030 -1.1265826743429088 0.3002304408547334 +177915064 -1.12796985749757 0.30018880454785646 +178014098 -1.1272304193706233 0.29915349196124075 +178113132 -1.1270601588780407 0.3000245828291767 +178212166 -1.1274562582396803 0.29949798047772874 +178311200 -1.127031979331942 0.3003137707458184 +178410234 -1.1277699350827712 0.3000462525523944 +178509268 -1.127710509915176 0.30008501337556015 +178608302 -1.127154144271844 0.2999449438610998 +178707336 -1.1272996367507078 0.2997506677025509 +178806370 -1.1276615578869174 0.30009468210837803 +178905404 -1.1287175915134657 0.29981949882257025 +179004438 -1.1285926673390987 0.3000498584699417 +179103472 -1.1280862644395757 0.3003303803231283 +179202506 -1.1279899272860394 0.30121792056439645 +179301540 -1.1280432223731829 0.3001142120133645 +179400574 -1.1283003245382408 0.3006616719519928 +179499608 -1.1282335904210303 0.30113566550604687 +179598642 -1.1285623084799643 0.30036859358649837 +179697676 -1.1290861452142646 0.300871328448711 +179796710 -1.1286487020761231 0.30087900066926065 +179895744 -1.1293260639654503 0.3009552588886717 +179994778 -1.1289587330505118 0.3009989500361808 +180093812 -1.1282724756670508 0.3013458981486781 +180192846 -1.1289752592590891 0.301265271188679 +180291880 -1.129091193384986 0.30125743494468044 +180390914 -1.1287528113559693 0.30154860656159693 +180489948 -1.1289300514798855 0.30135181201293454 +180588982 -1.130045413986705 0.3020965552272707 +180688016 -1.1293124061884574 0.3017332635376238 +180787050 -1.1296132517682789 0.30217974602973363 +180886084 -1.1297261151885374 0.3022080706377377 +180985118 -1.1305670574656894 0.30148087785811767 +181084152 -1.1300703683323594 0.30195453603899497 +181183186 -1.1297999273971613 0.30161434292198613 +181282220 -1.129875838006314 0.30169058305897484 +181381254 -1.1299887872182137 0.3025397523121176 +181480288 -1.1300064436738142 0.30240178934052825 +181579322 -1.1307119989836705 0.3023240619297681 +181678356 -1.1305994315825094 0.3020269000186965 +181777390 -1.1298689676300795 0.30238361721897133 +181876424 -1.130857153104766 0.3022210996532582 +181975458 -1.1301034987258372 0.3027332751062421 +182074492 -1.1299252185049589 0.3030945370507902 +182173526 -1.1303106308634925 0.30310387492532326 +182272560 -1.1312240505964353 0.3029856688460085 +182371594 -1.1307870100496955 0.303351098330457 +182470628 -1.1304806780527574 0.3028754832132692 +182569662 -1.131335020817709 0.3031580340562041 +182668696 -1.1306041913190705 0.3033908781850423 +182767730 -1.1306096344593088 0.30369269163361035 +182866764 -1.1318998781620127 0.3038865894709288 +182965798 -1.1319582929532368 0.30314852635010986 +183064832 -1.1314341912370562 0.3036554127691506 +183163866 -1.13128930101824 0.3036818562548073 +183262900 -1.1305832227956372 0.3048837009260779 +183361934 -1.1321073166917817 0.3040330957659317 +183460968 -1.1318484989275486 0.3039332026792282 +183560002 -1.1317667494626449 0.3044397651241973 +183659036 -1.1320644673646334 0.3039620276158183 +183758070 -1.1320447028566591 0.30430304909298306 +183857104 -1.1320760446962548 0.3042948218941341 +183956138 -1.1320273308746585 0.30493694216739886 +184055172 -1.1323892054307598 0.304413014264575 +184154206 -1.1319757739008598 0.30499216094350895 +184253240 -1.1320245524177106 0.3047630087224368 +184352274 -1.1326202321615084 0.304818973011696 +184451308 -1.1320944641237858 0.3047276443264626 +184550342 -1.1319190284190228 0.3050155282861202 +184649376 -1.1322596035341925 0.3053480856527534 +184748410 -1.1325740502401407 0.30531338680906883 +184847444 -1.1323529103005245 0.3056294525272027 +184946478 -1.132460438038011 0.3051810630338924 +185045512 -1.1326196920968112 0.3051423418717387 +185144546 -1.1328699483513494 0.30541662651665463 +185243580 -1.1330941667554775 0.3060776409315163 +185342614 -1.1329497232567676 0.30580937143638504 +185441648 -1.1328657676394638 0.3052602483566314 +185540682 -1.13373615677779 0.3057536116063623 +185639716 -1.1328782266779693 0.30554705578015984 +185738750 -1.1327905235474052 0.30588090626827413 +185837784 -1.1333119667500962 0.30622226075159276 +185936818 -1.1334706464714586 0.30652191129507883 +186035852 -1.1339892501324134 0.3062877492699023 +186134886 -1.13422614309468 0.3061411215654382 +186233920 -1.1333324279757506 0.30570605933936 +186332954 -1.1330427984914833 0.3055111349756 +186431988 -1.1340950660462419 0.3069493101834258 +186531022 -1.133882781115175 0.30710927701071233 +186630056 -1.1342362638389667 0.3069456953764585 +186729090 -1.134367495301753 0.30706828820198223 +186828124 -1.133851809584551 0.30715537201809323 +186927158 -1.1344773472375325 0.307009560947124 +187026192 -1.1344957886762426 0.30625762826519787 +187125226 -1.134501969962531 0.30653274114497253 +187224260 -1.1347101153757992 0.3073644792805423 +187323294 -1.1349451390045286 0.30718860905548395 +187422328 -1.1341823102659843 0.30692252191316444 +187521362 -1.1358487567520346 0.30766701713906813 +187620396 -1.1350373745666464 0.30754566321965693 +187719430 -1.1353908513290845 0.30738476362684747 +187818464 -1.1352135033861497 0.30713835752246277 +187917498 -1.1353521328537506 0.3075761079361442 +188016532 -1.1352729113383144 0.30813834252733074 +188115566 -1.135049274106431 0.3078818715299157 +188214600 -1.135424003127081 0.30827797697696935 +188313634 -1.1349606219288009 0.3080846872191764 +188412668 -1.1357428851106948 0.3080745275002801 +188511702 -1.135675804721977 0.3082887738036144 +188610736 -1.1363026504346496 0.3075460587912144 +188709770 -1.1356973453442842 0.30790966441157086 +188808804 -1.1361950874871694 0.3084002997109658 +188907838 -1.1364041468003854 0.3086833769072771 +189006872 -1.1354780262901047 0.30827056447804674 +189105906 -1.1359377784836568 0.30766810837080305 +189204940 -1.1356371538716876 0.30864648117278554 +189303974 -1.1359523215955942 0.30861076716148494 +189403008 -1.1362192830525883 0.3086319891535062 +189502042 -1.1363578289171927 0.30916770509639824 +189601076 -1.1364981778832444 0.3088663839749032 +189700110 -1.1364900628128976 0.30858777394371173 +189799144 -1.1363322787141912 0.3092513581676197 +189898178 -1.1371796172858772 0.30880432073847347 +189997212 -1.136731003662868 0.3093256453026029 +190096246 -1.138181877136298 0.30940082317221806 +190195280 -1.1372890788914745 0.3095953741167748 +190294314 -1.1366649529262212 0.3095976529091902 +190393348 -1.136769730650257 0.309489145085741 +190492382 -1.1373678230119686 0.3105205218059645 +190591416 -1.1379512490433812 0.30924986890996803 +190690450 -1.1375785608351714 0.30978874731501954 +190789484 -1.137169569627655 0.3095884876194883 +190888518 -1.1381686594843574 0.31000166139432483 +190987552 -1.1369241723149277 0.30988295793727216 +191086586 -1.1376795998091234 0.3103244169352854 +191185620 -1.1376024480071076 0.30971482186319543 +191284654 -1.137634859884772 0.310314116351195 +191383688 -1.1376984186725385 0.31055059297651855 +191482722 -1.1380862930702156 0.31077478244428025 +191581756 -1.1381568894921632 0.310605658456901 +191680790 -1.1388136909279858 0.3102927118023206 +191779824 -1.138332693468008 0.31151366532310926 +191878858 -1.1383405155814823 0.3109931335933527 +191977892 -1.1384577075926599 0.31111512244971407 +192076926 -1.1382170605006405 0.31157842473802516 +192175960 -1.1385575301958824 0.3112343661517124 +192274994 -1.1386590171009385 0.3113750754288607 +192374028 -1.1383189553234183 0.3115625691996653 +192473062 -1.1378850988709976 0.3113633491045145 +192572096 -1.1384884026601483 0.31094740950453553 +192671130 -1.1385044223513399 0.31061984561289463 +192770164 -1.1385938196426946 0.3126963841327324 +192869198 -1.1399991204853053 0.31072627970954836 +192968232 -1.1391080680430372 0.31177332762602533 +193067266 -1.1383346410348207 0.31186869995989724 +193166300 -1.1393851340569008 0.3117322401845929 +193265334 -1.138885689245097 0.3112972124194288 +193364368 -1.1390871457677934 0.3118865093530165 +193463402 -1.1394980865716318 0.3122354054250928 +193562436 -1.1391978895650352 0.3128537887352271 +193661470 -1.1395137917144735 0.3122406218796766 +193760504 -1.1390820536602193 0.31203592314263756 +193859538 -1.1397171600092573 0.3133746487478381 +193958572 -1.1401654441828997 0.31192944874694034 +194057606 -1.139786452174782 0.31261295761953417 +194156640 -1.139886205359899 0.3125518527030153 +194255674 -1.140343769201029 0.3121846608710861 +194354708 -1.1402673881831602 0.3124274945108015 +194453742 -1.1406217472109246 0.312530535087353 +194552776 -1.1409837113746715 0.31295778615303266 +194651810 -1.1406863187840646 0.313541191853737 +194750844 -1.1404754481449602 0.31332613125403636 +194849878 -1.1408181085674574 0.31298503398638433 +194948912 -1.1408659663435048 0.3125024489596112 +195047946 -1.141463866977237 0.31305791021304563 +195146980 -1.1405860532380432 0.31412985553128153 +195246014 -1.1408377580190026 0.3133618286579525 +195345048 -1.1405544601844326 0.31327036232275507 +195444082 -1.141850350891299 0.3130869334152068 +195543116 -1.1405216114496775 0.31322591227116175 +195642150 -1.1405784637949312 0.3130501610007566 +195741184 -1.1413412847431312 0.31309393046890366 +195840218 -1.1407623994863498 0.3135361554410034 +195939252 -1.1407638747546298 0.3139540364368516 +196038286 -1.1416503711536177 0.3131130125190026 +196137320 -1.1412787695692475 0.31353526624188816 +196236354 -1.1406190362926962 0.31331303876881916 +196335388 -1.1417527595436088 0.31251823915363963 +196434422 -1.1409876485952801 0.31435024162022557 +196533456 -1.1413344606581073 0.31376136756837936 +196632490 -1.1422426438127435 0.3132084903637231 +196731524 -1.1418342937431707 0.3146226215339604 +196830558 -1.1419857147682944 0.3139895419943126 +196929592 -1.1415208550806581 0.31474773531394684 +197028626 -1.142144859163866 0.3149982780521657 +197127660 -1.1421420700225366 0.31489602195490457 +197226694 -1.1420449852690977 0.3144613495399211 +197325728 -1.1424157671692992 0.31448846321301516 +197424762 -1.1429544435439583 0.3147737397689241 +197523796 -1.1423216479590275 0.31442470918919835 +197622830 -1.1431238757011346 0.3146888460251564 +197721864 -1.1429964596079685 0.31534702306078677 +197820898 -1.1429585373338007 0.3147502428090518 +197919932 -1.1430019490037946 0.3123716657298953 +198018966 -1.1431135666023196 0.3151554927847907 +198118000 -1.1426234427405872 0.31536177000840726 +198217034 -1.143170326212544 0.31497479847657694 +198316068 -1.1426898448266363 0.31492610924236664 +198415102 -1.143831732213643 0.3155470635177979 +198514136 -1.1433148538829323 0.3153722679490706 +198613170 -1.1433526317135656 0.31565884095417707 +198712204 -1.1430751548690086 0.3152742535611089 +198811238 -1.1437215013130642 0.31551568355900234 +198910272 -1.1436003987998464 0.3160178989204372 +199009306 -1.143159538733492 0.3153054493534358 +199108340 -1.1431121030541225 0.31536391732751 +199207374 -1.1438411651167648 0.31495795766680007 +199306408 -1.1427555402434721 0.3168665036282917 +199405442 -1.144151154253777 0.31657034081407365 +199504476 -1.1448735790482554 0.3164768709340913 +199603510 -1.1441517603715132 0.31654191191795494 +199702544 -1.1440924708592775 0.3159138849666406 +199801578 -1.1442956944945173 0.3163037471860155 +199900612 -1.1441406325898404 0.316568541070503 +199999646 -1.1443876877786179 0.3158408069462067 diff --git a/test/test_formatSweepFrequency.py b/test/test_formatSweepFrequency.py index a3b52315..5db3d595 100644 --- a/test/test_formatSweepFrequency.py +++ b/test/test_formatSweepFrequency.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/test_formatting.py b/test/test_formatting.py index 86997b2b..8f263f6e 100644 --- a/test/test_formatting.py +++ b/test/test_formatting.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,6 +52,15 @@ def test_format_frequency(self): self.assertEqual(fmt.format_frequency_short(0), '0.000Hz') self.assertEqual(fmt.format_frequency_short(-1), '-1.000Hz') + self.assertEqual(fmt.format_frequency_chart(1), '1.000') + self.assertEqual(fmt.format_frequency_chart(12), '12.00') + self.assertEqual(fmt.format_frequency_chart(123), '123.0') + self.assertEqual(fmt.format_frequency_chart(1234), '1.234k') + self.assertEqual(fmt.format_frequency_chart(1234567), '1.235M') + self.assertEqual(fmt.format_frequency_chart(1234567890), '1.235G') + self.assertEqual(fmt.format_frequency_chart(0), '0.000') + self.assertEqual(fmt.format_frequency_chart(-1), '-1.000') + def test_format_frequency_inputs(self): self.assertEqual(fmt.format_frequency_inputs(1), '1Hz') self.assertEqual(fmt.format_frequency_inputs(12), '12Hz') diff --git a/test/test_parseFrequency.py b/test/test_parseFrequency.py index fe74c8f1..e2736340 100644 --- a/test/test_parseFrequency.py +++ b/test/test_parseFrequency.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/test_rftools.py b/test/test_rftools.py index 6890727a..f2ece9ba 100644 --- a/test/test_rftools.py +++ b/test/test_rftools.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/test_sitools.py b/test/test_sitools.py index a0ca4680..f968a19a 100644 --- a/test/test_sitools.py +++ b/test/test_sitools.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,11 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import unittest +from math import inf +from decimal import Decimal # Needed for test_representation() # Import targets to be tested from NanoVNASaver.SITools import Format, Value -from decimal import Decimal -from math import inf F_DEFAULT = Format() diff --git a/test/test_sweep.py b/test/test_sweep.py index ed1762f2..4f1539a9 100644 --- a/test/test_sweep.py +++ b/test/test_sweep.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/test_touchstone.py b/test/test_touchstone.py index 0dc0ea5d..d3e9e928 100644 --- a/test/test_touchstone.py +++ b/test/test_touchstone.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -63,18 +63,18 @@ def test_load(self): ts = Touchstone("./test/data/valid.s1p") ts.load() self.assertEqual(str(ts.opts), "# HZ S RI R 50") - self.assertEqual(len(ts.s11data), 1010) - self.assertEqual(len(ts.s21data), 0) + self.assertEqual(len(ts.s11), 1010) + self.assertEqual(len(ts.s21), 0) self.assertEqual(ts.r, 50) ts = Touchstone("./test/data/valid.s2p") ts.load() ts.gen_interpolation() self.assertEqual(str(ts.opts), "# HZ S RI R 50") - self.assertEqual(len(ts.s11data), 1020) - self.assertEqual(len(ts.s21data), 1020) - self.assertEqual(len(ts.s12data), 1020) - self.assertEqual(len(ts.s22data), 1020) + self.assertEqual(len(ts.s11), 1020) + self.assertEqual(len(ts.s21), 1020) + self.assertEqual(len(ts.s12), 1020) + self.assertEqual(len(ts.s22), 1020) self.assertIn("! Vector Network Analyzer VNA R2", ts.comments) self.assertEqual(ts.min_freq(), 500000) self.assertEqual(ts.max_freq(), 900000000) @@ -107,6 +107,14 @@ def test_load(self): ts.load() self.assertRegex(cm.output[0], "No such file or directory") + def test_swap(self): + ts = Touchstone("./test/data/valid.s2p") + ts.load() + s11, s21, s12, s22 = ts.sdata + ts.swap() + s11_, s21_, s12_, s22_ = ts.sdata + self.assertEqual([s11_, s21_, s12_, s22_] ,[s22, s12, s21, s11]) + def test_db_conversation(self): ts_db = Touchstone("./test/data/attenuator-0643_DB.s2p") ts_db.load() @@ -114,12 +122,12 @@ def test_db_conversation(self): ts_ri.load() ts_ma = Touchstone("./test/data/attenuator-0643_MA.s2p") ts_ma.load() - self.assertEqual(len(ts_db.s11data), len(ts_ri.s11data)) - for dps_db, dps_ri in zip(ts_db.s11data, ts_ri.s11data): + self.assertEqual(len(ts_db.s11), len(ts_ri.s11)) + for dps_db, dps_ri in zip(ts_db.s11, ts_ri.s11): self.assertAlmostEqual(dps_db.z, dps_ri.z, places=5) - self.assertEqual(len(ts_db.s11data), len(ts_ma.s11data)) - for dps_db, dps_ma in zip(ts_db.s11data, ts_ma.s11data): + self.assertEqual(len(ts_db.s11), len(ts_ma.s11)) + for dps_db, dps_ma in zip(ts_db.s11, ts_ma.s11): self.assertAlmostEqual(dps_db.z, dps_ma.z, places=5) def test_load_scikit(self): @@ -137,21 +145,21 @@ def test_load_scikit(self): 'WARNING:NanoVNASaver.Touchstone:Reordering data', ]) self.assertEqual(str(ts.opts), "# HZ S RI R 50") - self.assertEqual(len(ts.s11data), 101) + self.assertEqual(len(ts.s11), 101) self.assertIn("!freq ReS11 ImS11 ReS21 ImS21 ReS12 ImS12 ReS22 ImS22", ts.comments) def test_setter(self): ts = Touchstone("") dp_list = [Datapoint(1, 0.0, 0.0), Datapoint(3, 1.0, 1.0)] - ts.s11data = dp_list[:] - ts.s21data = dp_list[:] - ts.s12data = dp_list[:] - ts.s22data = dp_list[:] - self.assertEqual(ts.s11data, dp_list) - self.assertEqual(ts.s21data, dp_list) - self.assertEqual(ts.s12data, dp_list) - self.assertEqual(ts.s22data, dp_list) + ts.s11 = dp_list[:] + ts.s21 = dp_list[:] + ts.s12 = dp_list[:] + ts.s22 = dp_list[:] + self.assertEqual(ts.s11, dp_list) + self.assertEqual(ts.s21, dp_list) + self.assertEqual(ts.s12, dp_list) + self.assertEqual(ts.s22, dp_list) self.assertEqual(ts.min_freq(), 1) self.assertEqual(ts.max_freq(), 3) ts.gen_interpolation() @@ -182,6 +190,6 @@ def test_save(self): ts.filename = "" self.assertRaises(FileNotFoundError, ts.save) - ts.s11data[0] = Datapoint(100, 0.1, 0.1) + ts.s11[0] = Datapoint(100, 0.1, 0.1) self.assertRaisesRegex( LookupError, "Frequencies of sdata not correlated", ts.saves, 4) diff --git a/test/test_version.py b/test/test_version.py index 17312ab1..ac41c171 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -2,7 +2,7 @@ # # A python program to view and export Touchstone data from a NanoVNA # Copyright (C) 2019, 2020 Rune B. Broberg -# Copyright (C) 2020 NanoVNA-Saver Authors +# Copyright (C) 2020,2021 NanoVNA-Saver Authors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by