diff --git a/ACT_python3.yml b/ACT_python3.yml index 0608e04..b54d77c 100644 --- a/ACT_python3.yml +++ b/ACT_python3.yml @@ -3,84 +3,176 @@ channels: - conda-forge - defaults dependencies: - - astropy=4.0.2=py36h779f372_0 - - certifi=2021.5.30=py36ha15d459_0 - - cycler=0.10.0=py_2 - - dill=0.3.4=pyhd8ed1ab_0 - - freetype=2.10.4=h546665d_1 - - imglyb=2.0.0=pyh8a188c0_0 - - intel-openmp=2021.3.0=h57928b3_3372 - - jbig=2.1=h8d14728_2003 - - jgo=1.0.3=pyhd8ed1ab_0 - - jpeg=9d=h8ffe710_0 - - jpype1=1.3.0=py36he95197e_0 - - kiwisolver=1.3.1=py36he95197e_1 - - lerc=2.2.1=h0e60522_0 - - libblas=3.9.0=11_win64_mkl - - libcblas=3.9.0=11_win64_mkl - - libdeflate=1.7=h8ffe710_5 - - liblapack=3.9.0=11_win64_mkl - - libpng=1.6.37=h1d00b33_2 - - libtiff=4.3.0=h0c97f57_1 - - libzlib=1.2.11=h8ffe710_1013 - - lz4-c=1.9.3=h8ffe710_1 + - aom=3.5.0=h63175ca_0 + - astropy=5.2.2=py38h634f036_0 + - blosc=1.21.4=hdccc3a2_0 + - brotli=1.0.9=hcfcfb64_8 + - brotli-bin=1.0.9=hcfcfb64_8 + - bzip2=1.0.8=h8ffe710_4 + - c-blosc2=2.9.2=h183a6f4_0 + - ca-certificates=2023.5.7=h56e8100_0 + - cairo=1.16.0=hdecc03f_1016 + - certifi=2023.5.7=pyhd8ed1ab_0 + - cfitsio=4.2.0=h9ebe7e4_0 + - charls=2.4.2=h1537add_0 + - charset-normalizer=3.1.0=pyhd8ed1ab_0 + - click=8.1.3=win_pyhd8ed1ab_2 + - cloudpickle=2.2.1=pyhd8ed1ab_0 + - colorama=0.4.6=pyhd8ed1ab_0 + - contourpy=1.0.7=py38hb1fd069_0 + - cycler=0.11.0=pyhd8ed1ab_0 + - cytoolz=0.12.0=py38h91455d4_1 + - dask-core=2023.5.0=pyhd8ed1ab_0 + - dav1d=1.2.1=hcfcfb64_0 + - dill=0.3.6=pyhd8ed1ab_1 + - double-conversion=3.2.0=h63175ca_1 + - et_xmlfile=1.1.0=pyhd8ed1ab_0 + - expat=2.5.0=h63175ca_1 + - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 + - font-ttf-inconsolata=3.000=h77eed37_0 + - font-ttf-source-code-pro=2.038=h77eed37_0 + - font-ttf-ubuntu=0.83=hab24e00_0 + - fontconfig=2.14.2=hbde0cde_0 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - fonttools=4.40.0=py38h91455d4_0 + - freetype=2.12.1=h546665d_1 + - fsspec=2023.6.0=pyh1a96a4e_0 + - gettext=0.21.1=h5728263_0 + - giflib=5.2.1=h64bf75a_3 + - graphite2=1.3.13=1000 + - harfbuzz=7.3.0=h196d34a_0 + - icu=72.1=h63175ca_0 + - idna=3.4=pyhd8ed1ab_0 + - imagecodecs=2023.1.23=py38h29c013e_2 + - imageio=2.31.1=pyh24c5eb1_0 + - imglyb=2.1.0=pyha21a80b_0 + - importlib-metadata=6.6.0=pyha770c72_0 + - importlib-resources=5.12.0=pyhd8ed1ab_0 + - importlib_metadata=6.6.0=hd8ed1ab_0 + - importlib_resources=5.12.0=pyhd8ed1ab_0 + - intel-openmp=2023.1.0=h57928b3_46319 + - jgo=1.0.5=pyhd8ed1ab_0 + - joblib=1.2.0=pyhd8ed1ab_0 + - jpype1=1.4.1=py38hb1fd069_1 + - jxrlib=1.1=h8ffe710_2 + - kiwisolver=1.4.4=py38hb1fd069_1 + - krb5=1.20.1=heb0366b_0 + - labeling=0.1.12=pyhd8ed1ab_1 + - lcms2=2.15=h3e3b177_1 + - lerc=4.0.0=h63175ca_0 + - libaec=1.0.6=h63175ca_1 + - libavif=0.11.1=ha7b97ba_2 + - libblas=3.9.0=17_win64_mkl + - libbrotlicommon=1.0.9=hcfcfb64_8 + - libbrotlidec=1.0.9=hcfcfb64_8 + - libbrotlienc=1.0.9=hcfcfb64_8 + - libcblas=3.9.0=17_win64_mkl + - libclang=16.0.5=default_heb8d277_0 + - libclang13=16.0.5=default_hc80b9e7_0 + - libcurl=8.1.2=h68f0423_0 + - libdeflate=1.18=hcfcfb64_0 + - libexpat=2.5.0=h63175ca_1 + - libffi=3.4.2=h8ffe710_5 + - libglib=2.76.3=he8f3873_0 + - libhwloc=2.9.1=nocuda_h15da153_6 + - libiconv=1.17=h8ffe710_0 + - libjpeg-turbo=2.1.5.1=hcfcfb64_0 + - liblapack=3.9.0=17_win64_mkl + - libpng=1.6.39=h19919ed_0 + - libsqlite=3.42.0=hcfcfb64_0 + - libssh2=1.11.0=h7dfc565_0 + - libtiff=4.5.0=h6c8260b_6 + - libwebp-base=1.3.0=hcfcfb64_0 + - libxcb=1.15=hcd874cb_0 + - libxml2=2.11.4=hc3477c8_0 + - libxslt=1.1.37=h6070c61_1 + - libzlib=1.2.13=hcfcfb64_5 + - libzopfli=1.0.3=h0e60522_0 + - llvmlite=0.40.0=py38h19421c1_0 + - locket=1.0.0=pyhd8ed1ab_0 + - lz4-c=1.9.4=hcfcfb64_0 - m2w64-gcc-libgfortran=5.3.0=6 - m2w64-gcc-libs=5.3.0=7 - m2w64-gcc-libs-core=5.3.0=7 - m2w64-gmp=6.1.0=2 - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 - - matplotlib-base=3.3.4=py36h1abdf75_0 + - matplotlib-base=3.7.1=py38h528a6c7_0 - maven=3.6.3=h57928b3_0 - - mkl=2021.3.0=hb70f87d_564 + - mkl=2022.1.0=h6a75c08_874 - msys2-conda-epoch=20160418=1 - - multiprocess=0.70.12.2=py36h68aa20f_0 - - numpy=1.19.5=py36h4b40d73_2 - - olefile=0.46=pyh9f0ad1d_1 - - openjdk=8.0.302=h57928b3_0 - - pathos=0.2.7=pyhd3deb0d_0 - - pillow=8.0.1=py36ha0524ae_0 - - pip=21.0.1=py36haa95532_0 - - pox=0.3.0=pyhd8ed1ab_0 - - ppft=1.6.6.4=pyhd8ed1ab_0 - - psutil=5.8.0=py36h68aa20f_1 - - pyimagej=1.0.2=py36ha15d459_0 - - pyparsing=2.4.7=pyh9f0ad1d_0 - - python=3.6.13=h3758d61_0 + - multiprocess=0.70.14=py38h91455d4_3 + - munkres=1.1.4=pyh9f0ad1d_0 + - networkx=3.1=pyhd8ed1ab_0 + - numba=0.57.0=py38hb182ae8_1 + - numpy=1.24.3=py38h1d91fd2_0 + - openjdk=8.0.332=h57928b3_0 + - openjpeg=2.5.0=ha2aaf27_2 + - openpyxl=3.1.2=py38h91455d4_0 + - openssl=3.1.1=hcfcfb64_1 + - packaging=23.1=pyhd8ed1ab_0 + - pandas=1.5.3=py38h5846ac1_1 + - partd=1.4.0=pyhd8ed1ab_0 + - pathos=0.3.0=pyhd8ed1ab_0 + - pcre2=10.40=h17e33f8_0 + - pillow=9.5.0=py38ha7eb54a_1 + - pip=23.1.2=pyhd8ed1ab_0 + - pixman=0.40.0=h8ffe710_0 + - platformdirs=3.5.3=pyhd8ed1ab_0 + - pooch=1.7.0=pyha770c72_3 + - pox=0.3.2=pyhd8ed1ab_0 + - ppft=1.7.6.6=pyhd8ed1ab_0 + - psutil=5.9.5=py38h91455d4_0 + - pthread-stubs=0.4=hcd874cb_1001 + - pthreads-win32=2.9.1=hfa6e2cd_3 + - pyerfa=2.0.0.3=py38hbaf524b_0 + - pyimagej=1.0.2=py38haa244fe_1 + - pyparsing=3.0.9=pyhd8ed1ab_0 + - pyqtgraph=0.13.3=pyhd8ed1ab_0 + - pyside6=6.5.1=py38hac9af9c_0 + - pysocks=1.7.1=pyh0701188_6 + - python=3.8.16=h4de0772_1_cpython - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python_abi=3.6=2_cp36m - - pytz=2021.3=pyhd8ed1ab_0 - - scipy=1.5.2=py36h7ff6e69_2 - - scyjava=1.3.1=pyhd8ed1ab_0 - - setuptools=58.0.4=py36haa95532_0 + - python_abi=3.8=3_cp38 + - pytz=2023.3=pyhd8ed1ab_0 + - pywavelets=1.4.1=py38hbaf524b_0 + - pyyaml=6.0=py38h91455d4_5 + - qt6-main=6.5.1=hf0c6fff_1 + - requests=2.31.0=pyhd8ed1ab_0 + - scikit-image=0.19.3=py38h5846ac1_2 + - scikit-learn=1.2.2=py38h763eb3e_2 + - scipy=1.10.1=py38h1aea9ed_3 + - scyjava=1.9.0=pyhd8ed1ab_0 + - setuptools=67.7.2=pyhd8ed1ab_0 - six=1.16.0=pyh6c4a22f_0 - - sqlite=3.36.0=h2bbff1b_0 - - tbb=2021.3.0=h2d74725_0 - - tk=8.6.11=h8ffe710_1 - - tornado=6.1=py36h68aa20f_1 - - typing_extensions=3.10.0.2=pyha770c72_0 - - vc=14.2=h21ff451_1 - - vs2015_runtime=14.27.29016=h5e58377_2 - - wheel=0.37.0=pyhd3eb1b0_1 - - wincertstore=0.2=py36h7fe50ca_0 - - xarray=0.18.2=pyhd8ed1ab_0 - - xz=5.2.5=h62dcd97_1 - - zlib=1.2.11=h8ffe710_1013 - - zstd=1.5.0=h6255e5f_0 + - skan=0.11.0=pyhd8ed1ab_0 + - snappy=1.1.10=hfb803bf_0 + - tbb=2021.9.0=h91493d7_0 + - threadpoolctl=3.1.0=pyh8a188c0_0 + - tifffile=2023.4.12=pyhd8ed1ab_0 + - tk=8.6.12=h8ffe710_0 + - toolz=0.12.0=pyhd8ed1ab_0 + - tqdm=4.65.0=pyhd8ed1ab_1 + - typing-extensions=4.6.3=hd8ed1ab_0 + - typing_extensions=4.6.3=pyha770c72_0 + - ucrt=10.0.22621.0=h57928b3_0 + - unicodedata2=15.0.0=py38h91455d4_0 + - urllib3=2.0.3=pyhd8ed1ab_0 + - vc=14.3=hb25d44b_16 + - vc14_runtime=14.34.31931=h5081d32_16 + - vs2015_runtime=14.34.31931=hed1258a_16 + - wheel=0.40.0=pyhd8ed1ab_0 + - win_inet_pton=1.1.0=pyhd8ed1ab_6 + - xarray=2023.1.0=pyhd8ed1ab_0 + - xorg-libxau=1.0.11=hcd874cb_0 + - xorg-libxdmcp=1.1.3=hcd874cb_0 + - xz=5.2.6=h8d14728_0 + - yaml=0.2.5=h8ffe710_2 + - zfp=1.0.0=h63175ca_3 + - zipp=3.15.0=pyhd8ed1ab_0 + - zlib=1.2.13=hcfcfb64_5 + - zlib-ng=2.0.7=hcfcfb64_0 + - zstd=1.5.2=h12be248_6 - pip: - - decorator==4.4.2 - - imagecodecs==2020.5.30 - - imageio==2.9.0 - - joblib==1.1.0 - - networkx==2.5.1 - - opencv-contrib-python==4.1.2.30 - - pandas==0.25.3 - - pyside6==6.0.2 - - pywavelets==1.1.1 - - scikit-image==0.17.2 - - scikit-learn==0.24.2 - - shiboken6==6.0.2 - - sklearn==0.0 - - threadpoolctl==3.1.0 - - tifffile==2019.7.26.2 - - tqdm==4.60.0 + - opencv-contrib-python==4.7.0.72 prefix: D:\Anaconda3\envs\ACT_python3 diff --git a/README.md b/README.md index 924b3b2..4a4993b 100755 --- a/README.md +++ b/README.md @@ -37,28 +37,28 @@ Requirements Full list please see ACT_python3.yml. -- Python 3 (tested with Python 3.6-3.9) - - os - - sys - - re - - datatime - - [opencv - 4.1.2.30](https://pypi.org/project/opencv-contrib-python/) - - [tqdm - 4.60.0](https://pypi.org/project/tqdm/) - - [astropy - 4.0.2](https://www.astropy.org/) - - [PIL - 8.0.1](https://pypi.org/project/Pillow/) - - [scipy - 1.5.2](https://www.scipy.org/) - - [numpy](https://numpy.org/) - - [math](https://docs.python.org/3/library/math.html) - - [tifffile](https://pypi.org/project/tifffile/) - - [pandas - 1.4.1](https://pandas.pydata.org/) - - [scikit-image - 0.17.2](https://scikit-image.org/) - - [scikit-learn - 0.24.2](https://scikit-learn.org/) - - [PySide 6 - 6.0.2](https://pypi.org/project/PySide6/) - - [pyqtgraph](https://github.com/pyqtgraph/pyqtgraph)(Included locally) - - [pathos](https://pypi.org/project/pathos/) - - [psutil](https://pypi.org/project/psutil/) - - [pyimagej](https://github.com/imagej/pyimagej) (install with conda-forge) - - Openjdk - 8 +- Python 3 (configured with Python 3.8.16) + - [astropy - 5.2.2](https://www.astropy.org/) + - [imageio - 2.31.1](https://anaconda.org/conda-forge/imageio) + - [imglyb - 2.1.0](https://anaconda.org/conda-forge/imglyb) + - [maven - 3.6.3](https://anaconda.org/conda-forge/maven) + - [numpy - 1.24.3](https://numpy.org/) + - [opencv-contrib-python - 4.7.0.72](https://pypi.org/project/opencv-contrib-python/) + - [openjdk - 8.0.332](https://anaconda.org/conda-forge/openjdk) + - [pandas - 1.4.1](https://pandas.pydata.org/) + - [pathos](https://anaconda.org/conda-forge/pathos) + - [pillow - 9.5.0](https://anaconda.org/conda-forge/pillow) + - [psutil](https://anaconda.org/conda-forge/psutil) + - [pyimagej - 1.0.2](https://github.com/imagej/pyimagej) (must install with conda-forge) + - [pyqtgraph - 0.13.3](https://github.com/pyqtgraph/pyqtgraph) + - [PySide 6 - 6.5.1](https://anaconda.org/conda-forge/pyside6) + - [scikit-image - 0.19.3](https://scikit-image.org/) + - [scikit-learn - 1.2.2](https://scikit-learn.org/) + - [scipy - 1.10.1](https://www.scipy.org/) + - [scyjava - 1.9.0](https://anaconda.org/conda-forge/scyjava) + - [skan - 0.11.0](https://github.com/jni/skan) + - [tifffile - 2023.4.12](https://anaconda.org/conda-forge/tifffile) + - [tqdm - 4.65.0](https://anaconda.org/conda-forge/tqdm) - [Fiji(is just imagej)](https://imagej.net/Fiji) - [ComDet](https://github.com/ekatrukha/ComDet) diff --git a/UI_form/ACT.pyproject.user b/UI_form/ACT.pyproject.user index 5257fcb..25cd68e 100644 --- a/UI_form/ACT.pyproject.user +++ b/UI_form/ACT.pyproject.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/logics.py b/logics.py index e6a0648..4d40a3c 100644 --- a/logics.py +++ b/logics.py @@ -1,7 +1,16 @@ +""" +Beta-2 release for Revision of SR length measurement +v2.1.1-beta-2 +update date: 20230716 +""" import os import re +import sys +import traceback from datetime import datetime import cv2 +import warnings +warnings.simplefilter(action='ignore', category=FutureWarning) import pandas as pd from tqdm import tqdm import numpy as np @@ -9,8 +18,9 @@ import tifffile as tiff import imagej from skimage import io -from skimage.morphology import disk, erosion, dilation, white_tophat, reconstruction, skeletonize_3d, closing +from skimage.morphology import disk, erosion, dilation, white_tophat, reconstruction, skeletonize, closing from skimage.measure import label, regionprops_table +import skan from sklearn.neighbors import NearestNeighbors from sklearn.cluster import DBSCAN from astropy.convolution import RickerWavelet2DKernel @@ -420,14 +430,14 @@ def __init__(self, data_path): self.path_result_main = data_path + '_results' if os.path.isdir(self.path_result_main) != 1: os.mkdir(self.path_result_main) - self.path_result_raw = os.path.join(self.path_result_main, 'raw') + self.path_result_raw = os.path.join(self.path_result_main, 'Samples') if os.path.isdir(self.path_result_raw) != 1: os.mkdir(self.path_result_raw) self.gather_project_info() def gather_project_info(self): - samples = [name for name in os.listdir(self.path_data_main) if not name.startswith('.') and name != 'results'] + samples = [name for name in os.listdir(self.path_data_main) if os.path.isdir(os.path.join(self.path_data_main, name))] if 'Ionomycin' in samples: self.samples = [self.path_data_main] else: @@ -482,6 +492,7 @@ def img_alignment(Ionomycin, Sample, Blank): centre_ = (Ionomycin.shape[0]/2, Ionomycin.shape[1]/2) # 2d fourier transform of averaged images + FIonomycin = np.fft.fft2(Ionomycin) FSample = np.fft.fft2(Sample) FBlank = np.fft.fft2(Blank) @@ -503,13 +514,11 @@ def img_alignment(Ionomycin, Sample, Blank): IS_y_offset = j-centre_[0] IB_x_offset = g-centre_[1] IB_y_offset = k-centre_[0] - # Correction - MIS = np.float64([[1, 0, IS_y_offset], [0, 1, IS_x_offset]]) + MIS = np.float64([[1, 0, *IS_y_offset], [0, 1, *IS_x_offset]]) Corrected_Sample = cv2.warpAffine(Sample, MIS, Ionomycin.shape) - MIB = np.float64([[1, 0, IB_y_offset], [0, 1, IB_x_offset]]) + MIB = np.float64([[1, 0, *IB_y_offset], [0, 1, *IB_x_offset]]) Corrected_Blank = cv2.warpAffine(Blank, MIB, Ionomycin.shape) - return Corrected_Sample, Corrected_Blank @@ -573,7 +582,7 @@ def influx_qc(field, peaks, influx_df): """ influx_df['Influx'] = [100 if i >= 100 and i <= 110 else i for i in influx_df['Influx']] influx_df['Influx'] = [0 if i <= 0 and i >= -10 else i for i in influx_df['Influx']] - influx_df['Influx'] = ['error' if ms.isnan(np.float(i)) or i < -10 or i > 110 else i for i in influx_df['Influx']] + influx_df['Influx'] = ['error' if ms.isnan(float(i)) or i < -10 or i > 110 else i for i in influx_df['Influx']] ### Generate a dataframe which contains the result of current FoV ### field_result = pd.concat([ @@ -609,13 +618,11 @@ def pass_log(text): def process_img(img_index, workload, threshold): - sample = workload[img_index] sample_summary = pd.DataFrame() # report which sample is running to log window #pass_log('Running sample: ' + sample) - ionomycin_path = os.path.join(sample, 'Ionomycin') sample_path = os.path.join(sample, 'Sample') blank_path = os.path.join(sample, 'Blank') @@ -626,7 +633,7 @@ def process_img(img_index, workload, threshold): ### Obtain filenames for fields of view ### field_names = extract_filename(ionomycin_path) - for field in field_names: + for field in tqdm(field_names, desc=f'Processing FoVs in {sample}'): ### Average tiff files ### ionomycin_mean = average_frame(os.path.join(ionomycin_path, field)) sample_mean = average_frame(os.path.join(sample_path, field)) @@ -637,8 +644,9 @@ def process_img(img_index, workload, threshold): ### Locate the peaks on the ionomycin image ### peaks = peak_locating(ionomycin_mean, threshold) - + if len(peaks) == 0: + #pass_log('Field ' + field + ' of sample ' + sample +' ignored due to no liposome located in this FoV.') field_summary = pd.DataFrame({ "FoV": [field], @@ -649,6 +657,7 @@ def process_img(img_index, workload, threshold): }) sample_summary = pd.concat([sample_summary, field_summary]) else: + ### Calculate the intensities of peaks with certain radius (in pixel) ### ionomycin_intensity = intensities(ionomycin_mean, peaks) sample_intensity = intensities(sample_aligned, peaks) @@ -661,6 +670,7 @@ def process_img(img_index, workload, threshold): field_result.to_csv(os.path.join(sample.replace(self.path_data_main, self.path_result_raw), field+".csv")) sample_summary = pd.concat([sample_summary, field_summary]) + sample_summary.to_csv(sample.replace(self.path_data_main, self.path_result_raw) + ".csv") @@ -796,9 +806,6 @@ def gather_project_info(self): self.error = 'The images are not stacked. Please check.' return 0 - - - return 1 @@ -1142,30 +1149,71 @@ def _cluster_DBSCAN(self, field_name): cluster_img = cluster_df.to_numpy() # convert pivot table to numpy array if self.parameters['length_calculation'] == True: # Run length calculation - num_clus = np.max(cluster_img) - length_list = [] - for i in range(1, num_clus+1): - coor = np.where(cluster_img == i) - coor0 = coor[0]-np.min(coor[0]) - coor1 = coor[1]-np.min(coor[1]) - clus_area = np.zeros((np.max(coor0)+1, np.max(coor1)+1)) - clus_area[(coor0, coor1)] = 1 - clus_area_close = closing(clus_area) - clus_skele = skeletonize_3d(clus_area_close) - xy = np.asarray(np.where(clus_skele)).T + + def total_length_calculation_by_NN(skeleton, length_list_main, length_list_total): + # use this method when the main branch path cannot be built + xy = np.asarray(np.where(skeleton)).T length = 0 nbrs = NearestNeighbors(radius = 1.5, algorithm='auto').fit(xy) rng = nbrs.radius_neighbors(xy) for j in rng[0]: length += sum(j) length = length/2 + 1 - length_list.append(length) + length_list_main.append(length) + length_list_total.append(length) + return length + + length_list_main = [] + length_list_total = [] + for i in tqdm(range(1, n_clusters+1), desc=f'Calculating cluster length for {field_name}'): + coor = np.where(cluster_img == i) + coor0 = coor[0]-np.min(coor[0]) + coor1 = coor[1]-np.min(coor[1]) + cluster_canvas = np.zeros((np.max(coor0)+1, np.max(coor1)+1)) + cluster_canvas[(coor0, coor1)] = 1 + #closed_cluster = closing(cluster_canvas) + cluster_skeleton = skeletonize(cluster_canvas) + #cluster_skeleton = skeletonize(closed_cluster) + try: + skeleton_summary = skan.summarize(skan.Skeleton(cluster_skeleton), find_main_branch=True) + except ValueError as ex: + print(ex) + print(f'Failed to build skeleton path for cluster No.{i}, calculating total length as main branch length.') + total_length_calculation_by_NN(cluster_skeleton, length_list_main, length_list_total) + except Exception as ex: + ex_type, ex_value, ex_traceback = sys.exc_info() + + # Extract unformatter stack traces as tuples + trace_back = traceback.extract_tb(ex_traceback) + + # Format stacktrace + stack_trace = list() + + for trace in trace_back: + stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3])) + + print("Exception type : %s " % ex_type.__name__) + print("Exception message : %s" %ex_value) + print("Stack trace : %s" %stack_trace) + print(f"Exception at cluster No. {i}, calculating total length as main branch length.") + total_length_calculation_by_NN(cluster_skeleton, length_list_main, length_list_total) + else: + skeleton_summary = skeleton_summary.groupby(['main']).sum().reset_index() + try: + length_list_main.append(float(skeleton_summary.loc[skeleton_summary['main'] == True]['euclidean-distance'])) + except TypeError: + print(f'Failed to build skeleton path for cluster No.{i}, calculating total length as main branch length.') + total_length_calculation_by_NN(cluster_skeleton, length_list_main, length_list_total) + else: + length_list_total.append(float(skeleton_summary['euclidean-distance'].sum())) + cluster_profile = regionprops_table(cluster_img, properties=['label', 'area', 'centroid', 'convex_area', 'major_axis_length', 'minor_axis_length', 'eccentricity','bbox']) # Profile the aggregates if self.parameters['length_calculation'] == True: - cluster_profile['length'] = length_list # Add the length result to the profile + cluster_profile['length_main_branch'] = length_list_main # Add the length result to the profile + cluster_profile['length_all_branches'] = length_list_total cluster_profile = pd.DataFrame(cluster_profile) @@ -1174,22 +1222,31 @@ def _cluster_DBSCAN(self, field_name): if self.parameters['length_calculation'] == True: - cluster_profile.columns = ['cluster_id', 'area', 'X_(px)', 'Y_(px)', 'convex_area', 'major_axis_length', 'minor_axis_length', 'eccentricity', 'xMin', 'yMin', 'xMax', 'yMax', 'length', 'n_localisation'] + cluster_profile.columns = ['cluster_id', 'area', 'X_(px)', 'Y_(px)', 'convex_area', 'major_axis_length', 'minor_axis_length', 'eccentricity', 'xMin', 'yMin', 'xMax', 'yMax', 'length_main_branch', 'length_all_branches', 'n_localisation'] + summary = cluster_profile.agg({ + 'cluster_id': 'max', + 'n_localisation' : ['max', 'min', 'mean'], + 'area': ['max', 'min', 'mean'], + 'convex_area': ['max', 'min', 'mean'], + 'major_axis_length' : ['max', 'min', 'mean'], + 'eccentricity': ['max', 'min', 'mean'], + 'length_main_branch': ['max', 'min', 'mean'], + 'length_all_branches': ['max', 'min', 'mean'] + }) else: cluster_profile.columns = ['cluster_id', 'area', 'X_(px)', 'Y_(px)', 'convex_area', 'major_axis_length', 'minor_axis_length', 'eccentricity', 'xMin', 'yMin', 'xMax', 'yMax', 'n_localisation'] - - # Save cluster profile file - cluster_profile.to_csv(os.path.join(self.path_result_fid, field_name+'_clusterProfile_' + str(self.parameters['DBSCAN']['eps']) + '_' + str(self.parameters['DBSCAN']['min_sample']) + '.csv')) - - summary = cluster_profile.agg({ + summary = cluster_profile.agg({ 'cluster_id': 'max', 'n_localisation' : ['max', 'min', 'mean'], 'area': ['max', 'min', 'mean'], 'convex_area': ['max', 'min', 'mean'], 'major_axis_length' : ['max', 'min', 'mean'], 'eccentricity': ['max', 'min', 'mean'] - }) + }) summary.at['max', 'n_noise'] = n_noise + # Save cluster profile file + cluster_profile.to_csv(os.path.join(self.path_result_fid, field_name+'_clusterProfile_' + str(self.parameters['DBSCAN']['eps']) + '_' + str(self.parameters['DBSCAN']['min_sample']) + '.csv')) + else: summary = pd.DataFrame({ 'cluster_id': [0, '', ''], @@ -1233,8 +1290,8 @@ def superRes_clustering(self, progress_signal=None): self._cluster_dataFiltering(field) except KeyError: print('Filtering is not selected.') + pass - report = self._cluster_DBSCAN(field) try: report_df = pd.concat([report_df, report]) diff --git a/main.py b/main.py index f0734fc..5dcfb1c 100755 --- a/main.py +++ b/main.py @@ -1,3 +1,8 @@ +""" +Beta-2 release for Revision of SR length measurement +v2.1.1-beta-2 +update date: 20230716 +""" import sys import os import shutil @@ -101,7 +106,7 @@ def createWidget(self, className, parent=None, name=""): print(loader.errorString()) sys.exit(-1) self.window.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) def updateLog(self, message): @@ -137,7 +142,7 @@ def showMessage(self, msg_type, message): msgBox.setIcon(QMessageBox.Information) msgBox.setWindowTitle('Information') msgBox.setText(message) - returnValue = msgBox.exec_() + returnValue = msgBox.exec() def loadDataPath(self): @@ -155,29 +160,22 @@ def clickDFLSPRun(self): def clickDFLSPGenerateReports(self): - data_path = self.window.DFLSP_pathEntry.text() - self.data_path = data_path.replace('_results' + self.parameters, '') - self.window.DFLSP_pathEntry.setText(self.data_path) guard = self._checkDFLSPParameters() if guard == 1: - if os.path.isdir(self.data_path + '_results' + self.parameters) ==False: + if not os.path.isdir(self.project.path_result_main): self.showMessage('w', 'This dataset has not been analysed. Please run analysis.') else: - - self.updateLog('Data path set to '+data_path) + self.updateLog('Data path set to '+ self.data_path) self._generateDFLSPReports() def clickDFLSPReadTaggedResults(self): - data_path = self.window.DFLSP_pathEntry.text() - self.data_path = data_path.replace('_results' + self.parameters, '') - self.window.DFLSP_pathEntry.setText(self.data_path) guard = self._checkDFLSPParameters() if guard == 1: - if os.path.isdir(self.data_path + '_results' + self.parameters) ==False: + if not os.path.isdir(self.project.path_result_main): self.showMessage('w', 'This dataset has not been analysed. Please run analysis.') else: - self.updateLog('Data path set to '+data_path) + self.updateLog('Data path set to '+ self.data_path) self._showDFLSPResult() self.window.DFLSP_tagButton.setEnabled(True) self.window.DFLSP_oaButton.setEnabled(True) @@ -191,8 +189,6 @@ def _checkDFLSPParameters(self): return 0 else: self.data_path = data_path - self.window.DFLSP_pathEntry.setText(self.data_path) - self.updateLog('Data path set to '+data_path) # Get the method for analysis self.method = self.window.DFLSP_methodSelector.currentText() @@ -222,6 +218,10 @@ def _checkDFLSPParameters(self): self.updateLog('Estimated particle size set as '+str(self.size)+' pixels.') self.parameters = '_' + self.method + '_' + str(self.threshold) + '_' + str(self.size) + self.data_path = self.data_path.replace('_results' + self.parameters, '') + self.window.DFLSP_pathEntry.setText(self.data_path) + self.updateLog('Data path set to '+data_path) + self.project = DiffractionLimitedAnalysis(self.data_path, self.parameters) # Creat DiffractionLimitedAnalysis object if self.project.error == 1: return 1 @@ -1092,7 +1092,7 @@ def loadUI(self): self.window = loader.load(ui_file, self.mainWindow) self.window.loadButton.clicked.connect(self.clickLoadButton) - self.window.buttonBox.button(self.window.buttonBox.Apply).clicked.connect(self._applyTags) + self.window.buttonBox.button(self.window.buttonBox.StandardButton.Apply).clicked.connect(self._applyTags) ui_file.close() if not self.window: @@ -1152,7 +1152,7 @@ def loadUI(self): loader = QUiLoader() self.window = loader.load(ui_file, self.mainWindow) - self.window.buttonBox.button(self.window.buttonBox.Apply).clicked.connect(self.clickedApply) + self.window.buttonBox.button(self.window.buttonBox.StandardButton.Apply).clicked.connect(self.clickedApply) rm_list = ['NoOfFoV', 'ParticlePerFoV', 'MeanSize', 'MeanIntegrInt', 'MeanIntPerArea'] df = pd.read_csv(self.parent.project.path_result_main + '/Summary.csv') @@ -1194,7 +1194,7 @@ def clickedApply(self): msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('Warning') msgBox.setText('Cannot select the same condition.') - returnValue = msgBox.exec_() + returnValue = msgBox.exec() else: self.output.emit(self.options[experimentSelection], self.options[xaxisSelection]) self.finished.emit() @@ -1411,8 +1411,8 @@ def loadUI(self): loader = QUiLoader() self.window = loader.load(ui_file, self.mainWindow) - self.window.buttonBox.button(self.window.buttonBox.Ok).clicked.connect(self._folderSplitter) - self.window.buttonBox.button(self.window.buttonBox.Cancel).clicked.connect(self._cancel) + self.window.buttonBox.button(self.window.buttonBox.StandardButton.Ok).clicked.connect(self._folderSplitter) + self.window.buttonBox.button(self.window.buttonBox.StandardButton.Cancel).clicked.connect(self._cancel) self.window.moreConditionButton.clicked.connect(self._moreCondition) self.window.conditionWidgetLayout = QGridLayout(self.window.conditionWidget) @@ -1453,14 +1453,14 @@ def _folderSplitter(self): msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('Warning') msgBox.setText('No path was specified.') - returnValue = msgBox.exec_() + returnValue = msgBox.exec() return 0 elif os.path.isdir(job_path) != True: msgBox = QMessageBox(self.mainWindow) msgBox.setIcon(QMessageBox.Warning) msgBox.setWindowTitle('Warning') msgBox.setText('Path input was invalid.') - returnValue = msgBox.exec_() + returnValue = msgBox.exec() return 0 else: job_dict = {} @@ -1490,4 +1490,4 @@ def _folderSplitter(self): app.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "UI_form/lulu.ico"))) main_window = MainWindow() main_window.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py deleted file mode 100644 index 1c6e76b..0000000 --- a/pyqtgraph/GraphicsScene/GraphicsScene.py +++ /dev/null @@ -1,578 +0,0 @@ -# -*- coding: utf-8 -*- -import time -import weakref -import warnings - -from ..Qt import QtCore, QtGui, QT_LIB, isQObjectAlive -from ..Point import Point -from .. import functions as fn -from .. import ptime as ptime -from .mouseEvents import * -from .. import debug as debug -from .. import getConfigOption - -getMillis = lambda: int(round(time.time() * 1000)) - - -if QT_LIB.startswith('PyQt'): - from ..Qt import sip - HAVE_SIP = True -else: - HAVE_SIP = False - - -__all__ = ['GraphicsScene'] - -class GraphicsScene(QtGui.QGraphicsScene): - """ - Extension of QGraphicsScene that implements a complete, parallel mouse event system. - (It would have been preferred to just alter the way QGraphicsScene creates and delivers - events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent - is private) - - * Generates MouseClicked events in addition to the usual press/move/release events. - (This works around a problem where it is impossible to have one item respond to a - drag if another is watching for a click.) - * Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects - * Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu. - * Allows items to decide _before_ a mouse click which item will be the recipient of mouse events. - This lets us indicate unambiguously to the user which item they are about to click/drag on - * Eats mouseMove events that occur too soon after a mouse press. - * Reimplements items() and itemAt() to circumvent PyQt bug - - ====================== ==================================================================== - **Signals** - sigMouseClicked(event) Emitted when the mouse is clicked over the scene. Use ev.pos() to - get the click position relative to the item that was clicked on, - or ev.scenePos() to get the click position in scene coordinates. - See :class:`pyqtgraph.GraphicsScene.MouseClickEvent`. - sigMouseMoved(pos) Emitted when the mouse cursor moves over the scene. The position - is given in scene coordinates. - sigMouseHover(items) Emitted when the mouse is moved over the scene. Items is a list - of items under the cursor. - sigItemAdded(item) Emitted when an item is added via addItem(). The item is given. - sigItemRemoved(item) Emitted when an item is removed via removeItem(). The item is given. - ====================== ==================================================================== - - Mouse interaction is as follows: - - 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents - as well as custom HoverEvents. - 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), - acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ - the user clicks/drags the specified mouse button, the item is guaranteed to be the - recipient of click/drag events (the item may wish to change its appearance to indicate this). - If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive - the requested event (because another item has already accepted it). - 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then - No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows - items to function properly if they are expecting the usual press/move/release sequence of events. - (It is recommended that items do NOT accept press events, and instead use click/drag events) - Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the - item is has its Selectable or Movable flags enabled. You may need to override this behavior. - 4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. - If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, - then a mouseDragEvent is generated. - If no drag events are generated before the button is released, then a mouseClickEvent is generated. - 5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent - in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. - ClickEvents may be delivered in this way even if no - item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial - move in a drag. - """ - - sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over - sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move - sigMouseClicked = QtCore.Signal(object) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on. - - sigPrepareForPaint = QtCore.Signal() ## emitted immediately before the scene is about to be rendered - - sigItemAdded = QtCore.Signal(object) ## emits the item object just added - sigItemRemoved = QtCore.Signal(object) ## emits the item object just removed - - _addressCache = weakref.WeakValueDictionary() - - ExportDirectory = None - - @classmethod - def registerObject(cls, obj): - warnings.warn( - "'registerObject' is deprecated and does nothing.", - DeprecationWarning, stacklevel=2 - ) - - def __init__(self, clickRadius=2, moveDistance=5, parent=None): - QtGui.QGraphicsScene.__init__(self, parent) - self.setClickRadius(clickRadius) - self.setMoveDistance(moveDistance) - self.exportDirectory = None - - self.clickEvents = [] - self.dragButtons = [] - self.mouseGrabber = None - self.dragItem = None - self.lastDrag = None - self.hoverItems = weakref.WeakKeyDictionary() - self.lastHoverEvent = None - self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks - - self.contextMenu = [QtGui.QAction(QtCore.QCoreApplication.translate("GraphicsScene", "Export..."), self)] - self.contextMenu[0].triggered.connect(self.showExportDialog) - - self.exportDialog = None - self._lastMoveEventTime = 0 - - def render(self, *args): - self.prepareForPaint() - return QtGui.QGraphicsScene.render(self, *args) - - def prepareForPaint(self): - """Called before every render. This method will inform items that the scene is about to - be rendered by emitting sigPrepareForPaint. - - This allows items to delay expensive processing until they know a paint will be required.""" - self.sigPrepareForPaint.emit() - - - def setClickRadius(self, r): - """ - Set the distance away from mouse clicks to search for interacting items. - When clicking, the scene searches first for items that directly intersect the click position - followed by any other items that are within a rectangle that extends r pixels away from the - click position. - """ - self._clickRadius = r - - def setMoveDistance(self, d): - """ - Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. - This ensures that clicks with a small amount of movement are recognized as clicks instead of - drags. - """ - self._moveDistance = d - - def mousePressEvent(self, ev): - super().mousePressEvent(ev) - if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events - if self.lastHoverEvent is not None: - # If the mouse has moved since the last hover event, send a new one. - # This can happen if a context menu is open while the mouse is moving. - if ev.scenePos() != self.lastHoverEvent.scenePos(): - self.sendHoverEvents(ev) - - self.clickEvents.append(MouseClickEvent(ev)) - - ## set focus on the topmost focusable item under this click - items = self.items(ev.scenePos()) - for i in items: - if i.isEnabled() and i.isVisible() and (i.flags() & i.ItemIsFocusable): - i.setFocus(QtCore.Qt.MouseFocusReason) - break - - def _moveEventIsAllowed(self): - # For ignoring events that are too close together - - # Max number of events per second - rateLimit = getConfigOption('mouseRateLimit') - if rateLimit <= 0: - return True - - # Delay between events (in milliseconds) - delay = 1000.0 / rateLimit - if getMillis() - self._lastMoveEventTime >= delay: - return True - return False - - - def mouseMoveEvent(self, ev): - # ignore high frequency events - if self._moveEventIsAllowed(): - self._lastMoveEventTime = getMillis() - self.sigMouseMoved.emit(ev.scenePos()) - - # First allow QGraphicsScene to eliver hoverEvent/Move/Exit Events - super().mouseMoveEvent(ev) - # Next Deliver our own Hover Events - self.sendHoverEvents(ev) - if ev.buttons(): - # button is pressed' send mouseMoveEvents and mouseDragEvents - super().mouseMoveEvent(ev) - if self.mouseGrabberItem() is None: - now = ptime.time() - init = False - ## keep track of which buttons are involved in dragging - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MiddleButton, QtCore.Qt.RightButton]: - if not (ev.buttons() & btn): - continue - if btn not in self.dragButtons: ## see if we've dragged far enough yet - cev = [e for e in self.clickEvents if e.button() == btn] - if cev: - cev = cev[0] - dist = Point(ev.scenePos() - cev.scenePos()).length() - if dist == 0 or (dist < self._moveDistance and now - cev.time() < self.minDragTime): - continue - init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True - self.dragButtons.append(btn) - ## if we have dragged buttons, deliver a drag event - if len(self.dragButtons) > 0: - if self.sendDragEvent(ev, init=init): - ev.accept() - - else: - super().mouseMoveEvent(ev) - # if you do not accept event (which is ignored) then cursor will disappear - ev.accept() - - def leaveEvent(self, ev): ## inform items that mouse is gone - if len(self.dragButtons) == 0: - self.sendHoverEvents(ev, exitOnly=True) - - def mouseReleaseEvent(self, ev): - if self.mouseGrabberItem() is None: - if ev.button() in self.dragButtons: - if self.sendDragEvent(ev, final=True): - #print "sent drag event" - ev.accept() - self.dragButtons.remove(ev.button()) - else: - cev = [e for e in self.clickEvents if e.button() == ev.button()] - if cev: - if self.sendClickEvent(cev[0]): - #print "sent click event" - ev.accept() - self.clickEvents.remove(cev[0]) - - if not ev.buttons(): - self.dragItem = None - self.dragButtons = [] - self.clickEvents = [] - self.lastDrag = None - super().mouseReleaseEvent(ev) - - self.sendHoverEvents(ev) ## let items prepare for next click/drag - - def mouseDoubleClickEvent(self, ev): - super().mouseDoubleClickEvent(ev) - if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events - self.clickEvents.append(MouseClickEvent(ev, double=True)) - - def sendHoverEvents(self, ev, exitOnly=False): - ## if exitOnly, then just inform all previously hovered items that the mouse has left. - - if exitOnly: - acceptable=False - items = [] - event = HoverEvent(None, acceptable) - else: - acceptable = not ev.buttons() ## if we are in mid-drag, do not allow items to accept the hover event. - event = HoverEvent(ev, acceptable) - items = self.itemsNearEvent(event, hoverable=True) - self.sigMouseHover.emit(items) - - prevItems = list(self.hoverItems.keys()) - - for item in items: - if hasattr(item, 'hoverEvent'): - event.currentItem = item - if item not in self.hoverItems: - self.hoverItems[item] = None - event.enter = True - else: - prevItems.remove(item) - event.enter = False - - try: - item.hoverEvent(event) - except: - debug.printExc("Error sending hover event:") - - event.enter = False - event.exit = True - #print "hover exit items:", prevItems - for item in prevItems: - event.currentItem = item - try: - # NOTE: isQObjectAlive(item) was added for PySide6 where - # verlet_chain_demo.py triggers a RuntimeError. - if isQObjectAlive(item) and item.scene() is self: - item.hoverEvent(event) - except: - debug.printExc("Error sending hover exit event:") - finally: - del self.hoverItems[item] - - # Update last hover event unless: - # - mouse is dragging (move+buttons); in this case we want the dragged - # item to continue receiving events until the drag is over - # - event is not a mouse event (QEvent.Leave sometimes appears here) - if (ev.type() == ev.GraphicsSceneMousePress or - (ev.type() == ev.GraphicsSceneMouseMove and not ev.buttons())): - self.lastHoverEvent = event ## save this so we can ask about accepted events later. - - def sendDragEvent(self, ev, init=False, final=False): - ## Send a MouseDragEvent to the current dragItem or to - ## items near the beginning of the drag - event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) - #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem - if init and self.dragItem is None: - if self.lastHoverEvent is not None: - acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) - else: - acceptedItem = None - - if acceptedItem is not None and acceptedItem.scene() is self: - #print "Drag -> pre-selected item:", acceptedItem - self.dragItem = acceptedItem - event.currentItem = self.dragItem - try: - self.dragItem.mouseDragEvent(event) - except: - debug.printExc("Error sending drag event:") - - else: - #print "drag -> new item" - for item in self.itemsNearEvent(event): - #print "check item:", item - if not item.isVisible() or not item.isEnabled(): - continue - if hasattr(item, 'mouseDragEvent'): - event.currentItem = item - try: - item.mouseDragEvent(event) - except: - debug.printExc("Error sending drag event:") - if event.isAccepted(): - #print " --> accepted" - self.dragItem = item - if item.flags() & item.ItemIsFocusable: - item.setFocus(QtCore.Qt.MouseFocusReason) - break - elif self.dragItem is not None: - event.currentItem = self.dragItem - try: - self.dragItem.mouseDragEvent(event) - except: - debug.printExc("Error sending hover exit event:") - - self.lastDrag = event - - return event.isAccepted() - - - def sendClickEvent(self, ev): - ## if we are in mid-drag, click events may only go to the dragged item. - if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): - ev.currentItem = self.dragItem - self.dragItem.mouseClickEvent(ev) - - ## otherwise, search near the cursor - else: - if self.lastHoverEvent is not None: - acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) - else: - acceptedItem = None - if acceptedItem is not None: - ev.currentItem = acceptedItem - try: - acceptedItem.mouseClickEvent(ev) - except: - debug.printExc("Error sending click event:") - else: - for item in self.itemsNearEvent(ev): - if not item.isVisible() or not item.isEnabled(): - continue - if hasattr(item, 'mouseClickEvent'): - ev.currentItem = item - try: - item.mouseClickEvent(ev) - except: - debug.printExc("Error sending click event:") - - if ev.isAccepted(): - if item.flags() & item.ItemIsFocusable: - item.setFocus(QtCore.Qt.MouseFocusReason) - break - self.sigMouseClicked.emit(ev) - return ev.isAccepted() - - def addItem(self, item): - # extend QGraphicsScene.addItem to emit a sigItemAdded signal - ret = QtGui.QGraphicsScene.addItem(self, item) - self.sigItemAdded.emit(item) - return ret - - def removeItem(self, item): - # extend QGraphicsScene.removeItem to emit a sigItemRemoved signal - ret = QtGui.QGraphicsScene.removeItem(self, item) - self.sigItemRemoved.emit(item) - return ret - - def items(self, *args): - items = QtGui.QGraphicsScene.items(self, *args) - return self.translateGraphicsItems(items) - - def selectedItems(self, *args): - items = QtGui.QGraphicsScene.selectedItems(self, *args) - return self.translateGraphicsItems(items) - - def itemAt(self, *args): - item = QtGui.QGraphicsScene.itemAt(self, *args) - return self.translateGraphicsItem(item) - - def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder, hoverable=False): - """ - Return an iterator that iterates first through the items that directly intersect point (in Z order) - followed by any other items that are within the scene's click radius. - """ - #tr = self.getViewWidget(event.widget()).transform() - view = self.views()[0] - tr = view.viewportTransform() - r = self._clickRadius - rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() - - seen = set() - if hasattr(event, 'buttonDownScenePos'): - point = event.buttonDownScenePos() - else: - point = event.scenePos() - w = rect.width() - h = rect.height() - rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) - #self.searchRect.setRect(rgn) - - - items = self.items(point, selMode, sortOrder, tr) - - ## remove items whose shape does not contain point (scene.items() apparently sucks at this) - items2 = [] - for item in items: - if hoverable and not hasattr(item, 'hoverEvent'): - continue - if item.scene() is not self: - continue - shape = item.shape() # Note: default shape() returns boundingRect() - if shape is None: - continue - if shape.contains(item.mapFromScene(point)): - items2.append(item) - - ## Sort by descending Z-order (don't trust scene.itms() to do this either) - ## use 'absolute' z value, which is the sum of all item/parent ZValues - def absZValue(item): - if item is None: - return 0 - return item.zValue() + absZValue(item.parentItem()) - - items2.sort(key=absZValue, reverse=True) - - return items2 - - #for item in items: - ##seen.add(item) - - #shape = item.mapToScene(item.shape()) - #if not shape.contains(point): - #continue - #yield item - #for item in self.items(rgn, selMode, sortOrder, tr): - ##if item not in seen: - #yield item - - def getViewWidget(self): - return self.views()[0] - - #def getViewWidget(self, widget): - ### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. - ### [[doesn't seem to work correctly]] - #if HAVE_SIP and isinstance(self, sip.wrapper): - #addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget)) - ##print "convert", widget, addr - #for v in self.views(): - #addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget)) - ##print " check:", v, addr2 - #if addr2 == addr: - #return v - #else: - #return widget - - def addParentContextMenus(self, item, menu, event): - """ - Can be called by any item in the scene to expand its context menu to include parent context menus. - Parents may implement getContextMenus to add new menus / actions to the existing menu. - getContextMenus must accept 1 argument (the event that generated the original menu) and - return a single QMenu or a list of QMenus. - - The final menu will look like: - - | Original Item 1 - | Original Item 2 - | ... - | Original Item N - | ------------------ - | Parent Item 1 - | Parent Item 2 - | ... - | Grandparent Item 1 - | ... - - - ============== ================================================== - **Arguments:** - item The item that initially created the context menu - (This is probably the item making the call to this function) - menu The context menu being shown by the item - event The original event that triggered the menu to appear. - ============== ================================================== - """ - - menusToAdd = [] - while item is not self: - item = item.parentItem() - if item is None: - item = self - if not hasattr(item, "getContextMenus"): - continue - subMenus = item.getContextMenus(event) or [] - if isinstance(subMenus, list): ## so that some items (like FlowchartViewBox) can return multiple menus - menusToAdd.extend(subMenus) - else: - menusToAdd.append(subMenus) - - if menusToAdd: - menu.addSeparator() - - for m in menusToAdd: - if isinstance(m, QtGui.QMenu): - menu.addAction(m.menuAction()) - elif isinstance(m, QtGui.QAction): - menu.addAction(m) - else: - raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m)))) - - return menu - - def getContextMenus(self, event): - self.contextMenuItem = event.acceptedItem - return self.contextMenu - - def showExportDialog(self): - if self.exportDialog is None: - from . import exportDialog - self.exportDialog = exportDialog.ExportDialog(self) - self.exportDialog.show(self.contextMenuItem) - - @staticmethod - def translateGraphicsItem(item): - # This function is intended as a workaround for a problem with older - # versions of PyQt (< 4.9?), where methods returning 'QGraphicsItem *' - # lose the type of the QGraphicsObject subclasses and instead return - # generic QGraphicsItem wrappers. - if HAVE_SIP and isinstance(item, sip.wrapper): - obj = item.toGraphicsObject() - if obj is not None: - item = obj - return item - - @staticmethod - def translateGraphicsItems(items): - return list(map(GraphicsScene.translateGraphicsItem, items)) diff --git a/pyqtgraph/GraphicsScene/__init__.py b/pyqtgraph/GraphicsScene/__init__.py deleted file mode 100644 index abe42c6..0000000 --- a/pyqtgraph/GraphicsScene/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .GraphicsScene import * diff --git a/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-36.pyc deleted file mode 100644 index ae5e499..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-37.pyc b/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-37.pyc deleted file mode 100644 index 8a4b161..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/GraphicsScene.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index b0d4a5d..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index ebb95de..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/exportDialog.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/exportDialog.cpython-36.pyc deleted file mode 100644 index be401f2..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/exportDialog.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index 85c929b..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index 7d5e657..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside2.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index e10c646..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside6.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index 30bb15c..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/exportDialogTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-36.pyc b/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-36.pyc deleted file mode 100644 index b74a782..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-37.pyc b/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-37.pyc deleted file mode 100644 index 2bddfe2..0000000 Binary files a/pyqtgraph/GraphicsScene/__pycache__/mouseEvents.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py deleted file mode 100644 index f4af265..0000000 --- a/pyqtgraph/GraphicsScene/exportDialog.py +++ /dev/null @@ -1,147 +0,0 @@ -from ..Qt import QtCore, QtGui, QtWidgets, QT_LIB -from .. import exporters as exporters -from .. import functions as fn -from ..graphicsItems.ViewBox import ViewBox -from ..graphicsItems.PlotItem import PlotItem - -import importlib -ui_template = importlib.import_module( - f'.exportDialogTemplate_{QT_LIB.lower()}', package=__package__) - - -class FormatExportListWidgetItem(QtWidgets.QListWidgetItem): - def __init__(self, expClass, *args, **kwargs): - QtWidgets.QListWidgetItem.__init__(self, *args, **kwargs) - self.expClass = expClass - - -class ExportDialog(QtGui.QWidget): - def __init__(self, scene): - QtGui.QWidget.__init__(self) - self.setVisible(False) - self.setWindowTitle("Export") - self.shown = False - self.currentExporter = None - self.scene = scene - - self.selectBox = QtGui.QGraphicsRectItem() - self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine)) - self.selectBox.hide() - self.scene.addItem(self.selectBox) - - self.ui = ui_template.Ui_Form() - self.ui.setupUi(self) - - self.ui.closeBtn.clicked.connect(self.close) - self.ui.exportBtn.clicked.connect(self.exportClicked) - self.ui.copyBtn.clicked.connect(self.copyClicked) - self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged) - self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged) - - - def show(self, item=None): - if item is not None: - ## Select next exportable parent of the item originally clicked on - while not isinstance(item, ViewBox) and not isinstance(item, PlotItem) and item is not None: - item = item.parentItem() - ## if this is a ViewBox inside a PlotItem, select the parent instead. - if isinstance(item, ViewBox) and isinstance(item.parentItem(), PlotItem): - item = item.parentItem() - self.updateItemList(select=item) - self.setVisible(True) - self.activateWindow() - self.raise_() - self.selectBox.setVisible(True) - - if not self.shown: - self.shown = True - vcenter = self.scene.getViewWidget().geometry().center() - self.setGeometry(vcenter.x()-self.width()/2, vcenter.y()-self.height()/2, self.width(), self.height()) - - def updateItemList(self, select=None): - self.ui.itemTree.clear() - si = QtGui.QTreeWidgetItem(["Entire Scene"]) - si.gitem = self.scene - self.ui.itemTree.addTopLevelItem(si) - self.ui.itemTree.setCurrentItem(si) - si.setExpanded(True) - for child in self.scene.items(): - if child.parentItem() is None: - self.updateItemTree(child, si, select=select) - - def updateItemTree(self, item, treeItem, select=None): - si = None - if isinstance(item, ViewBox): - si = QtGui.QTreeWidgetItem(['ViewBox']) - elif isinstance(item, PlotItem): - si = QtGui.QTreeWidgetItem(['Plot']) - - if si is not None: - si.gitem = item - treeItem.addChild(si) - treeItem = si - if si.gitem is select: - self.ui.itemTree.setCurrentItem(si) - - for ch in item.childItems(): - self.updateItemTree(ch, treeItem, select=select) - - - def exportItemChanged(self, item, prev): - if item is None: - return - if item.gitem is self.scene: - newBounds = self.scene.views()[0].viewRect() - else: - newBounds = item.gitem.sceneBoundingRect() - self.selectBox.setRect(newBounds) - self.selectBox.show() - self.updateFormatList() - - def updateFormatList(self): - current = self.ui.formatList.currentItem() - - self.ui.formatList.clear() - gotCurrent = False - for exp in exporters.listExporters(): - item = FormatExportListWidgetItem(exp, QtCore.QCoreApplication.translate('Exporter', exp.Name)) - self.ui.formatList.addItem(item) - if item == current: - self.ui.formatList.setCurrentRow(self.ui.formatList.count() - 1) - gotCurrent = True - - if not gotCurrent: - self.ui.formatList.setCurrentRow(0) - - def exportFormatChanged(self, item, prev): - if item is None: - self.currentExporter = None - self.ui.paramTree.clear() - return - expClass = item.expClass - exp = expClass(item=self.ui.itemTree.currentItem().gitem) - - params = exp.parameters() - - if params is None: - self.ui.paramTree.clear() - else: - self.ui.paramTree.setParameters(params) - self.currentExporter = exp - self.ui.copyBtn.setEnabled(exp.allowCopy) - - def exportClicked(self): - self.selectBox.hide() - self.currentExporter.export() - - def copyClicked(self): - self.selectBox.hide() - self.currentExporter.export(copy=True) - - def close(self): - self.selectBox.setVisible(False) - self.setVisible(False) - - def closeEvent(self, event): - self.close() - super().closeEvent(event) diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py deleted file mode 100644 index 418fd0f..0000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt5.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui' -# -# Created: Wed Mar 26 15:09:29 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(241, 367) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - self.itemTree = QtWidgets.QTreeWidget(Form) - self.itemTree.setObjectName("itemTree") - self.itemTree.headerItem().setText(0, "1") - self.itemTree.header().setVisible(False) - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - self.label_2 = QtWidgets.QLabel(Form) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - self.formatList = QtWidgets.QListWidget(Form) - self.formatList.setObjectName("formatList") - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - self.exportBtn = QtWidgets.QPushButton(Form) - self.exportBtn.setObjectName("exportBtn") - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - self.closeBtn = QtWidgets.QPushButton(Form) - self.closeBtn.setObjectName("closeBtn") - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - self.paramTree = ParameterTree(Form) - self.paramTree.setObjectName("paramTree") - self.paramTree.headerItem().setText(0, "1") - self.paramTree.header().setVisible(False) - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - self.label_3 = QtWidgets.QLabel(Form) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - self.copyBtn = QtWidgets.QPushButton(Form) - self.copyBtn.setObjectName("copyBtn") - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Export")) - self.label.setText(_translate("Form", "Item to export:")) - self.label_2.setText(_translate("Form", "Export format")) - self.exportBtn.setText(_translate("Form", "Export")) - self.closeBtn.setText(_translate("Form", "Close")) - self.label_3.setText(_translate("Form", "Export options")) - self.copyBtn.setText(_translate("Form", "Copy")) - -from ..parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py deleted file mode 100644 index 55cbe3a..0000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py +++ /dev/null @@ -1,63 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\GraphicsScene\exportDialogTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(241, 367) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - self.itemTree = QtWidgets.QTreeWidget(Form) - self.itemTree.setObjectName("itemTree") - self.itemTree.headerItem().setText(0, "1") - self.itemTree.header().setVisible(False) - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - self.label_2 = QtWidgets.QLabel(Form) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - self.formatList = QtWidgets.QListWidget(Form) - self.formatList.setObjectName("formatList") - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - self.exportBtn = QtWidgets.QPushButton(Form) - self.exportBtn.setObjectName("exportBtn") - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - self.closeBtn = QtWidgets.QPushButton(Form) - self.closeBtn.setObjectName("closeBtn") - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - self.paramTree = ParameterTree(Form) - self.paramTree.setObjectName("paramTree") - self.paramTree.headerItem().setText(0, "1") - self.paramTree.header().setVisible(False) - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - self.label_3 = QtWidgets.QLabel(Form) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - self.copyBtn = QtWidgets.QPushButton(Form) - self.copyBtn.setObjectName("copyBtn") - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Export")) - self.label.setText(_translate("Form", "Item to export:")) - self.label_2.setText(_translate("Form", "Export format")) - self.exportBtn.setText(_translate("Form", "Export")) - self.closeBtn.setText(_translate("Form", "Close")) - self.label_3.setText(_translate("Form", "Export options")) - self.copyBtn.setText(_translate("Form", "Copy")) -from ..parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py deleted file mode 100644 index 6c0fec4..0000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'exportDialogTemplate.ui' -# -# Created: Sun Sep 18 19:19:58 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(241, 367) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - self.itemTree = QtWidgets.QTreeWidget(Form) - self.itemTree.setObjectName("itemTree") - self.itemTree.headerItem().setText(0, "1") - self.itemTree.header().setVisible(False) - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - self.label_2 = QtWidgets.QLabel(Form) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - self.formatList = QtWidgets.QListWidget(Form) - self.formatList.setObjectName("formatList") - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - self.exportBtn = QtWidgets.QPushButton(Form) - self.exportBtn.setObjectName("exportBtn") - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - self.closeBtn = QtWidgets.QPushButton(Form) - self.closeBtn.setObjectName("closeBtn") - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - self.paramTree = ParameterTree(Form) - self.paramTree.setObjectName("paramTree") - self.paramTree.headerItem().setText(0, "1") - self.paramTree.header().setVisible(False) - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - self.label_3 = QtWidgets.QLabel(Form) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - self.copyBtn = QtWidgets.QPushButton(Form) - self.copyBtn.setObjectName("copyBtn") - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Export", None, -1)) - self.label.setText(QtWidgets.QApplication.translate("Form", "Item to export:", None, -1)) - self.label_2.setText(QtWidgets.QApplication.translate("Form", "Export format", None, -1)) - self.exportBtn.setText(QtWidgets.QApplication.translate("Form", "Export", None, -1)) - self.closeBtn.setText(QtWidgets.QApplication.translate("Form", "Close", None, -1)) - self.label_3.setText(QtWidgets.QApplication.translate("Form", "Export options", None, -1)) - self.copyBtn.setText(QtWidgets.QApplication.translate("Form", "Copy", None, -1)) - -from ..parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside6.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside6.py deleted file mode 100644 index 92d813f..0000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside6.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'exportDialogTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from ..parametertree import ParameterTree - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(241, 367) - self.gridLayout = QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(u"gridLayout") - self.label = QLabel(Form) - self.label.setObjectName(u"label") - - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - - self.itemTree = QTreeWidget(Form) - __qtreewidgetitem = QTreeWidgetItem() - __qtreewidgetitem.setText(0, u"1"); - self.itemTree.setHeaderItem(__qtreewidgetitem) - self.itemTree.setObjectName(u"itemTree") - self.itemTree.header().setVisible(False) - - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - - self.label_2 = QLabel(Form) - self.label_2.setObjectName(u"label_2") - - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - - self.formatList = QListWidget(Form) - self.formatList.setObjectName(u"formatList") - - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - - self.exportBtn = QPushButton(Form) - self.exportBtn.setObjectName(u"exportBtn") - - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - - self.closeBtn = QPushButton(Form) - self.closeBtn.setObjectName(u"closeBtn") - - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - - self.paramTree = ParameterTree(Form) - __qtreewidgetitem1 = QTreeWidgetItem() - __qtreewidgetitem1.setText(0, u"1"); - self.paramTree.setHeaderItem(__qtreewidgetitem1) - self.paramTree.setObjectName(u"paramTree") - self.paramTree.header().setVisible(False) - - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - - self.label_3 = QLabel(Form) - self.label_3.setObjectName(u"label_3") - - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - - self.copyBtn = QPushButton(Form) - self.copyBtn.setObjectName(u"copyBtn") - - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"Export", None)) - self.label.setText(QCoreApplication.translate("Form", u"Item to export:", None)) - self.label_2.setText(QCoreApplication.translate("Form", u"Export format", None)) - self.exportBtn.setText(QCoreApplication.translate("Form", u"Export", None)) - self.closeBtn.setText(QCoreApplication.translate("Form", u"Close", None)) - self.label_3.setText(QCoreApplication.translate("Form", u"Export options", None)) - self.copyBtn.setText(QCoreApplication.translate("Form", u"Copy", None)) - # retranslateUi - diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/pyqtgraph/GraphicsScene/mouseEvents.py deleted file mode 100644 index b8c210a..0000000 --- a/pyqtgraph/GraphicsScene/mouseEvents.py +++ /dev/null @@ -1,380 +0,0 @@ -from ..Point import Point -from ..Qt import QtCore, QtGui -import weakref -from .. import ptime as ptime - -class MouseDragEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseDragEvent() method when the item is being mouse-dragged. - - """ - - - - def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): - self.start = start - self.finish = finish - self.accepted = False - self.currentItem = None - self._buttonDownScenePos = {} - self._buttonDownScreenPos = {} - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MiddleButton, QtCore.Qt.RightButton]: - self._buttonDownScenePos[btn] = moveEvent.buttonDownScenePos(btn) - self._buttonDownScreenPos[btn] = moveEvent.buttonDownScreenPos(btn) - self._scenePos = moveEvent.scenePos() - self._screenPos = moveEvent.screenPos() - if lastEvent is None: - self._lastScenePos = pressEvent.scenePos() - self._lastScreenPos = pressEvent.screenPos() - else: - self._lastScenePos = lastEvent.scenePos() - self._lastScreenPos = lastEvent.screenPos() - self._buttons = moveEvent.buttons() - self._button = pressEvent.button() - self._modifiers = moveEvent.modifiers() - self.acceptedItem = None - - def accept(self): - """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" - self.accepted = True - self.acceptedItem = self.currentItem - - def ignore(self): - """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" - self.accepted = False - - def isAccepted(self): - return self.accepted - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position (pixels relative to widget) of the mouse.""" - return Point(self._screenPos) - - def buttonDownScenePos(self, btn=None): - """ - Return the scene position of the mouse at the time *btn* was pressed. - If *btn* is omitted, then the button that initiated the drag is assumed. - """ - if btn is None: - btn = self.button() - return Point(self._buttonDownScenePos[btn]) - - def buttonDownScreenPos(self, btn=None): - """ - Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed. - If *btn* is omitted, then the button that initiated the drag is assumed. - """ - if btn is None: - btn = self.button() - return Point(self._buttonDownScreenPos[btn]) - - def lastScenePos(self): - """ - Return the scene position of the mouse immediately prior to this event. - """ - return Point(self._lastScenePos) - - def lastScreenPos(self): - """ - Return the screen position of the mouse immediately prior to this event. - """ - return Point(self._lastScreenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def button(self): - """Return the button that initiated the drag (may be different from the buttons currently pressed) - (see QGraphicsSceneMouseEvent::button in the Qt documentation) - - """ - return self._button - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def buttonDownPos(self, btn=None): - """ - Return the position of the mouse at the time the drag was initiated - in the coordinate system of the item that the event was delivered to. - """ - if btn is None: - btn = self.button() - return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[btn])) - - def isStart(self): - """Returns True if this event is the first since a drag was initiated.""" - return self.start - - def isFinish(self): - """Returns False if this is the last event in a drag. Note that this - event will have the same position as the previous one.""" - return self.finish - - def __repr__(self): - if self.currentItem is None: - lp = self._lastScenePos - p = self._scenePos - else: - lp = self.lastPos() - p = self.pos() - return "(%g,%g) buttons=%s start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isStart()), str(self.isFinish())) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - - """ - return self._modifiers - - - -class MouseClickEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseClickEvent() method when the item is clicked. - - - """ - - def __init__(self, pressEvent, double=False): - self.accepted = False - self.currentItem = None - self._double = double - self._scenePos = pressEvent.scenePos() - self._screenPos = pressEvent.screenPos() - self._button = pressEvent.button() - self._buttons = pressEvent.buttons() - self._modifiers = pressEvent.modifiers() - self._time = ptime.time() - self.acceptedItem = None - - def accept(self): - """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" - self.accepted = True - self.acceptedItem = self.currentItem - - def ignore(self): - """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" - self.accepted = False - - def isAccepted(self): - return self.accepted - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position (pixels relative to widget) of the mouse.""" - return Point(self._screenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def button(self): - """Return the mouse button that generated the click event. - (see QGraphicsSceneMouseEvent::button in the Qt documentation) - """ - return self._button - - def double(self): - """Return True if this is a double-click.""" - return self._double - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - """ - return self._modifiers - - def __repr__(self): - try: - if self.currentItem is None: - p = self._scenePos - else: - p = self.pos() - return "" % (p.x(), p.y(), str(self.button())) - except: - return "" % (str(self.button())) - - def time(self): - return self._time - - - -class HoverEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their hoverEvent() method when the mouse is hovering over the item. - This event class both informs items that the mouse cursor is nearby and allows items to - communicate with one another about whether each item will accept *potential* mouse events. - - It is common for multiple overlapping items to receive hover events and respond by changing - their appearance. This can be misleading to the user since, in general, only one item will - respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) - and/or acceptDrags(button). - - Each item may make multiple calls to acceptClicks/Drags, each time for a different button. - If the method returns True, then the item is guaranteed to be - the recipient of the claimed event IF the user presses the specified mouse button before - moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified - event (because another has already claimed it) and the item should change its appearance - accordingly. - - event.isEnter() returns True if the mouse has just entered the item's shape; - event.isExit() returns True if the mouse has just left. - """ - def __init__(self, moveEvent, acceptable): - self.enter = False - self.acceptable = acceptable - self.exit = False - self.__clickItems = weakref.WeakValueDictionary() - self.__dragItems = weakref.WeakValueDictionary() - self.currentItem = None - if moveEvent is not None: - self._scenePos = moveEvent.scenePos() - self._screenPos = moveEvent.screenPos() - self._lastScenePos = moveEvent.lastScenePos() - self._lastScreenPos = moveEvent.lastScreenPos() - self._buttons = moveEvent.buttons() - self._modifiers = moveEvent.modifiers() - else: - self.exit = True - - def isEnter(self): - """Returns True if the mouse has just entered the item's shape""" - return self.enter - - def isExit(self): - """Returns True if the mouse has just exited the item's shape""" - return self.exit - - def acceptClicks(self, button): - """Inform the scene that the item (that the event was delivered to) - would accept a mouse click event if the user were to click before - moving the mouse again. - - Returns True if the request is successful, otherwise returns False (indicating - that some other item would receive an incoming click). - """ - if not self.acceptable: - return False - if button not in self.__clickItems: - self.__clickItems[button] = self.currentItem - return True - return False - - def acceptDrags(self, button): - """Inform the scene that the item (that the event was delivered to) - would accept a mouse drag event if the user were to drag before - the next hover event. - - Returns True if the request is successful, otherwise returns False (indicating - that some other item would receive an incoming drag event). - """ - if not self.acceptable: - return False - if button not in self.__dragItems: - self.__dragItems[button] = self.currentItem - return True - return False - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position of the mouse.""" - return Point(self._screenPos) - - def lastScenePos(self): - """Return the previous scene position of the mouse.""" - return Point(self._lastScenePos) - - def lastScreenPos(self): - """Return the previous screen position of the mouse.""" - return Point(self._lastScreenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def __repr__(self): - if self.exit: - return "" - - if self.currentItem is None: - lp = self._lastScenePos - p = self._scenePos - else: - lp = self.lastPos() - p = self.pos() - return "(%g,%g) buttons=%s enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isEnter()), str(self.isExit())) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - """ - return self._modifiers - - def clickItems(self): - return self.__clickItems - - def dragItems(self): - return self.__dragItems - - - \ No newline at end of file diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py deleted file mode 100644 index f276050..0000000 --- a/pyqtgraph/PlotData.py +++ /dev/null @@ -1,53 +0,0 @@ -import numpy as np - - -class PlotData(object): - """ - Class used for managing plot data - - allows data sharing between multiple graphics items (curve, scatter, graph..) - - each item may define the columns it needs - - column groupings ('pos' or x, y, z) - - efficiently appendable - - log, fft transformations - - color mode conversion (float/byte/qcolor) - - pen/brush conversion - - per-field cached masking - - allows multiple masking fields (different graphics need to mask on different criteria) - - removal of nan/inf values - - option for single value shared by entire column - - cached downsampling - - cached min / max / hasnan / isuniform - """ - def __init__(self): - self.fields = {} - - self.maxVals = {} ## cache for max/min - self.minVals = {} - - def addFields(self, **fields): - for f in fields: - if f not in self.fields: - self.fields[f] = None - - def hasField(self, f): - return f in self.fields - - def __getitem__(self, field): - return self.fields[field] - - def __setitem__(self, field, val): - self.fields[field] = val - - def max(self, field): - mx = self.maxVals.get(field, None) - if mx is None: - mx = np.max(self[field]) - self.maxVals[field] = mx - return mx - - def min(self, field): - mn = self.minVals.get(field, None) - if mn is None: - mn = np.min(self[field]) - self.minVals[field] = mn - return mn diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py deleted file mode 100644 index fea37dd..0000000 --- a/pyqtgraph/Point.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Point.py - Extension of QPointF which adds a few missing methods. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from .Qt import QtCore -import numpy as np - -def clip(x, mn, mx): - if x > mx: - return mx - if x < mn: - return mn - return x - -class Point(QtCore.QPointF): - """Extension of QPointF which adds a few missing methods.""" - - def __init__(self, *args): - if len(args) == 1: - if isinstance(args[0], QtCore.QSizeF): - QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height())) - return - elif isinstance(args[0], float) or isinstance(args[0], int): - QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) - return - elif hasattr(args[0], '__getitem__'): - QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) - return - elif len(args) == 2: - QtCore.QPointF.__init__(self, args[0], args[1]) - return - QtCore.QPointF.__init__(self, *args) - - def __len__(self): - return 2 - - def __reduce__(self): - return (Point, (self.x(), self.y())) - - def __getitem__(self, i): - if i == 0: - return self.x() - elif i == 1: - return self.y() - else: - raise IndexError("Point has no index %s" % str(i)) - - def __setitem__(self, i, x): - if i == 0: - return self.setX(x) - elif i == 1: - return self.setY(x) - else: - raise IndexError("Point has no index %s" % str(i)) - - def __radd__(self, a): - return self._math_('__radd__', a) - - def __add__(self, a): - return self._math_('__add__', a) - - def __rsub__(self, a): - return self._math_('__rsub__', a) - - def __sub__(self, a): - return self._math_('__sub__', a) - - def __rmul__(self, a): - return self._math_('__rmul__', a) - - def __mul__(self, a): - return self._math_('__mul__', a) - - def __rdiv__(self, a): - return self._math_('__rdiv__', a) - - def __div__(self, a): - return self._math_('__div__', a) - - def __truediv__(self, a): - return self._math_('__truediv__', a) - - def __rtruediv__(self, a): - return self._math_('__rtruediv__', a) - - def __rpow__(self, a): - return self._math_('__rpow__', a) - - def __pow__(self, a): - return self._math_('__pow__', a) - - def _math_(self, op, x): - #print "point math:", op - #try: - #fn = getattr(QtCore.QPointF, op) - #pt = fn(self, x) - #print fn, pt, self, x - #return Point(pt) - #except AttributeError: - x = Point(x) - return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) - - def length(self): - """Returns the vector length of this Point.""" - try: - return (self[0]**2 + self[1]**2) ** 0.5 - except OverflowError: - try: - return self[1] / np.sin(np.arctan2(self[1], self[0])) - except OverflowError: - return np.inf - - def norm(self): - """Returns a vector in the same direction with unit length.""" - return self / self.length() - - def angle(self, a): - """Returns the angle in degrees between this vector and the vector a.""" - n1 = self.length() - n2 = a.length() - if n1 == 0. or n2 == 0.: - return None - ## Probably this should be done with arctan2 instead.. - ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians - c = self.cross(a) - if c > 0: - ang *= -1. - return ang * 180. / np.pi - - def dot(self, a): - """Returns the dot product of a and this Point.""" - a = Point(a) - return self[0]*a[0] + self[1]*a[1] - - def cross(self, a): - a = Point(a) - return self[0]*a[1] - self[1]*a[0] - - def proj(self, b): - """Return the projection of this vector onto the vector b""" - b1 = b / b.length() - return self.dot(b1) * b1 - - def __repr__(self): - return "Point(%f, %f)" % (self[0], self[1]) - - - def min(self): - return min(self[0], self[1]) - - def max(self): - return max(self[0], self[1]) - - def copy(self): - return Point(self) - - def toQPoint(self): - return QtCore.QPoint(int(self[0]), int(self[1])) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py deleted file mode 100644 index 4c2e450..0000000 --- a/pyqtgraph/Qt.py +++ /dev/null @@ -1,432 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module exists to smooth out some of the differences between PySide and PyQt4: - -* Automatically import either PyQt4 or PySide depending on availability -* Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper - you want to use. -* Declare QtCore.Signal, .Slot in PyQt4 -* Declare loadUiType function for Pyside - -""" - -import os, sys, re, time, subprocess, warnings -import enum - -from .python2_3 import asUnicode - -PYSIDE = 'PySide' -PYSIDE2 = 'PySide2' -PYSIDE6 = 'PySide6' -PYQT4 = 'PyQt4' -PYQT5 = 'PyQt5' -PYQT6 = 'PyQt6' - -QT_LIB = os.getenv('PYQTGRAPH_QT_LIB') - -## Automatically determine which Qt package to use (unless specified by -## environment variable). -## This is done by first checking to see whether one of the libraries -## is already imported. If not, then attempt to import in the order -## specified in libOrder. -if QT_LIB is None: - libOrder = [PYQT5, PYSIDE2, PYSIDE6, PYQT6] - - for lib in libOrder: - if lib in sys.modules: - QT_LIB = lib - break - -if QT_LIB is None: - for lib in libOrder: - try: - __import__(lib) - QT_LIB = lib - break - except ImportError: - pass - -if QT_LIB is None: - raise Exception("PyQtGraph requires one of PyQt5, PyQt6, PySide2 or PySide6; none of these packages could be imported.") - - -class FailedImport(object): - """Used to defer ImportErrors until we are sure the module is needed. - """ - def __init__(self, err): - self.err = err - - def __getattr__(self, attr): - raise self.err - - -# Make a loadUiType function like PyQt has - -# Credit: -# http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313 - -class _StringIO(object): - """Alternative to built-in StringIO needed to circumvent unicode/ascii issues""" - def __init__(self): - self.data = [] - - def write(self, data): - self.data.append(data) - - def getvalue(self): - return ''.join(map(asUnicode, self.data)).encode('utf8') - - -def _loadUiType(uiFile): - """ - PySide lacks a "loadUiType" command like PyQt4's, so we have to convert - the ui file to py code in-memory first and then execute it in a - special frame to retrieve the form_class. - - from stackoverflow: http://stackoverflow.com/a/14195313/3781327 - - seems like this might also be a legitimate solution, but I'm not sure - how to make PyQt4 and pyside look the same... - http://stackoverflow.com/a/8717832 - """ - - pyside2uic = None - if QT_LIB == PYSIDE2: - try: - import pyside2uic - except ImportError: - # later versions of pyside2 have dropped pyside2uic; use the uic binary instead. - pyside2uic = None - - if pyside2uic is None: - pyside2version = tuple(map(int, PySide2.__version__.split("."))) - if (5, 14) <= pyside2version < (5, 14, 2, 2): - warnings.warn('For UI compilation, it is recommended to upgrade to PySide >= 5.15') - - # get class names from ui file - import xml.etree.ElementTree as xml - parsed = xml.parse(uiFile) - widget_class = parsed.find('widget').get('class') - form_class = parsed.find('class').text - - # convert ui file to python code - if pyside2uic is None: - uic_executable = QT_LIB.lower() + '-uic' - uipy = subprocess.check_output([uic_executable, uiFile]) - else: - o = _StringIO() - with open(uiFile, 'r') as f: - pyside2uic.compileUi(f, o, indent=0) - uipy = o.getvalue() - - # execute python code - pyc = compile(uipy, '', 'exec') - frame = {} - exec(pyc, frame) - - # fetch the base_class and form class based on their type in the xml from designer - form_class = frame['Ui_%s'%form_class] - base_class = eval('QtGui.%s'%widget_class) - - return form_class, base_class - - -if QT_LIB == PYQT5: - # We're using PyQt5 which has a different structure so we're going to use a shim to - # recreate the Qt4 structure for Qt5 - from PyQt5 import QtGui, QtCore, QtWidgets, sip, uic - - try: - from PyQt5 import QtSvg - except ImportError as err: - QtSvg = FailedImport(err) - try: - from PyQt5 import QtTest - except ImportError as err: - QtTest = FailedImport(err) - - VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR - -elif QT_LIB == PYQT6: - from PyQt6 import QtGui, QtCore, QtWidgets, sip, uic - - try: - from PyQt6 import QtSvg - except ImportError as err: - QtSvg = FailedImport(err) - try: - from PyQt6 import QtOpenGLWidgets - except ImportError as err: - QtOpenGLWidgets = FailedImport(err) - try: - from PyQt6 import QtTest - except ImportError as err: - QtTest = FailedImport(err) - - VERSION_INFO = 'PyQt6 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR - -elif QT_LIB == PYSIDE2: - from PySide2 import QtGui, QtCore, QtWidgets - - try: - from PySide2 import QtSvg - except ImportError as err: - QtSvg = FailedImport(err) - try: - from PySide2 import QtTest - except ImportError as err: - QtTest = FailedImport(err) - - import shiboken2 - isQObjectAlive = shiboken2.isValid - import PySide2 - VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__ - -elif QT_LIB == PYSIDE6: - from PySide6 import QtGui, QtCore, QtWidgets - - try: - from PySide6 import QtSvg - except ImportError as err: - QtSvg = FailedImport(err) - try: - from PySide6 import QtOpenGLWidgets - except ImportError as err: - QtOpenGLWidgets = FailedImport(err) - try: - from PySide6 import QtTest - except ImportError as err: - QtTest = FailedImport(err) - - import shiboken6 - isQObjectAlive = shiboken6.isValid - import PySide6 - VERSION_INFO = 'PySide6 ' + PySide6.__version__ + ' Qt ' + QtCore.__version__ - -else: - raise ValueError("Invalid Qt lib '%s'" % QT_LIB) - - -# common to PyQt5, PyQt6, PySide2 and PySide6 -if QT_LIB in [PYQT5, PYQT6, PYSIDE2, PYSIDE6]: - # We're using Qt5 which has a different structure so we're going to use a shim to - # recreate the Qt4 structure - - if QT_LIB in [PYQT5, PYSIDE2]: - __QGraphicsItem_scale = QtWidgets.QGraphicsItem.scale - - def scale(self, *args): - warnings.warn( - "Deprecated Qt API, will be removed in 0.13.0.", - DeprecationWarning, stacklevel=2 - ) - if args: - sx, sy = args - tr = self.transform() - tr.scale(sx, sy) - self.setTransform(tr) - else: - return __QGraphicsItem_scale(self) - QtWidgets.QGraphicsItem.scale = scale - - def rotate(self, angle): - warnings.warn( - "Deprecated Qt API, will be removed in 0.13.0.", - DeprecationWarning, stacklevel=2 - ) - tr = self.transform() - tr.rotate(angle) - self.setTransform(tr) - QtWidgets.QGraphicsItem.rotate = rotate - - def translate(self, dx, dy): - warnings.warn( - "Deprecated Qt API, will be removed in 0.13.0.", - DeprecationWarning, stacklevel=2 - ) - tr = self.transform() - tr.translate(dx, dy) - self.setTransform(tr) - QtWidgets.QGraphicsItem.translate = translate - - def setMargin(self, i): - warnings.warn( - "Deprecated Qt API, will be removed in 0.13.0.", - DeprecationWarning, stacklevel=2 - ) - self.setContentsMargins(i, i, i, i) - QtWidgets.QGridLayout.setMargin = setMargin - - def setResizeMode(self, *args): - warnings.warn( - "Deprecated Qt API, will be removed in 0.13.0.", - DeprecationWarning, stacklevel=2 - ) - self.setSectionResizeMode(*args) - QtWidgets.QHeaderView.setResizeMode = setResizeMode - - # Import all QtWidgets objects into QtGui - for o in dir(QtWidgets): - if o.startswith('Q'): - setattr(QtGui, o, getattr(QtWidgets,o) ) - - QtGui.QApplication.setGraphicsSystem = None - - -if QT_LIB in [PYQT6, PYSIDE6]: - # We're using Qt6 which has a different structure so we're going to use a shim to - # recreate the Qt5 structure - - if not isinstance(QtOpenGLWidgets, FailedImport): - QtWidgets.QOpenGLWidget = QtOpenGLWidgets.QOpenGLWidget - - -# Common to PySide2 and PySide6 -if QT_LIB in [PYSIDE2, PYSIDE6]: - QtVersion = QtCore.__version__ - loadUiType = _loadUiType - - # PySide does not implement qWait - if not isinstance(QtTest, FailedImport): - if not hasattr(QtTest.QTest, 'qWait'): - @staticmethod - def qWait(msec): - start = time.time() - QtGui.QApplication.processEvents() - while time.time() < start + msec * 0.001: - QtGui.QApplication.processEvents() - QtTest.QTest.qWait = qWait - - -# Common to PyQt5 and PyQt6 -if QT_LIB in [PYQT5, PYQT6]: - QtVersion = QtCore.QT_VERSION_STR - - # PyQt, starting in v5.5, calls qAbort when an exception is raised inside - # a slot. To maintain backward compatibility (and sanity for interactive - # users), we install a global exception hook to override this behavior. - if sys.excepthook == sys.__excepthook__: - sys_excepthook = sys.excepthook - def pyqt_qabort_override(*args, **kwds): - return sys_excepthook(*args, **kwds) - sys.excepthook = pyqt_qabort_override - - def isQObjectAlive(obj): - return not sip.isdeleted(obj) - - loadUiType = uic.loadUiType - - QtCore.Signal = QtCore.pyqtSignal - - -if QT_LIB == PYSIDE6: - # PySide6 6.0 has a missing binding - if not hasattr(QtGui.QGradient, 'setStops'): - def __setStops(self, stops): - for pos, color in stops: - self.setColorAt(pos, color) - QtGui.QGradient.setStops = __setStops - - -if QT_LIB == PYQT6: - # module.Class.EnumClass.Enum -> module.Class.Enum - def promote_enums(module): - class_names = [x for x in dir(module) if x.startswith('Q')] - for class_name in class_names: - klass = getattr(module, class_name) - if not isinstance(klass, sip.wrappertype): - continue - attrib_names = [x for x in dir(klass) if x[0].isupper()] - for attrib_name in attrib_names: - attrib = getattr(klass, attrib_name) - if not isinstance(attrib, enum.EnumMeta): - continue - for e in attrib: - setattr(klass, e.name, e) - - promote_enums(QtCore) - promote_enums(QtGui) - promote_enums(QtWidgets) - - # QKeyEvent::key() returns an int - # so comparison with a Key_* enum will always be False - # here we convert the enum to its int value - keys = ['Up', 'Down', 'Right', 'Left', 'Return', 'Enter', 'Delete', 'Backspace', - 'PageUp', 'PageDown', 'Home', 'End', 'Tab', 'Backtab', 'Escape', 'Space'] - for name in keys: - e = getattr(QtCore.Qt.Key, 'Key_' + name) - setattr(QtCore.Qt, e.name, e.value) - - # shim the old names for QPointF mouse coords - QtGui.QSinglePointEvent.localPos = lambda o : o.position() - QtGui.QSinglePointEvent.windowPos = lambda o : o.scenePosition() - QtGui.QSinglePointEvent.screenPos = lambda o : o.globalPosition() - - QtWidgets.QApplication.exec_ = QtWidgets.QApplication.exec - - # PyQt6 6.0.0 has a bug where it can't handle certain Type values returned - # by the Qt library. - if QtCore.PYQT_VERSION == 0x60000: - def new_method(self, old_method=QtCore.QEvent.type): - try: - typ = old_method(self) - except ValueError: - typ = QtCore.QEvent.Type.None_ - return typ - QtCore.QEvent.type = new_method - del new_method - - -# USE_XXX variables are deprecated -USE_PYSIDE = QT_LIB == PYSIDE -USE_PYQT4 = QT_LIB == PYQT4 -USE_PYQT5 = QT_LIB == PYQT5 - - -## Make sure we have Qt >= 5.12 -versionReq = [5, 12] -m = re.match(r'(\d+)\.(\d+).*', QtVersion) -if m is not None and list(map(int, m.groups())) < versionReq: - print(list(map(int, m.groups()))) - raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion)) - -App = QtWidgets.QApplication -# subclassing QApplication causes segfaults on PySide{2, 6} / Python 3.8.7+ - -QAPP = None -def mkQApp(name=None): - """ - Creates new QApplication or returns current instance if existing. - - ============== ======================================================== - **Arguments:** - name (str) Application name, passed to Qt - ============== ======================================================== - """ - global QAPP - - def onPaletteChange(palette): - color = palette.base().color().name() - app = QtWidgets.QApplication.instance() - app.setProperty('darkMode', color.lower() != "#ffffff") - - QAPP = QtGui.QApplication.instance() - if QAPP is None: - # hidpi handling - qtVersionCompare = tuple(map(int, QtVersion.split("."))) - if qtVersionCompare > (6, 0): - # Qt6 seems to support hidpi without needing to do anything so continue - pass - elif qtVersionCompare > (5, 14): - os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" - QtGui.QApplication.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) - else: # qt 5.12 and 5.13 - QtGui.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - QtGui.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) - QAPP = QtGui.QApplication(sys.argv or ["pyqtgraph"]) - QAPP.paletteChanged.connect(onPaletteChange) - QAPP.paletteChanged.emit(QAPP.palette()) - - if name is not None: - QAPP.setApplicationName(name) - return QAPP diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py deleted file mode 100644 index 35ec062..0000000 --- a/pyqtgraph/SRTTransform.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -from .Point import Point -import numpy as np -import warnings - -class SRTTransform(QtGui.QTransform): - """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate - This transform has no shear; angles are always preserved. - """ - def __init__(self, init=None): - QtGui.QTransform.__init__(self) - self.reset() - - if init is None: - return - elif isinstance(init, dict): - self.restoreState(init) - elif isinstance(init, SRTTransform): - self._state = { - 'pos': Point(init._state['pos']), - 'scale': Point(init._state['scale']), - 'angle': init._state['angle'] - } - self.update() - elif isinstance(init, QtGui.QTransform): - self.setFromQTransform(init) - elif isinstance(init, QtGui.QMatrix4x4): - self.setFromMatrix4x4(init) - else: - raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init))) - - - def getScale(self): - return self._state['scale'] - - def getAngle(self): - warnings.warn( - 'SRTTransform.getAngle() is deprecated, use SRTTransform.getRotation() instead' - 'will be removed in 0.13', - DeprecationWarning, stacklevel=2 - ) - return self.getRotation() - - def getRotation(self): - return self._state['angle'] - - def getTranslation(self): - return self._state['pos'] - - def reset(self): - self._state = { - 'pos': Point(0,0), - 'scale': Point(1,1), - 'angle': 0.0 ## in degrees - } - self.update() - - def setFromQTransform(self, tr): - p1 = Point(tr.map(0., 0.)) - p2 = Point(tr.map(1., 0.)) - p3 = Point(tr.map(0., 1.)) - - dp2 = Point(p2-p1) - dp3 = Point(p3-p1) - - ## detect flipped axes - if dp2.angle(dp3) > 0: - #da = 180 - da = 0 - sy = -1.0 - else: - da = 0 - sy = 1.0 - - self._state = { - 'pos': Point(p1), - 'scale': Point(dp2.length(), dp3.length() * sy), - 'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da - } - self.update() - - def setFromMatrix4x4(self, m): - m = SRTTransform3D(m) - angle, axis = m.getRotation() - if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): - print("angle: %s axis: %s" % (str(angle), str(axis))) - raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") - self._state = { - 'pos': Point(m.getTranslation()), - 'scale': Point(m.getScale()), - 'angle': angle - } - self.update() - - def translate(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - t = Point(*args) - self.setTranslate(self._state['pos']+t) - - def setTranslate(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - self._state['pos'] = Point(*args) - self.update() - - def scale(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - s = Point(*args) - self.setScale(self._state['scale'] * s) - - def setScale(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - self._state['scale'] = Point(*args) - self.update() - - def rotate(self, angle): - """Rotate the transformation by angle (in degrees)""" - self.setRotate(self._state['angle'] + angle) - - def setRotate(self, angle): - """Set the transformation rotation to angle (in degrees)""" - self._state['angle'] = angle - self.update() - - def __truediv__(self, t): - """A / B == B^-1 * A""" - dt = t.inverted()[0] * self - return SRTTransform(dt) - - def __div__(self, t): - return self.__truediv__(t) - - def __mul__(self, t): - return SRTTransform(QtGui.QTransform.__mul__(self, t)) - - def saveState(self): - p = self._state['pos'] - s = self._state['scale'] - #if s[0] == 0: - #raise Exception('Invalid scale: %s' % str(s)) - return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} - - def restoreState(self, state): - self._state['pos'] = Point(state.get('pos', (0,0))) - self._state['scale'] = Point(state.get('scale', (1.,1.))) - self._state['angle'] = state.get('angle', 0) - self.update() - - def update(self): - QtGui.QTransform.reset(self) - ## modifications to the transform are multiplied on the right, so we need to reverse order here. - QtGui.QTransform.translate(self, *self._state['pos']) - QtGui.QTransform.rotate(self, self._state['angle']) - QtGui.QTransform.scale(self, *self._state['scale']) - - def __repr__(self): - return str(self.saveState()) - - def matrix(self): - return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) - - -if __name__ == '__main__': - from . import widgets - import GraphicsView - from .functions import * - app = pg.mkQApp() - win = QtGui.QMainWindow() - win.show() - cw = GraphicsView.GraphicsView() - #cw.enableMouse() - win.setCentralWidget(cw) - s = QtGui.QGraphicsScene() - cw.setScene(s) - win.resize(600,600) - cw.enableMouse() - cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) - - class Item(QtGui.QGraphicsItem): - def __init__(self): - QtGui.QGraphicsItem.__init__(self) - self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) - self.b.setPen(QtGui.QPen(mkPen('y'))) - self.t1 = QtGui.QGraphicsTextItem(self) - self.t1.setHtml('R') - self.t1.translate(20, 20) - self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) - self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) - self.l1.setPen(QtGui.QPen(mkPen('y'))) - self.l2.setPen(QtGui.QPen(mkPen('y'))) - def boundingRect(self): - return QtCore.QRectF() - def paint(self, *args): - pass - - #s.addItem(b) - #s.addItem(t1) - item = Item() - s.addItem(item) - l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) - l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) - l1.setPen(QtGui.QPen(mkPen('r'))) - l2.setPen(QtGui.QPen(mkPen('r'))) - s.addItem(l1) - s.addItem(l2) - - tr1 = SRTTransform() - tr2 = SRTTransform() - tr3 = QtGui.QTransform() - tr3.translate(20, 0) - tr3.rotate(45) - print("QTransform -> Transform:", SRTTransform(tr3)) - - print("tr1:", tr1) - - tr2.translate(20, 0) - tr2.rotate(45) - print("tr2:", tr2) - - dt = tr2/tr1 - print("tr2 / tr1 = ", dt) - - print("tr2 * tr1 = ", tr2*tr1) - - tr4 = SRTTransform() - tr4.scale(-1, 1) - tr4.rotate(30) - print("tr1 * tr4 = ", tr1*tr4) - - w1 = widgets.TestROI((19,19), (22, 22), invertible=True) - #w2 = widgets.TestROI((0,0), (150, 150)) - w1.setZValue(10) - s.addItem(w1) - #s.addItem(w2) - w1Base = w1.getState() - #w2Base = w2.getState() - def update(): - tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - item.setTransform(tr1) - - #def update2(): - #tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - #t1.setTransform(tr1) - #w1.setState(w1Base) - #w1.applyGlobalTransform(tr2) - - w1.sigRegionChanged.connect(update) - #w2.sigRegionChanged.connect(update2) - -from .SRTTransform3D import SRTTransform3D diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py deleted file mode 100644 index 7d458ed..0000000 --- a/pyqtgraph/SRTTransform3D.py +++ /dev/null @@ -1,315 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -from .Vector import Vector -from .Transform3D import Transform3D -from .Vector import Vector -import numpy as np - -class SRTTransform3D(Transform3D): - """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate - This transform has no shear; angles are always preserved. - """ - def __init__(self, init=None): - Transform3D.__init__(self) - self.reset() - if init is None: - return - if init.__class__ is QtGui.QTransform: - init = SRTTransform(init) - - if isinstance(init, dict): - self.restoreState(init) - elif isinstance(init, SRTTransform3D): - self._state = { - 'pos': Vector(init._state['pos']), - 'scale': Vector(init._state['scale']), - 'angle': init._state['angle'], - 'axis': Vector(init._state['axis']), - } - self.update() - elif isinstance(init, SRTTransform): - self._state = { - 'pos': Vector(init._state['pos']), - 'scale': Vector(init._state['scale']), - 'angle': init._state['angle'], - 'axis': Vector(0, 0, 1), - } - self._state['scale'][2] = 1.0 - self.update() - elif isinstance(init, QtGui.QMatrix4x4): - self.setFromMatrix(init) - else: - raise Exception("Cannot build SRTTransform3D from argument type:", type(init)) - - - def getScale(self): - return Vector(self._state['scale']) - - def getRotation(self): - """Return (angle, axis) of rotation""" - return self._state['angle'], Vector(self._state['axis']) - - def getTranslation(self): - return Vector(self._state['pos']) - - def reset(self): - self._state = { - 'pos': Vector(0,0,0), - 'scale': Vector(1,1,1), - 'angle': 0.0, ## in degrees - 'axis': (0, 0, 1) - } - self.update() - - def translate(self, *args): - """Adjust the translation of this transform""" - t = Vector(*args) - self.setTranslate(self._state['pos']+t) - - def setTranslate(self, *args): - """Set the translation of this transform""" - self._state['pos'] = Vector(*args) - self.update() - - def scale(self, *args): - """adjust the scale of this transform""" - ## try to prevent accidentally setting 0 scale on z axis - if len(args) == 1 and hasattr(args[0], '__len__'): - args = args[0] - if len(args) == 2: - args = args + (1,) - - s = Vector(*args) - self.setScale(self._state['scale'] * s) - - def setScale(self, *args): - """Set the scale of this transform""" - if len(args) == 1 and hasattr(args[0], '__len__'): - args = args[0] - if len(args) == 2: - args = args + (1,) - self._state['scale'] = Vector(*args) - self.update() - - def rotate(self, angle, axis=(0,0,1)): - """Adjust the rotation of this transform""" - origAxis = self._state['axis'] - if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]: - self.setRotate(self._state['angle'] + angle) - else: - m = QtGui.QMatrix4x4() - m.translate(*self._state['pos']) - m.rotate(self._state['angle'], *self._state['axis']) - m.rotate(angle, *axis) - m.scale(*self._state['scale']) - self.setFromMatrix(m) - - def setRotate(self, angle, axis=(0,0,1)): - """Set the transformation rotation to angle (in degrees)""" - - self._state['angle'] = angle - self._state['axis'] = Vector(axis) - self.update() - - def setFromMatrix(self, m): - """ - Set this transform based on the elements of *m* - The input matrix must be affine AND have no shear, - otherwise the conversion will most likely fail. - """ - import numpy.linalg - for i in range(4): - self.setRow(i, m.row(i)) - m = self.matrix().reshape(4,4) - ## translation is 4th column - self._state['pos'] = m[:3,3] - - ## scale is vector-length of first three columns - scale = (m[:3,:3]**2).sum(axis=0)**0.5 - ## see whether there is an inversion - z = np.cross(m[0, :3], m[1, :3]) - if np.dot(z, m[2, :3]) < 0: - scale[1] *= -1 ## doesn't really matter which axis we invert - self._state['scale'] = scale - - ## rotation axis is the eigenvector with eigenvalue=1 - r = m[:3, :3] / scale[np.newaxis, :] - try: - evals, evecs = numpy.linalg.eig(r) - except: - print("Rotation matrix: %s" % str(r)) - print("Scale: %s" % str(scale)) - print("Original matrix: %s" % str(m)) - raise - eigIndex = np.argwhere(np.abs(evals-1) < 1e-6) - if len(eigIndex) < 1: - print("eigenvalues: %s" % str(evals)) - print("eigenvectors: %s" % str(evecs)) - print("index: %s, %s" % (str(eigIndex), str(evals-1))) - raise Exception("Could not determine rotation axis.") - axis = evecs[:,eigIndex[0,0]].real - axis /= ((axis**2).sum())**0.5 - self._state['axis'] = axis - - ## trace(r) == 2 cos(angle) + 1, so: - cos = (r.trace()-1)*0.5 ## this only gets us abs(angle) - - ## The off-diagonal values can be used to correct the angle ambiguity, - ## but we need to figure out which element to use: - axisInd = np.argmax(np.abs(axis)) - rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd] - - ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd]; - ## solve for sin(angle) - sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) - - ## finally, we get the complete angle from arctan(sin/cos) - self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi - if self._state['angle'] == 0: - self._state['axis'] = (0,0,1) - - def as2D(self): - """Return a QTransform representing the x,y portion of this transform (if possible)""" - return SRTTransform(self) - - #def __div__(self, t): - #"""A / B == B^-1 * A""" - #dt = t.inverted()[0] * self - #return SRTTransform(dt) - - #def __mul__(self, t): - #return SRTTransform(QtGui.QTransform.__mul__(self, t)) - - def saveState(self): - p = self._state['pos'] - s = self._state['scale'] - ax = self._state['axis'] - #if s[0] == 0: - #raise Exception('Invalid scale: %s' % str(s)) - return { - 'pos': (p[0], p[1], p[2]), - 'scale': (s[0], s[1], s[2]), - 'angle': self._state['angle'], - 'axis': (ax[0], ax[1], ax[2]) - } - - def restoreState(self, state): - self._state['pos'] = Vector(state.get('pos', (0.,0.,0.))) - scale = state.get('scale', (1.,1.,1.)) - scale = tuple(scale) + (1.,) * (3-len(scale)) - self._state['scale'] = Vector(scale) - self._state['angle'] = state.get('angle', 0.) - self._state['axis'] = state.get('axis', (0, 0, 1)) - self.update() - - def update(self): - Transform3D.setToIdentity(self) - ## modifications to the transform are multiplied on the right, so we need to reverse order here. - Transform3D.translate(self, *self._state['pos']) - Transform3D.rotate(self, self._state['angle'], *self._state['axis']) - Transform3D.scale(self, *self._state['scale']) - - def __repr__(self): - return str(self.saveState()) - - def matrix(self, nd=3): - if nd == 3: - return np.array(self.copyDataTo()).reshape(4,4) - elif nd == 2: - m = np.array(self.copyDataTo()).reshape(4,4) - m[2] = m[3] - m[:,2] = m[:,3] - return m[:3,:3] - else: - raise Exception("Argument 'nd' must be 2 or 3") - -if __name__ == '__main__': - from . import widgets - import GraphicsView - from .functions import * - app = pg.mkQApp() - win = QtGui.QMainWindow() - win.show() - cw = GraphicsView.GraphicsView() - #cw.enableMouse() - win.setCentralWidget(cw) - s = QtGui.QGraphicsScene() - cw.setScene(s) - win.resize(600,600) - cw.enableMouse() - cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) - - class Item(QtGui.QGraphicsItem): - def __init__(self): - QtGui.QGraphicsItem.__init__(self) - self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) - self.b.setPen(QtGui.QPen(mkPen('y'))) - self.t1 = QtGui.QGraphicsTextItem(self) - self.t1.setHtml('R') - self.t1.translate(20, 20) - self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) - self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) - self.l1.setPen(QtGui.QPen(mkPen('y'))) - self.l2.setPen(QtGui.QPen(mkPen('y'))) - def boundingRect(self): - return QtCore.QRectF() - def paint(self, *args): - pass - - #s.addItem(b) - #s.addItem(t1) - item = Item() - s.addItem(item) - l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) - l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) - l1.setPen(QtGui.QPen(mkPen('r'))) - l2.setPen(QtGui.QPen(mkPen('r'))) - s.addItem(l1) - s.addItem(l2) - - tr1 = SRTTransform() - tr2 = SRTTransform() - tr3 = QtGui.QTransform() - tr3.translate(20, 0) - tr3.rotate(45) - print("QTransform -> Transform: %s" % str(SRTTransform(tr3))) - - print("tr1: %s" % str(tr1)) - - tr2.translate(20, 0) - tr2.rotate(45) - print("tr2: %s" % str(tr2)) - - dt = tr2/tr1 - print("tr2 / tr1 = %s" % str(dt)) - - print("tr2 * tr1 = %s" % str(tr2*tr1)) - - tr4 = SRTTransform() - tr4.scale(-1, 1) - tr4.rotate(30) - print("tr1 * tr4 = %s" % str(tr1*tr4)) - - w1 = widgets.TestROI((19,19), (22, 22), invertible=True) - #w2 = widgets.TestROI((0,0), (150, 150)) - w1.setZValue(10) - s.addItem(w1) - #s.addItem(w2) - w1Base = w1.getState() - #w2Base = w2.getState() - def update(): - tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - item.setTransform(tr1) - - #def update2(): - #tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - #t1.setTransform(tr1) - #w1.setState(w1Base) - #w1.applyGlobalTransform(tr2) - - w1.sigRegionChanged.connect(update) - #w2.sigRegionChanged.connect(update2) - -from .SRTTransform import SRTTransform diff --git a/pyqtgraph/SignalProxy.py b/pyqtgraph/SignalProxy.py deleted file mode 100644 index 16aa97c..0000000 --- a/pyqtgraph/SignalProxy.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -import weakref - -from .Qt import QtCore -from .ptime import time -from . import ThreadsafeTimer -from .functions import SignalBlock - -__all__ = ['SignalProxy'] - - -class SignalProxy(QtCore.QObject): - """Object which collects rapid-fire signals and condenses them - into a single signal or a rate-limited stream of signals. - Used, for example, to prevent a SpinBox from generating multiple - signals when the mouse wheel is rolled over it. - - Emits sigDelayed after input signals have stopped for a certain period of - time. - """ - - sigDelayed = QtCore.Signal(object) - - def __init__(self, signal, delay=0.3, rateLimit=0, slot=None): - """Initialization arguments: - signal - a bound Signal or pyqtSignal instance - delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) - slot - Optional function to connect sigDelayed to. - rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a - steady rate while they are being received. - """ - - QtCore.QObject.__init__(self) - self.delay = delay - self.rateLimit = rateLimit - self.args = None - self.timer = ThreadsafeTimer.ThreadsafeTimer() - self.timer.timeout.connect(self.flush) - self.lastFlushTime = None - self.signal = signal - self.signal.connect(self.signalReceived) - if slot is not None: - self.blockSignal = False - self.sigDelayed.connect(slot) - self.slot = weakref.ref(slot) - else: - self.blockSignal = True - self.slot = None - - def setDelay(self, delay): - self.delay = delay - - def signalReceived(self, *args): - """Received signal. Cancel previous timer and store args to be - forwarded later.""" - if self.blockSignal: - return - self.args = args - if self.rateLimit == 0: - self.timer.stop() - self.timer.start(int(self.delay * 1000) + 1) - else: - now = time() - if self.lastFlushTime is None: - leakTime = 0 - else: - lastFlush = self.lastFlushTime - leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now) - - self.timer.stop() - self.timer.start(int(min(leakTime, self.delay) * 1000) + 1) - - def flush(self): - """If there is a signal queued up, send it now.""" - if self.args is None or self.blockSignal: - return False - args, self.args = self.args, None - self.timer.stop() - self.lastFlushTime = time() - self.sigDelayed.emit(args) - return True - - def disconnect(self): - self.blockSignal = True - try: - self.signal.disconnect(self.signalReceived) - except: - pass - try: - # XXX: This is a weakref, however segfaulting on PySide and - # Python 2. We come back later - self.sigDelayed.disconnect(self.slot) - except: - pass - finally: - self.slot = None - - def connectSlot(self, slot): - """Connect the `SignalProxy` to an external slot""" - assert self.slot is None, "Slot was already connected!" - self.slot = weakref.ref(slot) - self.sigDelayed.connect(slot) - self.blockSignal = False - - def block(self): - """Return a SignalBlocker that temporarily blocks input signals to - this proxy. - """ - return SignalBlock(self.signal, self.signalReceived) diff --git a/pyqtgraph/ThreadsafeTimer.py b/pyqtgraph/ThreadsafeTimer.py deleted file mode 100644 index 375f378..0000000 --- a/pyqtgraph/ThreadsafeTimer.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui - -class ThreadsafeTimer(QtCore.QObject): - """ - Thread-safe replacement for QTimer. - """ - - timeout = QtCore.Signal() - sigTimerStopRequested = QtCore.Signal() - sigTimerStartRequested = QtCore.Signal(object) - - def __init__(self): - QtCore.QObject.__init__(self) - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.timerFinished) - self.timer.moveToThread(QtCore.QCoreApplication.instance().thread()) - self.moveToThread(QtCore.QCoreApplication.instance().thread()) - self.sigTimerStopRequested.connect(self.stop, QtCore.Qt.QueuedConnection) - self.sigTimerStartRequested.connect(self.start, QtCore.Qt.QueuedConnection) - - - def start(self, timeout): - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - #print "start timer", self, "from gui thread" - self.timer.start(int(timeout)) - else: - #print "start timer", self, "from remote thread" - self.sigTimerStartRequested.emit(timeout) - - def stop(self): - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - #print "stop timer", self, "from gui thread" - self.timer.stop() - else: - #print "stop timer", self, "from remote thread" - self.sigTimerStopRequested.emit() - - def timerFinished(self): - self.timeout.emit() diff --git a/pyqtgraph/Transform3D.py b/pyqtgraph/Transform3D.py deleted file mode 100644 index b5a41bc..0000000 --- a/pyqtgraph/Transform3D.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -from . import functions as fn -from .Vector import Vector -import numpy as np - - -class Transform3D(QtGui.QMatrix4x4): - """ - Extension of QMatrix4x4 with some helpful methods added. - """ - def __init__(self, *args): - if len(args) == 1: - if isinstance(args[0], (list, tuple, np.ndarray)): - args = [x for y in args[0] for x in y] - if len(args) != 16: - raise TypeError("Single argument to Transform3D must have 16 elements.") - elif isinstance(args[0], QtGui.QMatrix4x4): - args = list(args[0].copyDataTo()) - - QtGui.QMatrix4x4.__init__(self, *args) - - def matrix(self, nd=3): - if nd == 3: - return np.array(self.copyDataTo()).reshape(4,4) - elif nd == 2: - m = np.array(self.copyDataTo()).reshape(4,4) - m[2] = m[3] - m[:,2] = m[:,3] - return m[:3,:3] - else: - raise Exception("Argument 'nd' must be 2 or 3") - - def map(self, obj): - """ - Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates - """ - if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3): - if obj.ndim >= 2: - return fn.transformCoordinates(self, obj) - elif obj.ndim == 1: - v = QtGui.QMatrix4x4.map(self, Vector(obj)) - return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]] - elif isinstance(obj, (list, tuple)): - v = QtGui.QMatrix4x4.map(self, Vector(obj)) - return type(obj)([v.x(), v.y(), v.z()])[:len(obj)] - else: - return QtGui.QMatrix4x4.map(self, obj) - - def inverted(self): - inv, b = QtGui.QMatrix4x4.inverted(self) - return Transform3D(inv), b diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py deleted file mode 100644 index a9d28e4..0000000 --- a/pyqtgraph/Vector.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Vector.py - Extension of QVector3D which adds a few missing methods. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from .Qt import QtGui, QtCore, QT_LIB -import numpy as np - -class Vector(QtGui.QVector3D): - """Extension of QVector3D which adds a few helpful methods.""" - - def __init__(self, *args): - """ - Handle additional constructions of a Vector - - ============== ================================================================================================ - **Arguments:** - *args* Could be any of: - - * 3 numerics (x, y, and z) - * 2 numerics (x, y, and `0` assumed for z) - * Either of the previous in a list-like collection - * 1 QSizeF (`0` assumed for z) - * 1 QPointF (`0` assumed for z) - * Any other valid QVector3D init args. - ============== ================================================================================================ - """ - initArgs = args - if len(args) == 1: - if isinstance(args[0], QtCore.QSizeF): - initArgs = (float(args[0].width()), float(args[0].height()), 0) - elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): - initArgs = (float(args[0].x()), float(args[0].y()), 0) - elif hasattr(args[0], '__getitem__') and not isinstance(args[0], QtGui.QVector3D): - vals = list(args[0]) - if len(vals) == 2: - vals.append(0) - if len(vals) != 3: - raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) - initArgs = vals - elif len(args) == 2: - initArgs = (args[0], args[1], 0) - QtGui.QVector3D.__init__(self, *initArgs) - - def __len__(self): - return 3 - - def __add__(self, b): - # workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173 - if QT_LIB == 'PySide' and isinstance(b, QtGui.QVector3D): - b = Vector(b) - return QtGui.QVector3D.__add__(self, b) - - #def __reduce__(self): - #return (Point, (self.x(), self.y())) - - def __getitem__(self, i): - if i == 0: - return self.x() - elif i == 1: - return self.y() - elif i == 2: - return self.z() - else: - raise IndexError("Point has no index %s" % str(i)) - - def __setitem__(self, i, x): - if i == 0: - return self.setX(x) - elif i == 1: - return self.setY(x) - elif i == 2: - return self.setZ(x) - else: - raise IndexError("Point has no index %s" % str(i)) - - def __iter__(self): - yield(self.x()) - yield(self.y()) - yield(self.z()) - - def angle(self, a): - """Returns the angle in degrees between this vector and the vector a.""" - n1 = self.length() - n2 = a.length() - if n1 == 0. or n2 == 0.: - return None - ## Probably this should be done with arctan2 instead.. - ang = np.arccos(np.clip(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians -# c = self.crossProduct(a) -# if c > 0: -# ang *= -1. - return ang * 180. / np.pi - - def __abs__(self): - return Vector(abs(self.x()), abs(self.y()), abs(self.z())) - - \ No newline at end of file diff --git a/pyqtgraph/WidgetGroup.py b/pyqtgraph/WidgetGroup.py deleted file mode 100644 index 6b99f86..0000000 --- a/pyqtgraph/WidgetGroup.py +++ /dev/null @@ -1,283 +0,0 @@ -# -*- coding: utf-8 -*- -""" -WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -This class addresses the problem of having to save and restore the state -of a large group of widgets. -""" - -from .Qt import QtCore, QtGui, QT_LIB -import weakref, inspect -from .python2_3 import asUnicode - - -__all__ = ['WidgetGroup'] - -def splitterState(w): - s = str(w.saveState().toPercentEncoding()) - return s - -def restoreSplitter(w, s): - if type(s) is list: - w.setSizes(s) - elif type(s) is str: - w.restoreState(QtCore.QByteArray.fromPercentEncoding(s.encode())) - else: - print("Can't configure QSplitter using object of type", type(s)) - if w.count() > 0: ## make sure at least one item is not collapsed - for i in w.sizes(): - if i > 0: - return - w.setSizes([50] * w.count()) - -def comboState(w): - ind = w.currentIndex() - data = w.itemData(ind) - #if not data.isValid(): - if data is not None: - try: - if not data.isValid(): - data = None - else: - data = data.toInt()[0] - except AttributeError: - pass - if data is None: - return asUnicode(w.itemText(ind)) - else: - return data - -def setComboState(w, v): - if type(v) is int: - #ind = w.findData(QtCore.QVariant(v)) - ind = w.findData(v) - if ind > -1: - w.setCurrentIndex(ind) - return - w.setCurrentIndex(w.findText(str(v))) - - -class WidgetGroup(QtCore.QObject): - """This class takes a list of widgets and keeps an internal record of their - state that is always up to date. - - Allows reading and writing from groups of widgets simultaneously. - """ - - ## List of widget types that can be handled by WidgetGroup. - ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) - ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just - ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) - ## If the change signal is None, the value of the widget is not cached. - ## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method - ## which returns the tuple. - classes = { - QtGui.QSpinBox: - (lambda w: w.valueChanged, - QtGui.QSpinBox.value, - QtGui.QSpinBox.setValue), - QtGui.QDoubleSpinBox: - (lambda w: w.valueChanged, - QtGui.QDoubleSpinBox.value, - QtGui.QDoubleSpinBox.setValue), - QtGui.QSplitter: - (None, - splitterState, - restoreSplitter, - True), - QtGui.QCheckBox: - (lambda w: w.stateChanged, - QtGui.QCheckBox.isChecked, - QtGui.QCheckBox.setChecked), - QtGui.QComboBox: - (lambda w: w.currentIndexChanged, - comboState, - setComboState), - QtGui.QGroupBox: - (lambda w: w.toggled, - QtGui.QGroupBox.isChecked, - QtGui.QGroupBox.setChecked, - True), - QtGui.QLineEdit: - (lambda w: w.editingFinished, - lambda w: str(w.text()), - QtGui.QLineEdit.setText), - QtGui.QRadioButton: - (lambda w: w.toggled, - QtGui.QRadioButton.isChecked, - QtGui.QRadioButton.setChecked), - QtGui.QSlider: - (lambda w: w.valueChanged, - QtGui.QSlider.value, - QtGui.QSlider.setValue), - } - - sigChanged = QtCore.Signal(str, object) - - - def __init__(self, widgetList=None): - """Initialize WidgetGroup, adding specified widgets into this group. - widgetList can be: - - a list of widget specifications (widget, [name], [scale]) - - a dict of name: widget pairs - - any QObject, and all compatible child widgets will be added recursively. - - The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded - in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) - """ - QtCore.QObject.__init__(self) - self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here - self.scales = weakref.WeakKeyDictionary() - self.cache = {} ## name:value pairs - self.uncachedWidgets = weakref.WeakKeyDictionary() - if isinstance(widgetList, QtCore.QObject): - self.autoAdd(widgetList) - elif isinstance(widgetList, list): - for w in widgetList: - self.addWidget(*w) - elif isinstance(widgetList, dict): - for name, w in widgetList.items(): - self.addWidget(w, name) - elif widgetList is None: - return - else: - raise Exception("Wrong argument type %s" % type(widgetList)) - - def addWidget(self, w, name=None, scale=None): - if not self.acceptsType(w): - raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) - if name is None: - name = str(w.objectName()) - if name == '': - raise Exception("Cannot add widget '%s' without a name." % str(w)) - self.widgetList[w] = name - self.scales[w] = scale - self.readWidget(w) - - if type(w) in WidgetGroup.classes: - signal = WidgetGroup.classes[type(w)][0] - else: - signal = w.widgetGroupInterface()[0] - - if signal is not None: - if inspect.isfunction(signal) or inspect.ismethod(signal): - signal = signal(w) - signal.connect(self.mkChangeCallback(w)) - else: - self.uncachedWidgets[w] = None - - def findWidget(self, name): - for w in self.widgetList: - if self.widgetList[w] == name: - return w - return None - - def interface(self, obj): - t = type(obj) - if t in WidgetGroup.classes: - return WidgetGroup.classes[t] - else: - return obj.widgetGroupInterface() - - def checkForChildren(self, obj): - """Return true if we should automatically search the children of this object for more.""" - iface = self.interface(obj) - return (len(iface) > 3 and iface[3]) - - def autoAdd(self, obj): - ## Find all children of this object and add them if possible. - accepted = self.acceptsType(obj) - if accepted: - #print "%s auto add %s" % (self.objectName(), obj.objectName()) - self.addWidget(obj) - - if not accepted or self.checkForChildren(obj): - for c in obj.children(): - self.autoAdd(c) - - def acceptsType(self, obj): - for c in WidgetGroup.classes: - if isinstance(obj, c): - return True - if hasattr(obj, 'widgetGroupInterface'): - return True - return False - - def setScale(self, widget, scale): - val = self.readWidget(widget) - self.scales[widget] = scale - self.setWidget(widget, val) - - def mkChangeCallback(self, w): - return lambda *args: self.widgetChanged(w, *args) - - def widgetChanged(self, w, *args): - n = self.widgetList[w] - v1 = self.cache[n] - v2 = self.readWidget(w) - if v1 != v2: - self.sigChanged.emit(self.widgetList[w], v2) - - def state(self): - for w in self.uncachedWidgets: - self.readWidget(w) - return self.cache.copy() - - def setState(self, s): - for w in self.widgetList: - n = self.widgetList[w] - if n not in s: - continue - self.setWidget(w, s[n]) - - def readWidget(self, w): - if type(w) in WidgetGroup.classes: - getFunc = WidgetGroup.classes[type(w)][1] - else: - getFunc = w.widgetGroupInterface()[1] - - if getFunc is None: - return None - - ## if the getter function provided in the interface is a bound method, - ## then just call the method directly. Otherwise, pass in the widget as the first arg - ## to the function. - if inspect.ismethod(getFunc) and getFunc.__self__ is not None: - val = getFunc() - else: - val = getFunc(w) - - if self.scales[w] is not None: - val /= self.scales[w] - #if isinstance(val, QtCore.QString): - #val = str(val) - n = self.widgetList[w] - self.cache[n] = val - return val - - def setWidget(self, w, v): - v1 = v - if self.scales[w] is not None: - v *= self.scales[w] - - if type(w) in WidgetGroup.classes: - setFunc = WidgetGroup.classes[type(w)][2] - else: - setFunc = w.widgetGroupInterface()[2] - - ## if the setter function provided in the interface is a bound method, - ## then just call the method directly. Otherwise, pass in the widget as the first arg - ## to the function. - if inspect.ismethod(setFunc) and setFunc.__self__ is not None: - setFunc(v) - else: - setFunc(w, v) - - #name = self.widgetList[w] - #if name in self.cache and (self.cache[name] != v1): - #print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1)) - - - \ No newline at end of file diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py deleted file mode 100644 index 71a8662..0000000 --- a/pyqtgraph/__init__.py +++ /dev/null @@ -1,467 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PyQtGraph - Scientific Graphics and GUI Library for Python -www.pyqtgraph.org -""" - -__version__ = '0.11.1.dev0' - -### import all the goodies and add some helper functions for easy CLI use - -## 'Qt' is a local module; it is intended mainly to cover up the differences -## between PyQt4 and PySide. -from .Qt import QtGui, mkQApp - -## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) -#if QtGui.QApplication.instance() is None: - #app = QtGui.QApplication([]) - -import numpy ## pyqtgraph requires numpy - ## (import here to avoid massive error dump later on if numpy is not available) - -import os, sys - -## check python version -## Allow anything >= 2.7 -if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6): - raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1])) - -## helpers for 2/3 compatibility -from . import python2_3 - -## in general openGL is poorly supported with Qt+GraphicsView. -## we only enable it where the performance benefit is critical. -## Note this only applies to 2D graphics; 3D graphics always use OpenGL. -if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation - useOpenGL = False -elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs - useOpenGL = False - if QtGui.QApplication.instance() is not None: - print('Warning: QApplication was created before pyqtgraph was imported; there may be problems.') -else: - useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. - -CONFIG_OPTIONS = { - 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. - 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox - # foreground/background take any arguments to the 'mkColor' in /pyqtgraph/functions.py - 'foreground': 'd', ## default foreground color for axes, labels, etc. - 'background': 'k', ## default background for GraphicsWidget - 'antialias': False, - 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets - 'useWeave': False, ## Use weave to speed up some operations, if it is available - 'weaveDebug': False, ## Print full error message if weave compile fails - 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide - 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) - 'crashWarning': False, # If True, print warnings about situations that may result in a crash - 'mouseRateLimit': 100, # For ignoring frequent mouse events, max number of mouse move events per second, if <= 0, then it is switched off - 'imageAxisOrder': 'col-major', # For 'row-major', image data is expected in the standard (row, col) order. - # For 'col-major', image data is expected in reversed (col, row) order. - # The default is 'col-major' for backward compatibility, but this may - # change in the future. - 'useCupy': False, # When True, attempt to use cupy ( currently only with ImageItem and related functions ) -} - - -def setConfigOption(opt, value): - if opt not in CONFIG_OPTIONS: - raise KeyError('Unknown configuration option "%s"' % opt) - if opt == 'imageAxisOrder' and value not in ('row-major', 'col-major'): - raise ValueError('imageAxisOrder must be either "row-major" or "col-major"') - CONFIG_OPTIONS[opt] = value - -def setConfigOptions(**opts): - """Set global configuration options. - - Each keyword argument sets one global option. - """ - for k,v in opts.items(): - setConfigOption(k, v) - -def getConfigOption(opt): - """Return the value of a single global configuration option. - """ - return CONFIG_OPTIONS[opt] - - -def systemInfo(): - print("sys.platform: %s" % sys.platform) - print("sys.version: %s" % sys.version) - from .Qt import VERSION_INFO - print("qt bindings: %s" % VERSION_INFO) - - global __version__ - rev = None - if __version__ is None: ## this code was probably checked out from bzr; look up the last-revision file - lastRevFile = os.path.join(os.path.dirname(__file__), '..', '.bzr', 'branch', 'last-revision') - if os.path.exists(lastRevFile): - with open(lastRevFile, 'r') as fd: - rev = fd.read().strip() - - print("pyqtgraph: %s; %s" % (__version__, rev)) - print("config:") - import pprint - pprint.pprint(CONFIG_OPTIONS) - -## Rename orphaned .pyc files. This is *probably* safe :) -## We only do this if __version__ is None, indicating the code was probably pulled -## from the repository. -def renamePyc(startDir): - ### Used to rename orphaned .pyc files - ### When a python file changes its location in the repository, usually the .pyc file - ### is left behind, possibly causing mysterious and difficult to track bugs. - - ### Note that this is no longer necessary for python 3.2; from PEP 3147: - ### "If the py source file is missing, the pyc file inside __pycache__ will be ignored. - ### This eliminates the problem of accidental stale pyc file imports." - - printed = False - startDir = os.path.abspath(startDir) - for path, dirs, files in os.walk(startDir): - if '__pycache__' in path: - continue - for f in files: - fileName = os.path.join(path, f) - base, ext = os.path.splitext(fileName) - py = base + ".py" - if ext == '.pyc' and not os.path.isfile(py): - if not printed: - print("NOTE: Renaming orphaned .pyc files:") - printed = True - n = 1 - while True: - name2 = fileName + ".renamed%d" % n - if not os.path.exists(name2): - break - n += 1 - print(" " + fileName + " ==>") - print(" " + name2) - os.rename(fileName, name2) - -path = os.path.split(__file__)[0] -if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore. - renamePyc(path) - - -## Import almost everything to make it available from a single namespace -## don't import the more complex systems--canvas, parametertree, flowchart, dockarea -## these must be imported separately. -#from . import frozenSupport -#def importModules(path, globals, locals, excludes=()): - #"""Import all modules residing within *path*, return a dict of name: module pairs. - - #Note that *path* MUST be relative to the module doing the import. - #""" - #d = os.path.join(os.path.split(globals['__file__'])[0], path) - #files = set() - #for f in frozenSupport.listdir(d): - #if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']: - #files.add(f) - #elif f[-3:] == '.py' and f != '__init__.py': - #files.add(f[:-3]) - #elif f[-4:] == '.pyc' and f != '__init__.pyc': - #files.add(f[:-4]) - - #mods = {} - #path = path.replace(os.sep, '.') - #for modName in files: - #if modName in excludes: - #continue - #try: - #if len(path) > 0: - #modName = path + '.' + modName - #print( "from .%s import * " % modName) - #mod = __import__(modName, globals, locals, ['*'], 1) - #mods[modName] = mod - #except: - #import traceback - #traceback.print_stack() - #sys.excepthook(*sys.exc_info()) - #print("[Error importing module: %s]" % modName) - - #return mods - -#def importAll(path, globals, locals, excludes=()): - #"""Given a list of modules, import all names from each module into the global namespace.""" - #mods = importModules(path, globals, locals, excludes) - #for mod in mods.values(): - #if hasattr(mod, '__all__'): - #names = mod.__all__ - #else: - #names = [n for n in dir(mod) if n[0] != '_'] - #for k in names: - #if hasattr(mod, k): - #globals[k] = getattr(mod, k) - -# Dynamic imports are disabled. This causes too many problems. -#importAll('graphicsItems', globals(), locals()) -#importAll('widgets', globals(), locals(), - #excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView']) - -from .graphicsItems.VTickGroup import * -from .graphicsItems.GraphicsWidget import * -from .graphicsItems.ScaleBar import * -from .graphicsItems.PlotDataItem import * -from .graphicsItems.GraphItem import * -from .graphicsItems.TextItem import * -from .graphicsItems.GraphicsLayout import * -from .graphicsItems.UIGraphicsItem import * -from .graphicsItems.GraphicsObject import * -from .graphicsItems.PlotItem import * -from .graphicsItems.ROI import * -from .graphicsItems.InfiniteLine import * -from .graphicsItems.HistogramLUTItem import * -from .graphicsItems.GridItem import * -from .graphicsItems.GradientLegend import * -from .graphicsItems.GraphicsItem import * -from .graphicsItems.BarGraphItem import * -from .graphicsItems.ViewBox import * -from .graphicsItems.ArrowItem import * -from .graphicsItems.ImageItem import * -from .graphicsItems.PColorMeshItem import * -from .graphicsItems.AxisItem import * -from .graphicsItems.DateAxisItem import * -from .graphicsItems.LabelItem import * -from .graphicsItems.CurvePoint import * -from .graphicsItems.GraphicsWidgetAnchor import * -from .graphicsItems.PlotCurveItem import * -from .graphicsItems.ButtonItem import * -from .graphicsItems.GradientEditorItem import * -from .graphicsItems.MultiPlotItem import * -from .graphicsItems.ErrorBarItem import * -from .graphicsItems.IsocurveItem import * -from .graphicsItems.LinearRegionItem import * -from .graphicsItems.FillBetweenItem import * -from .graphicsItems.LegendItem import * -from .graphicsItems.ScatterPlotItem import * -from .graphicsItems.ItemGroup import * - -from .widgets.MultiPlotWidget import * -from .widgets.ScatterPlotWidget import * -from .widgets.ColorMapWidget import * -from .widgets.FileDialog import * -from .widgets.ValueLabel import * -from .widgets.HistogramLUTWidget import * -from .widgets.CheckTable import * -from .widgets.BusyCursor import * -from .widgets.PlotWidget import * -from .widgets.ComboBox import * -from .widgets.GradientWidget import * -from .widgets.DataFilterWidget import * -from .widgets.SpinBox import * -from .widgets.JoystickButton import * -from .widgets.GraphicsLayoutWidget import * -from .widgets.TreeWidget import * -from .widgets.PathButton import * -from .widgets.VerticalLabel import * -from .widgets.FeedbackButton import * -from .widgets.ColorButton import * -from .widgets.DataTreeWidget import * -from .widgets.DiffTreeWidget import * -from .widgets.GraphicsView import * -from .widgets.LayoutWidget import * -from .widgets.TableWidget import * -from .widgets.ProgressDialog import * -from .widgets.GroupBox import GroupBox -from .widgets.RemoteGraphicsView import RemoteGraphicsView - -from .imageview import * -from .WidgetGroup import * -from .Point import Point -from .Vector import Vector -from .SRTTransform import SRTTransform -from .Transform3D import Transform3D -from .SRTTransform3D import SRTTransform3D -from .functions import * -from .graphicsWindows import * -from .SignalProxy import * -from .colormap import * -from .ptime import time -from .Qt import isQObjectAlive -from .ThreadsafeTimer import * - - -############################################################## -## PyQt and PySide both are prone to crashing on exit. -## There are two general approaches to dealing with this: -## 1. Install atexit handlers that assist in tearing down to avoid crashes. -## This helps, but is never perfect. -## 2. Terminate the process before python starts tearing down -## This is potentially dangerous - -## Attempts to work around exit crashes: -import atexit -_cleanupCalled = False -def cleanup(): - global _cleanupCalled - if _cleanupCalled: - return - - if not getConfigOption('exitCleanup'): - return - - ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. - - ## Workaround for Qt exit crash: - ## ALL QGraphicsItems must have a scene before they are deleted. - ## This is potentially very expensive, but preferred over crashing. - ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. - app = QtGui.QApplication.instance() - if app is None or not isinstance(app, QtGui.QApplication): - # app was never constructed is already deleted or is an - # QCoreApplication/QGuiApplication and not a full QApplication - return - import gc - s = QtGui.QGraphicsScene() - for o in gc.get_objects(): - try: - if isinstance(o, QtGui.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None: - if getConfigOption('crashWarning'): - sys.stderr.write('Error: graphics item without scene. ' - 'Make sure ViewBox.close() and GraphicsView.close() ' - 'are properly called before app shutdown (%s)\n' % (o,)) - - s.addItem(o) - except (RuntimeError, ReferenceError): ## occurs if a python wrapper no longer has its underlying C++ object - continue - _cleanupCalled = True - -atexit.register(cleanup) - -# Call cleanup when QApplication quits. This is necessary because sometimes -# the QApplication will quit before the atexit callbacks are invoked. -# Note: cannot connect this function until QApplication has been created, so -# instead we have GraphicsView.__init__ call this for us. -_cleanupConnected = False -def _connectCleanup(): - global _cleanupConnected - if _cleanupConnected: - return - QtGui.QApplication.instance().aboutToQuit.connect(cleanup) - _cleanupConnected = True - - -## Optional function for exiting immediately (with some manual teardown) -def exit(): - """ - Causes python to exit without garbage-collecting any objects, and thus avoids - calling object destructor methods. This is a sledgehammer workaround for - a variety of bugs in PyQt and Pyside that cause crashes on exit. - - This function does the following in an attempt to 'safely' terminate - the process: - - * Invoke atexit callbacks - * Close all open file handles - * os._exit() - - Note: there is some potential for causing damage with this function if you - are using objects that _require_ their destructors to be called (for example, - to properly terminate log files, disconnect from devices, etc). Situations - like this are probably quite rare, but use at your own risk. - """ - - ## first disable our own cleanup function; won't be needing it. - setConfigOptions(exitCleanup=False) - - ## invoke atexit callbacks - atexit._run_exitfuncs() - - ## close file handles - if sys.platform == 'darwin': - for fd in range(3, 4096): - if fd in [7]: # trying to close 7 produces an illegal instruction on the Mac. - continue - try: - os.close(fd) - except OSError: - pass - else: - os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. - - os._exit(0) - - -## Convenience functions for command-line use -plots = [] -images = [] -QAPP = None - -def plot(*args, **kargs): - """ - Create and return a :class:`PlotWidget ` - Accepts a *title* argument to set the title of the window. - All other arguments are used to plot data. (see :func:`PlotItem.plot() `) - """ - mkQApp() - pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] - pwArgs = {} - dataArgs = {} - for k in kargs: - if k in pwArgList: - pwArgs[k] = kargs[k] - else: - dataArgs[k] = kargs[k] - windowTitle = pwArgs.pop("title", "PlotWidget") - w = PlotWidget(**pwArgs) - w.setWindowTitle(windowTitle) - if len(args) > 0 or len(dataArgs) > 0: - w.plot(*args, **dataArgs) - plots.append(w) - w.show() - return w - -def image(*args, **kargs): - """ - Create and return an :class:`ImageView ` - Will show 2D or 3D image data. - Accepts a *title* argument to set the title of the window. - All other arguments are used to show data. (see :func:`ImageView.setImage() `) - """ - mkQApp() - w = ImageView() - windowTitle = kargs.pop("title", "ImageView") - w.setWindowTitle(windowTitle) - w.setImage(*args, **kargs) - images.append(w) - w.show() - return w -show = image ## for backward compatibility - - -def dbg(*args, **kwds): - """ - Create a console window and begin watching for exceptions. - - All arguments are passed to :func:`ConsoleWidget.__init__() `. - """ - mkQApp() - from . import console - c = console.ConsoleWidget(*args, **kwds) - c.catchAllExceptions() - c.show() - global consoles - try: - consoles.append(c) - except NameError: - consoles = [c] - return c - - -def stack(*args, **kwds): - """ - Create a console window and show the current stack trace. - - All arguments are passed to :func:`ConsoleWidget.__init__() `. - """ - mkQApp() - from . import console - c = console.ConsoleWidget(*args, **kwds) - c.setStack() - c.show() - global consoles - try: - consoles.append(c) - except NameError: - consoles = [c] - return c diff --git a/pyqtgraph/__pycache__/PlotData.cpython-36.pyc b/pyqtgraph/__pycache__/PlotData.cpython-36.pyc deleted file mode 100644 index d5fac4f..0000000 Binary files a/pyqtgraph/__pycache__/PlotData.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Point.cpython-36.pyc b/pyqtgraph/__pycache__/Point.cpython-36.pyc deleted file mode 100644 index f7cf936..0000000 Binary files a/pyqtgraph/__pycache__/Point.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Point.cpython-37.pyc b/pyqtgraph/__pycache__/Point.cpython-37.pyc deleted file mode 100644 index fcb05c0..0000000 Binary files a/pyqtgraph/__pycache__/Point.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Qt.cpython-36.pyc b/pyqtgraph/__pycache__/Qt.cpython-36.pyc deleted file mode 100644 index e5fc2e2..0000000 Binary files a/pyqtgraph/__pycache__/Qt.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Qt.cpython-37.pyc b/pyqtgraph/__pycache__/Qt.cpython-37.pyc deleted file mode 100644 index cda1cd7..0000000 Binary files a/pyqtgraph/__pycache__/Qt.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SRTTransform.cpython-36.pyc b/pyqtgraph/__pycache__/SRTTransform.cpython-36.pyc deleted file mode 100644 index d5e2ac1..0000000 Binary files a/pyqtgraph/__pycache__/SRTTransform.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SRTTransform.cpython-37.pyc b/pyqtgraph/__pycache__/SRTTransform.cpython-37.pyc deleted file mode 100644 index 6cb3899..0000000 Binary files a/pyqtgraph/__pycache__/SRTTransform.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SRTTransform3D.cpython-36.pyc b/pyqtgraph/__pycache__/SRTTransform3D.cpython-36.pyc deleted file mode 100644 index ccdfdda..0000000 Binary files a/pyqtgraph/__pycache__/SRTTransform3D.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SRTTransform3D.cpython-37.pyc b/pyqtgraph/__pycache__/SRTTransform3D.cpython-37.pyc deleted file mode 100644 index c752d5d..0000000 Binary files a/pyqtgraph/__pycache__/SRTTransform3D.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SignalProxy.cpython-36.pyc b/pyqtgraph/__pycache__/SignalProxy.cpython-36.pyc deleted file mode 100644 index 473fac6..0000000 Binary files a/pyqtgraph/__pycache__/SignalProxy.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/SignalProxy.cpython-37.pyc b/pyqtgraph/__pycache__/SignalProxy.cpython-37.pyc deleted file mode 100644 index 04d64f9..0000000 Binary files a/pyqtgraph/__pycache__/SignalProxy.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-36.pyc b/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-36.pyc deleted file mode 100644 index 24563c9..0000000 Binary files a/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-37.pyc b/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-37.pyc deleted file mode 100644 index b72f71c..0000000 Binary files a/pyqtgraph/__pycache__/ThreadsafeTimer.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Transform3D.cpython-36.pyc b/pyqtgraph/__pycache__/Transform3D.cpython-36.pyc deleted file mode 100644 index 907dcf0..0000000 Binary files a/pyqtgraph/__pycache__/Transform3D.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Transform3D.cpython-37.pyc b/pyqtgraph/__pycache__/Transform3D.cpython-37.pyc deleted file mode 100644 index 89358a2..0000000 Binary files a/pyqtgraph/__pycache__/Transform3D.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Vector.cpython-36.pyc b/pyqtgraph/__pycache__/Vector.cpython-36.pyc deleted file mode 100644 index 71d6b2f..0000000 Binary files a/pyqtgraph/__pycache__/Vector.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/Vector.cpython-37.pyc b/pyqtgraph/__pycache__/Vector.cpython-37.pyc deleted file mode 100644 index 75ffdd5..0000000 Binary files a/pyqtgraph/__pycache__/Vector.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/WidgetGroup.cpython-36.pyc b/pyqtgraph/__pycache__/WidgetGroup.cpython-36.pyc deleted file mode 100644 index d05f066..0000000 Binary files a/pyqtgraph/__pycache__/WidgetGroup.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/WidgetGroup.cpython-37.pyc b/pyqtgraph/__pycache__/WidgetGroup.cpython-37.pyc deleted file mode 100644 index 6cf726b..0000000 Binary files a/pyqtgraph/__pycache__/WidgetGroup.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 77de882..0000000 Binary files a/pyqtgraph/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index f870b22..0000000 Binary files a/pyqtgraph/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/colormap.cpython-36.pyc b/pyqtgraph/__pycache__/colormap.cpython-36.pyc deleted file mode 100644 index 936dd0a..0000000 Binary files a/pyqtgraph/__pycache__/colormap.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/colormap.cpython-37.pyc b/pyqtgraph/__pycache__/colormap.cpython-37.pyc deleted file mode 100644 index a3d60ab..0000000 Binary files a/pyqtgraph/__pycache__/colormap.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/configfile.cpython-36.pyc b/pyqtgraph/__pycache__/configfile.cpython-36.pyc deleted file mode 100644 index 8a81a99..0000000 Binary files a/pyqtgraph/__pycache__/configfile.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/debug.cpython-36.pyc b/pyqtgraph/__pycache__/debug.cpython-36.pyc deleted file mode 100644 index f6dc58e..0000000 Binary files a/pyqtgraph/__pycache__/debug.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/debug.cpython-37.pyc b/pyqtgraph/__pycache__/debug.cpython-37.pyc deleted file mode 100644 index 47e2dc0..0000000 Binary files a/pyqtgraph/__pycache__/debug.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/exceptionHandling.cpython-36.pyc b/pyqtgraph/__pycache__/exceptionHandling.cpython-36.pyc deleted file mode 100644 index f710573..0000000 Binary files a/pyqtgraph/__pycache__/exceptionHandling.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/frozenSupport.cpython-36.pyc b/pyqtgraph/__pycache__/frozenSupport.cpython-36.pyc deleted file mode 100644 index 93b3aa5..0000000 Binary files a/pyqtgraph/__pycache__/frozenSupport.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/functions.cpython-36.pyc b/pyqtgraph/__pycache__/functions.cpython-36.pyc deleted file mode 100644 index e1e730c..0000000 Binary files a/pyqtgraph/__pycache__/functions.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/functions.cpython-37.pyc b/pyqtgraph/__pycache__/functions.cpython-37.pyc deleted file mode 100644 index 9b59702..0000000 Binary files a/pyqtgraph/__pycache__/functions.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/graphicsWindows.cpython-36.pyc b/pyqtgraph/__pycache__/graphicsWindows.cpython-36.pyc deleted file mode 100644 index 458bc83..0000000 Binary files a/pyqtgraph/__pycache__/graphicsWindows.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/graphicsWindows.cpython-37.pyc b/pyqtgraph/__pycache__/graphicsWindows.cpython-37.pyc deleted file mode 100644 index f0478d2..0000000 Binary files a/pyqtgraph/__pycache__/graphicsWindows.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/ordereddict.cpython-36.pyc b/pyqtgraph/__pycache__/ordereddict.cpython-36.pyc deleted file mode 100644 index 43fe609..0000000 Binary files a/pyqtgraph/__pycache__/ordereddict.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/pgcollections.cpython-36.pyc b/pyqtgraph/__pycache__/pgcollections.cpython-36.pyc deleted file mode 100644 index 34f4801..0000000 Binary files a/pyqtgraph/__pycache__/pgcollections.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/ptime.cpython-36.pyc b/pyqtgraph/__pycache__/ptime.cpython-36.pyc deleted file mode 100644 index 3a3f000..0000000 Binary files a/pyqtgraph/__pycache__/ptime.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/ptime.cpython-37.pyc b/pyqtgraph/__pycache__/ptime.cpython-37.pyc deleted file mode 100644 index 8c719eb..0000000 Binary files a/pyqtgraph/__pycache__/ptime.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/python2_3.cpython-36.pyc b/pyqtgraph/__pycache__/python2_3.cpython-36.pyc deleted file mode 100644 index 1e7adc1..0000000 Binary files a/pyqtgraph/__pycache__/python2_3.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/python2_3.cpython-37.pyc b/pyqtgraph/__pycache__/python2_3.cpython-37.pyc deleted file mode 100644 index a280ff8..0000000 Binary files a/pyqtgraph/__pycache__/python2_3.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/reload.cpython-36.pyc b/pyqtgraph/__pycache__/reload.cpython-36.pyc deleted file mode 100644 index 591ccc2..0000000 Binary files a/pyqtgraph/__pycache__/reload.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/reload.cpython-37.pyc b/pyqtgraph/__pycache__/reload.cpython-37.pyc deleted file mode 100644 index a8508af..0000000 Binary files a/pyqtgraph/__pycache__/reload.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/__pycache__/units.cpython-36.pyc b/pyqtgraph/__pycache__/units.cpython-36.pyc deleted file mode 100644 index 605b356..0000000 Binary files a/pyqtgraph/__pycache__/units.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/Canvas.py b/pyqtgraph/canvas/Canvas.py deleted file mode 100644 index 31a10b3..0000000 --- a/pyqtgraph/canvas/Canvas.py +++ /dev/null @@ -1,470 +0,0 @@ -# -*- coding: utf-8 -*- - -from ..Qt import QtGui, QtCore, QT_LIB -from ..graphicsItems.ROI import ROI -from ..graphicsItems.ViewBox import ViewBox -from ..graphicsItems.GridItem import GridItem - -import importlib -ui_template = importlib.import_module( - f'.CanvasTemplate_{QT_LIB.lower()}', package=__package__) - -import numpy as np -from .. import debug -import weakref -import gc -from .CanvasManager import CanvasManager -from .CanvasItem import CanvasItem, GroupCanvasItem - -translate = QtCore.QCoreApplication.translate - -class Canvas(QtGui.QWidget): - - sigSelectionChanged = QtCore.Signal(object, object) - sigItemTransformChanged = QtCore.Signal(object, object) - sigItemTransformChangeFinished = QtCore.Signal(object, object) - - def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): - QtGui.QWidget.__init__(self, parent) - self.ui = ui_template.Ui_Form() - self.ui.setupUi(self) - self.view = ViewBox() - self.ui.view.setCentralItem(self.view) - self.itemList = self.ui.itemList - self.itemList.setSelectionMode(self.itemList.ExtendedSelection) - self.allowTransforms = allowTransforms - self.multiSelectBox = SelectBox() - self.view.addItem(self.multiSelectBox) - self.multiSelectBox.hide() - self.multiSelectBox.setZValue(1e6) - self.ui.mirrorSelectionBtn.hide() - self.ui.reflectSelectionBtn.hide() - self.ui.resetTransformsBtn.hide() - - self.redirect = None ## which canvas to redirect items to - self.items = [] - - self.view.setAspectLocked(True) - - grid = GridItem() - self.grid = CanvasItem(grid, name='Grid', movable=False) - self.addItem(self.grid) - - self.hideBtn = QtGui.QPushButton('>', self) - self.hideBtn.setFixedWidth(20) - self.hideBtn.setFixedHeight(20) - self.ctrlSize = 200 - self.sizeApplied = False - self.hideBtn.clicked.connect(self.hideBtnClicked) - self.ui.splitter.splitterMoved.connect(self.splitterMoved) - - self.ui.itemList.itemChanged.connect(self.treeItemChanged) - self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) - self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) - self.ui.autoRangeBtn.clicked.connect(self.autoRange) - self.ui.redirectCheck.toggled.connect(self.updateRedirect) - self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) - self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) - self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) - self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) - self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked) - self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) - - self.resizeEvent() - if hideCtrl: - self.hideBtnClicked() - - if name is not None: - self.registeredName = CanvasManager.instance().registerCanvas(self, name) - self.ui.redirectCombo.setHostName(self.registeredName) - - self.menu = QtGui.QMenu() - remAct = QtGui.QAction(translate("Context Menu", "Remove item"), self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent - - def splitterMoved(self): - self.resizeEvent() - - def hideBtnClicked(self): - ctrlSize = self.ui.splitter.sizes()[1] - if ctrlSize == 0: - cs = self.ctrlSize - w = self.ui.splitter.size().width() - if cs > w: - cs = w - 20 - self.ui.splitter.setSizes([w-cs, cs]) - self.hideBtn.setText('>') - else: - self.ctrlSize = ctrlSize - self.ui.splitter.setSizes([100, 0]) - self.hideBtn.setText('<') - self.resizeEvent() - - def autoRange(self): - self.view.autoRange() - - def resizeEvent(self, ev=None): - if ev is not None: - super().resizeEvent(ev) - self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) - - if not self.sizeApplied: - self.sizeApplied = True - s = min(self.width(), max(100, min(200, self.width()*0.25))) - s2 = self.width()-s - self.ui.splitter.setSizes([s2, s]) - - def updateRedirect(self, *args): - ### Decide whether/where to redirect items and make it so - cname = str(self.ui.redirectCombo.currentText()) - man = CanvasManager.instance() - if self.ui.redirectCheck.isChecked() and cname != '': - redirect = man.getCanvas(cname) - else: - redirect = None - - if self.redirect is redirect: - return - - self.redirect = redirect - if redirect is None: - self.reclaimItems() - else: - self.redirectItems(redirect) - - def redirectItems(self, canvas): - for i in self.items: - if i is self.grid: - continue - li = i.listItem - parent = li.parent() - if parent is None: - tree = li.treeWidget() - if tree is None: - print("Skipping item", i, i.name) - continue - tree.removeTopLevelItem(li) - else: - parent.removeChild(li) - canvas.addItem(i) - - def reclaimItems(self): - items = self.items - self.items = [self.grid] - items.remove(self.grid) - - for i in items: - i.canvas.removeItem(i) - self.addItem(i) - - def treeItemChanged(self, item, col): - try: - citem = item.canvasItem() - except AttributeError: - return - if item.checkState(0) == QtCore.Qt.Checked: - for i in range(item.childCount()): - item.child(i).setCheckState(0, QtCore.Qt.Checked) - citem.show() - else: - for i in range(item.childCount()): - item.child(i).setCheckState(0, QtCore.Qt.Unchecked) - citem.hide() - - def treeItemSelected(self): - sel = self.selectedItems() - - if len(sel) == 0: - return - - multi = len(sel) > 1 - for i in self.items: - ## updated the selected state of every item - i.selectionChanged(i in sel, multi) - - if len(sel)==1: - self.multiSelectBox.hide() - self.ui.mirrorSelectionBtn.hide() - self.ui.reflectSelectionBtn.hide() - self.ui.resetTransformsBtn.hide() - elif len(sel) > 1: - self.showMultiSelectBox() - - self.sigSelectionChanged.emit(self, sel) - - def selectedItems(self): - """ - Return list of all selected canvasItems - """ - return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None] - - def selectItem(self, item): - li = item.listItem - self.itemList.setCurrentItem(li) - - def showMultiSelectBox(self): - ## Get list of selected canvas items - items = self.selectedItems() - - rect = self.view.itemBoundingRect(items[0].graphicsItem()) - for i in items: - if not i.isMovable(): ## all items in selection must be movable - return - br = self.view.itemBoundingRect(i.graphicsItem()) - rect = rect|br - - self.multiSelectBox.blockSignals(True) - self.multiSelectBox.setPos([rect.x(), rect.y()]) - self.multiSelectBox.setSize(rect.size()) - self.multiSelectBox.setAngle(0) - self.multiSelectBox.blockSignals(False) - - self.multiSelectBox.show() - - self.ui.mirrorSelectionBtn.show() - self.ui.reflectSelectionBtn.show() - self.ui.resetTransformsBtn.show() - - def mirrorSelectionClicked(self): - for ci in self.selectedItems(): - ci.mirrorY() - self.showMultiSelectBox() - - def reflectSelectionClicked(self): - for ci in self.selectedItems(): - ci.mirrorXY() - self.showMultiSelectBox() - - def resetTransformsClicked(self): - for i in self.selectedItems(): - i.resetTransformClicked() - self.showMultiSelectBox() - - def multiSelectBoxChanged(self): - self.multiSelectBoxMoved() - - def multiSelectBoxChangeFinished(self): - for ci in self.selectedItems(): - ci.applyTemporaryTransform() - ci.sigTransformChangeFinished.emit(ci) - - def multiSelectBoxMoved(self): - transform = self.multiSelectBox.getGlobalTransform() - for ci in self.selectedItems(): - ci.setTemporaryTransform(transform) - ci.sigTransformChanged.emit(ci) - - def addGraphicsItem(self, item, **opts): - """Add a new GraphicsItem to the scene at pos. - Common options are name, pos, scale, and z - """ - citem = CanvasItem(item, **opts) - item._canvasItem = citem - self.addItem(citem) - return citem - - def addGroup(self, name, **kargs): - group = GroupCanvasItem(name=name) - self.addItem(group, **kargs) - return group - - def addItem(self, citem): - """ - Add an item to the canvas. - """ - - ## Check for redirections - if self.redirect is not None: - name = self.redirect.addItem(citem) - self.items.append(citem) - return name - - if not self.allowTransforms: - citem.setMovable(False) - - citem.sigTransformChanged.connect(self.itemTransformChanged) - citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) - citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) - - - ## Determine name to use in the item list - name = citem.opts['name'] - if name is None: - name = 'item' - newname = name - - ## If name already exists, append a number to the end - ## NAH. Let items have the same name if they really want. - #c=0 - #while newname in self.items: - #c += 1 - #newname = name + '_%03d' %c - #name = newname - - ## find parent and add item to tree - insertLocation = 0 - #print "Inserting node:", name - - - ## determine parent list item where this item should be inserted - parent = citem.parentItem() - if parent in (None, self.view.childGroup): - parent = self.itemList.invisibleRootItem() - else: - parent = parent.listItem - - ## set Z value above all other siblings if none was specified - siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] - z = citem.zValue() - if z is None: - zvals = [i.zValue() for i in siblings] - if parent is self.itemList.invisibleRootItem(): - if len(zvals) == 0: - z = 0 - else: - z = max(zvals)+10 - else: - if len(zvals) == 0: - z = parent.canvasItem().zValue() - else: - z = max(zvals)+1 - citem.setZValue(z) - - ## determine location to insert item relative to its siblings - for i in range(parent.childCount()): - ch = parent.child(i) - zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here? - if zval < z: - insertLocation = i - break - else: - insertLocation = i+1 - - node = QtGui.QTreeWidgetItem([name]) - flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled - if not isinstance(citem, GroupCanvasItem): - flags = flags & ~QtCore.Qt.ItemIsDropEnabled - node.setFlags(flags) - if citem.opts['visible']: - node.setCheckState(0, QtCore.Qt.Checked) - else: - node.setCheckState(0, QtCore.Qt.Unchecked) - - node.name = name - parent.insertChild(insertLocation, node) - - citem.name = name - citem.listItem = node - node.canvasItem = weakref.ref(citem) - self.items.append(citem) - - ctrl = citem.ctrlWidget() - ctrl.hide() - self.ui.ctrlLayout.addWidget(ctrl) - - ## inform the canvasItem that its parent canvas has changed - citem.setCanvas(self) - - ## Autoscale to fit the first item added (not including the grid). - if len(self.items) == 2: - self.autoRange() - - return citem - - def treeItemMoved(self, item, parent, index): - ##Item moved in tree; update Z values - if parent is self.itemList.invisibleRootItem(): - item.canvasItem().setParentItem(self.view.childGroup) - else: - item.canvasItem().setParentItem(parent.canvasItem()) - siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] - - zvals = [i.zValue() for i in siblings] - zvals.sort(reverse=True) - - for i in range(len(siblings)): - item = siblings[i] - item.setZValue(zvals[i]) - - def itemVisibilityChanged(self, item): - listItem = item.listItem - checked = listItem.checkState(0) == QtCore.Qt.Checked - vis = item.isVisible() - if vis != checked: - if vis: - listItem.setCheckState(0, QtCore.Qt.Checked) - else: - listItem.setCheckState(0, QtCore.Qt.Unchecked) - - def removeItem(self, item): - if isinstance(item, QtGui.QTreeWidgetItem): - item = item.canvasItem() - - if isinstance(item, CanvasItem): - item.setCanvas(None) - listItem = item.listItem - listItem.canvasItem = None - item.listItem = None - self.itemList.removeTopLevelItem(listItem) - self.items.remove(item) - ctrl = item.ctrlWidget() - ctrl.hide() - self.ui.ctrlLayout.removeWidget(ctrl) - ctrl.setParent(None) - else: - if hasattr(item, '_canvasItem'): - self.removeItem(item._canvasItem) - else: - self.view.removeItem(item) - - gc.collect() - - def clear(self): - while len(self.items) > 0: - self.removeItem(self.items[0]) - - def addToScene(self, item): - self.view.addItem(item) - - def removeFromScene(self, item): - self.view.removeItem(item) - - def listItems(self): - """Return a dictionary of name:item pairs""" - return self.items - - def getListItem(self, name): - return self.items[name] - - def itemTransformChanged(self, item): - self.sigItemTransformChanged.emit(self, item) - - def itemTransformChangeFinished(self, item): - self.sigItemTransformChangeFinished.emit(self, item) - - def itemListContextMenuEvent(self, ev): - self.menuItem = self.itemList.itemAt(ev.pos()) - self.menu.popup(ev.globalPos()) - - def removeClicked(self): - for item in self.selectedItems(): - self.removeItem(item) - self.menuItem = None - import gc - gc.collect() - - -class SelectBox(ROI): - def __init__(self, scalable=False): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, [0,0], [1,1]) - center = [0.5, 0.5] - - if scalable: - self.addScaleHandle([1, 1], center, lockAspect=True) - self.addScaleHandle([0, 0], center, lockAspect=True) - self.addRotateHandle([0, 1], center) - self.addRotateHandle([1, 0], center) diff --git a/pyqtgraph/canvas/CanvasItem.py b/pyqtgraph/canvas/CanvasItem.py deleted file mode 100644 index 8861205..0000000 --- a/pyqtgraph/canvas/CanvasItem.py +++ /dev/null @@ -1,485 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from ..Qt import QtGui, QtCore, QtSvg, QT_LIB -from ..graphicsItems.ROI import ROI -from .. import SRTTransform, ItemGroup -import importlib -ui_template = importlib.import_module( - f'.TransformGuiTemplate_{QT_LIB.lower()}', package=__package__) - -from .. import debug - -translate = QtCore.QCoreApplication.translate - -class SelectBox(ROI): - def __init__(self, scalable=False, rotatable=True): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, [0,0], [1,1], invertible=True) - center = [0.5, 0.5] - - if scalable: - self.addScaleHandle([1, 1], center, lockAspect=True) - self.addScaleHandle([0, 0], center, lockAspect=True) - if rotatable: - self.addRotateHandle([0, 1], center) - self.addRotateHandle([1, 0], center) - -class CanvasItem(QtCore.QObject): - - sigResetUserTransform = QtCore.Signal(object) - sigTransformChangeFinished = QtCore.Signal(object) - sigTransformChanged = QtCore.Signal(object) - - """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and - provides a control widget""" - - sigVisibilityChanged = QtCore.Signal(object) - transformCopyBuffer = None - - def __init__(self, item, **opts): - defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'rotatable': True, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, - defOpts.update(opts) - self.opts = defOpts - self.selectedAlone = False ## whether this item is the only one selected - - QtCore.QObject.__init__(self) - self.canvas = None - self._graphicsItem = item - - parent = self.opts['parent'] - if parent is not None: - self._graphicsItem.setParentItem(parent.graphicsItem()) - self._parentItem = parent - else: - self._parentItem = None - - z = self.opts['z'] - if z is not None: - item.setZValue(z) - - self.ctrl = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.layout.setContentsMargins(0,0,0,0) - self.ctrl.setLayout(self.layout) - - self.alphaLabel = QtGui.QLabel(translate("CanvasItem", "Alpha")) - self.alphaSlider = QtGui.QSlider() - self.alphaSlider.setMaximum(1023) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setValue(1023) - self.layout.addWidget(self.alphaLabel, 0, 0) - self.layout.addWidget(self.alphaSlider, 0, 1) - self.resetTransformBtn = QtGui.QPushButton('Reset Transform') - self.copyBtn = QtGui.QPushButton('Copy') - self.pasteBtn = QtGui.QPushButton('Paste') - - self.transformWidget = QtGui.QWidget() - self.transformGui = ui_template.Ui_Form() - self.transformGui.setupUi(self.transformWidget) - self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) - self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) - self.transformGui.reflectImageBtn.clicked.connect(self.mirrorXY) - - self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) - self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) - self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) - self.alphaSlider.valueChanged.connect(self.alphaChanged) - self.alphaSlider.sliderPressed.connect(self.alphaPressed) - self.alphaSlider.sliderReleased.connect(self.alphaReleased) - self.resetTransformBtn.clicked.connect(self.resetTransformClicked) - self.copyBtn.clicked.connect(self.copyClicked) - self.pasteBtn.clicked.connect(self.pasteClicked) - - self.setMovable(self.opts['movable']) ## update gui to reflect this option - - if 'transform' in self.opts: - self.baseTransform = self.opts['transform'] - else: - self.baseTransform = SRTTransform() - if 'pos' in self.opts and self.opts['pos'] is not None: - self.baseTransform.translate(self.opts['pos']) - if 'angle' in self.opts and self.opts['angle'] is not None: - self.baseTransform.rotate(self.opts['angle']) - if 'scale' in self.opts and self.opts['scale'] is not None: - self.baseTransform.scale(self.opts['scale']) - - ## create selection box (only visible when selected) - tr = self.baseTransform.saveState() - if 'scalable' not in opts and tr['scale'] == (1,1): - self.opts['scalable'] = True - - ## every CanvasItem implements its own individual selection box - ## so that subclasses are free to make their own. - self.selectBox = SelectBox(scalable=self.opts['scalable'], rotatable=self.opts['rotatable']) - self.selectBox.hide() - self.selectBox.setZValue(1e6) - self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved - self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) - - ## set up the transformations that will be applied to the item - ## (It is not safe to use item.setTransform, since the item might count on that not changing) - self.itemRotation = QtGui.QGraphicsRotation() - self.itemScale = QtGui.QGraphicsScale() - self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) - - self.tempTransform = SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. - self.userTransform = SRTTransform() ## stores the total transform of the object - self.resetUserTransform() - - - def setMovable(self, m): - self.opts['movable'] = m - - if m: - self.resetTransformBtn.show() - self.copyBtn.show() - self.pasteBtn.show() - else: - self.resetTransformBtn.hide() - self.copyBtn.hide() - self.pasteBtn.hide() - - def setCanvas(self, canvas): - ## Called by canvas whenever the item is added. - ## It is our responsibility to add all graphicsItems to the canvas's scene - ## The canvas will automatically add our graphicsitem, - ## so we just need to take care of the selectbox. - if canvas is self.canvas: - return - - if canvas is None: - self.canvas.removeFromScene(self._graphicsItem) - self.canvas.removeFromScene(self.selectBox) - else: - canvas.addToScene(self._graphicsItem) - canvas.addToScene(self.selectBox) - self.canvas = canvas - - def graphicsItem(self): - """Return the graphicsItem for this canvasItem.""" - return self._graphicsItem - - def parentItem(self): - return self._parentItem - - def setParentItem(self, parent): - self._parentItem = parent - if parent is not None: - if isinstance(parent, CanvasItem): - parent = parent.graphicsItem() - self.graphicsItem().setParentItem(parent) - - #def name(self): - #return self.opts['name'] - - def copyClicked(self): - CanvasItem.transformCopyBuffer = self.saveTransform() - - def pasteClicked(self): - t = CanvasItem.transformCopyBuffer - if t is None: - return - else: - self.restoreTransform(t) - - def mirrorY(self): - if not self.isMovable(): - return - - #flip = self.transformGui.mirrorImageCheck.isChecked() - #tr = self.userTransform.saveState() - - inv = SRTTransform() - inv.scale(-1, 1) - self.userTransform = self.userTransform * inv - self.updateTransform() - self.selectBoxFromUser() - self.sigTransformChangeFinished.emit(self) - #if flip: - #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: - #return - #else: - #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) - #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) - #self.userTransform.setRotate(-tr['angle']) - #self.updateTransform() - #self.selectBoxFromUser() - #return - #elif not flip: - #if tr['scale'][0] > 0 and tr['scale'][1] > 0: - #return - #else: - #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) - #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) - #self.userTransform.setRotate(-tr['angle']) - #self.updateTransform() - #self.selectBoxFromUser() - #return - - def mirrorXY(self): - if not self.isMovable(): - return - self.rotate(180.) - # inv = SRTTransform() - # inv.scale(-1, -1) - # self.userTransform = self.userTransform * inv #flip lr/ud - # s=self.updateTransform() - # self.setTranslate(-2*s['pos'][0], -2*s['pos'][1]) - # self.selectBoxFromUser() - - def hasUserTransform(self): - #print self.userRotate, self.userTranslate - return not self.userTransform.isIdentity() - - def ctrlWidget(self): - return self.ctrl - - def alphaChanged(self, val): - alpha = val / 1023. - self._graphicsItem.setOpacity(alpha) - - def setAlpha(self, alpha): - self.alphaSlider.setValue(int(np.clip(alpha * 1023, 0, 1023))) - - def alpha(self): - return self.alphaSlider.value() / 1023. - - def isMovable(self): - return self.opts['movable'] - - def selectBoxMoved(self): - """The selection box has moved; get its transformation information and pass to the graphics item""" - self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) - self.updateTransform() - - def scale(self, x, y): - self.userTransform.scale(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def rotate(self, ang): - self.userTransform.rotate(ang) - self.selectBoxFromUser() - self.updateTransform() - - def translate(self, x, y): - self.userTransform.translate(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def setTranslate(self, x, y): - self.userTransform.setTranslate(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def setRotate(self, angle): - self.userTransform.setRotate(angle) - self.selectBoxFromUser() - self.updateTransform() - - def setScale(self, x, y): - self.userTransform.setScale(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def setTemporaryTransform(self, transform): - self.tempTransform = transform - self.updateTransform() - - def applyTemporaryTransform(self): - """Collapses tempTransform into UserTransform, resets tempTransform""" - self.userTransform = self.userTransform * self.tempTransform ## order is important! - self.resetTemporaryTransform() - self.selectBoxFromUser() ## update the selection box to match the new userTransform - - def resetTemporaryTransform(self): - self.tempTransform = SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere. - self.updateTransform() - - def transform(self): - return self._graphicsItem.transform() - - def updateTransform(self): - """Regenerate the item position from the base, user, and temp transforms""" - transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important - s = transform.saveState() - self._graphicsItem.setPos(*s['pos']) - - self.itemRotation.setAngle(s['angle']) - self.itemScale.setXScale(s['scale'][0]) - self.itemScale.setYScale(s['scale'][1]) - - self.displayTransform(transform) - return(s) # return the transform state - - def displayTransform(self, transform): - """Updates transform numbers in the ctrl widget.""" - tr = transform.saveState() - - self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) - self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) - self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) - - def resetUserTransform(self): - self.userTransform.reset() - self.updateTransform() - - self.selectBox.blockSignals(True) - self.selectBoxToItem() - self.selectBox.blockSignals(False) - self.sigTransformChanged.emit(self) - self.sigTransformChangeFinished.emit(self) - - def resetTransformClicked(self): - self.resetUserTransform() - self.sigResetUserTransform.emit(self) - - def restoreTransform(self, tr): - try: - self.userTransform = SRTTransform(tr) - self.updateTransform() - - self.selectBoxFromUser() ## move select box to match - self.sigTransformChanged.emit(self) - self.sigTransformChangeFinished.emit(self) - except: - self.userTransform = SRTTransform() - debug.printExc("Failed to load transform:") - - def saveTransform(self): - """Return a dict containing the current user transform""" - return self.userTransform.saveState() - - def selectBoxFromUser(self): - """Move the selection box to match the current userTransform""" - ## user transform - #trans = QtGui.QTransform() - #trans.translate(*self.userTranslate) - #trans.rotate(-self.userRotate) - - #x2, y2 = trans.map(*self.selectBoxBase['pos']) - - self.selectBox.blockSignals(True) - self.selectBox.setState(self.selectBoxBase) - self.selectBox.applyGlobalTransform(self.userTransform) - #self.selectBox.setAngle(self.userRotate) - #self.selectBox.setPos([x2, y2]) - self.selectBox.blockSignals(False) - - def selectBoxToItem(self): - """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" - self.itemRect = self._graphicsItem.boundingRect() - rect = self._graphicsItem.mapRectToParent(self.itemRect) - self.selectBox.blockSignals(True) - self.selectBox.setPos([rect.x(), rect.y()]) - self.selectBox.setSize(rect.size()) - self.selectBox.setAngle(0) - self.selectBoxBase = self.selectBox.getState().copy() - self.selectBox.blockSignals(False) - - def zValue(self): - return self.opts['z'] - - def setZValue(self, z): - self.opts['z'] = z - if z is not None: - self._graphicsItem.setZValue(z) - - def selectionChanged(self, sel, multi): - """ - Inform the item that its selection state has changed. - ============== ========================================================= - **Arguments:** - sel (bool) whether the item is currently selected - multi (bool) whether there are multiple items currently - selected - ============== ========================================================= - """ - self.selectedAlone = sel and not multi - self.showSelectBox() - if self.selectedAlone: - self.ctrlWidget().show() - else: - self.ctrlWidget().hide() - - def showSelectBox(self): - """Display the selection box around this item if it is selected and movable""" - if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: - self.selectBox.show() - else: - self.selectBox.hide() - - def hideSelectBox(self): - self.selectBox.hide() - - def selectBoxChanged(self): - self.selectBoxMoved() - self.sigTransformChanged.emit(self) - - def selectBoxChangeFinished(self): - self.sigTransformChangeFinished.emit(self) - - def alphaPressed(self): - """Hide selection box while slider is moving""" - self.hideSelectBox() - - def alphaReleased(self): - self.showSelectBox() - - def show(self): - if self.opts['visible']: - return - self.opts['visible'] = True - self._graphicsItem.show() - self.showSelectBox() - self.sigVisibilityChanged.emit(self) - - def hide(self): - if not self.opts['visible']: - return - self.opts['visible'] = False - self._graphicsItem.hide() - self.hideSelectBox() - self.sigVisibilityChanged.emit(self) - - def setVisible(self, vis): - if vis: - self.show() - else: - self.hide() - - def isVisible(self): - return self.opts['visible'] - - def saveState(self): - return { - 'type': self.__class__.__name__, - 'name': self.name, - 'visible': self.isVisible(), - 'alpha': self.alpha(), - 'userTransform': self.saveTransform(), - 'z': self.zValue(), - 'scalable': self.opts['scalable'], - 'rotatable': self.opts['rotatable'], - 'movable': self.opts['movable'], - } - - def restoreState(self, state): - self.setVisible(state['visible']) - self.setAlpha(state['alpha']) - self.restoreTransform(state['userTransform']) - self.setZValue(state['z']) - - -class GroupCanvasItem(CanvasItem): - """ - Canvas item used for grouping others - """ - - def __init__(self, **opts): - defOpts = {'movable': False, 'scalable': False} - defOpts.update(opts) - item = ItemGroup() - CanvasItem.__init__(self, item, **defOpts) - diff --git a/pyqtgraph/canvas/CanvasManager.py b/pyqtgraph/canvas/CanvasManager.py deleted file mode 100644 index 2818803..0000000 --- a/pyqtgraph/canvas/CanvasManager.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui -if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal -import weakref - -class CanvasManager(QtCore.QObject): - SINGLETON = None - - sigCanvasListChanged = QtCore.Signal() - - def __init__(self): - if CanvasManager.SINGLETON is not None: - raise Exception("Can only create one canvas manager.") - CanvasManager.SINGLETON = self - QtCore.QObject.__init__(self) - self.canvases = weakref.WeakValueDictionary() - - @classmethod - def instance(cls): - return CanvasManager.SINGLETON - - def registerCanvas(self, canvas, name): - n2 = name - i = 0 - while n2 in self.canvases: - n2 = "%s_%03d" % (name, i) - i += 1 - self.canvases[n2] = canvas - self.sigCanvasListChanged.emit() - return n2 - - def unregisterCanvas(self, name): - c = self.canvases[name] - del self.canvases[name] - self.sigCanvasListChanged.emit() - - def listCanvases(self): - return list(self.canvases.keys()) - - def getCanvas(self, name): - return self.canvases[name] - - -manager = CanvasManager() - - -class CanvasCombo(QtGui.QComboBox): - def __init__(self, parent=None): - QtGui.QComboBox.__init__(self, parent) - man = CanvasManager.instance() - man.sigCanvasListChanged.connect(self.updateCanvasList) - self.hostName = None - self.updateCanvasList() - - def updateCanvasList(self): - canvases = CanvasManager.instance().listCanvases() - canvases.insert(0, "") - if self.hostName in canvases: - canvases.remove(self.hostName) - - sel = self.currentText() - if sel in canvases: - self.blockSignals(True) ## change does not affect current selection; block signals during update - self.clear() - for i in canvases: - self.addItem(i) - if i == sel: - self.setCurrentIndex(self.count()) - - self.blockSignals(False) - - def setHostName(self, name): - self.hostName = name - self.updateCanvasList() - diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt5.py b/pyqtgraph/canvas/CanvasTemplate_pyqt5.py deleted file mode 100644 index 83afc77..0000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyqt5.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'CanvasTemplate.ui' -# -# Created by: PyQt5 UI code generator 5.7.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(821, 578) - self.gridLayout_2 = QtWidgets.QGridLayout(Form) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.view = GraphicsView(self.splitter) - self.view.setObjectName("view") - self.vsplitter = QtWidgets.QSplitter(self.splitter) - self.vsplitter.setOrientation(QtCore.Qt.Vertical) - self.vsplitter.setObjectName("vsplitter") - self.canvasCtrlWidget = QtWidgets.QWidget(self.vsplitter) - self.canvasCtrlWidget.setObjectName("canvasCtrlWidget") - self.gridLayout = QtWidgets.QGridLayout(self.canvasCtrlWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.autoRangeBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - self.autoRangeBtn.setObjectName("autoRangeBtn") - self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.redirectCheck = QtWidgets.QCheckBox(self.canvasCtrlWidget) - self.redirectCheck.setObjectName("redirectCheck") - self.horizontalLayout.addWidget(self.redirectCheck) - self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) - self.redirectCombo.setObjectName("redirectCombo") - self.horizontalLayout.addWidget(self.redirectCombo) - self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) - self.itemList = TreeWidget(self.canvasCtrlWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy) - self.itemList.setHeaderHidden(True) - self.itemList.setObjectName("itemList") - self.itemList.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) - self.resetTransformsBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.resetTransformsBtn.setObjectName("resetTransformsBtn") - self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) - self.mirrorSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") - self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) - self.reflectSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") - self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) - self.canvasItemCtrl = QtWidgets.QWidget(self.vsplitter) - self.canvasItemCtrl.setObjectName("canvasItemCtrl") - self.ctrlLayout = QtWidgets.QGridLayout(self.canvasItemCtrl) - self.ctrlLayout.setContentsMargins(0, 0, 0, 0) - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setObjectName("ctrlLayout") - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.autoRangeBtn.setText(_translate("Form", "Auto Range")) - self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) - self.redirectCheck.setText(_translate("Form", "Redirect")) - self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms")) - self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection")) - self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY")) - -from ..widgets.GraphicsView import GraphicsView -from ..widgets.TreeWidget import TreeWidget -from .CanvasManager import CanvasCombo diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt6.py b/pyqtgraph/canvas/CanvasTemplate_pyqt6.py deleted file mode 100644 index 7955793..0000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyqt6.py +++ /dev/null @@ -1,92 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\canvas\CanvasTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(821, 578) - self.gridLayout_2 = QtWidgets.QGridLayout(Form) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Orientations.Horizontal) - self.splitter.setObjectName("splitter") - self.view = GraphicsView(self.splitter) - self.view.setObjectName("view") - self.vsplitter = QtWidgets.QSplitter(self.splitter) - self.vsplitter.setOrientation(QtCore.Qt.Orientations.Vertical) - self.vsplitter.setObjectName("vsplitter") - self.canvasCtrlWidget = QtWidgets.QWidget(self.vsplitter) - self.canvasCtrlWidget.setObjectName("canvasCtrlWidget") - self.gridLayout = QtWidgets.QGridLayout(self.canvasCtrlWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.autoRangeBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - self.autoRangeBtn.setObjectName("autoRangeBtn") - self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.redirectCheck = QtWidgets.QCheckBox(self.canvasCtrlWidget) - self.redirectCheck.setObjectName("redirectCheck") - self.horizontalLayout.addWidget(self.redirectCheck) - self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) - self.redirectCombo.setObjectName("redirectCombo") - self.horizontalLayout.addWidget(self.redirectCombo) - self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) - self.itemList = TreeWidget(self.canvasCtrlWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy) - self.itemList.setHeaderHidden(True) - self.itemList.setObjectName("itemList") - self.itemList.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) - self.resetTransformsBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.resetTransformsBtn.setObjectName("resetTransformsBtn") - self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) - self.mirrorSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") - self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) - self.reflectSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) - self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") - self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) - self.canvasItemCtrl = QtWidgets.QWidget(self.vsplitter) - self.canvasItemCtrl.setObjectName("canvasItemCtrl") - self.ctrlLayout = QtWidgets.QGridLayout(self.canvasItemCtrl) - self.ctrlLayout.setContentsMargins(0, 0, 0, 0) - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setObjectName("ctrlLayout") - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.autoRangeBtn.setText(_translate("Form", "Auto Range")) - self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) - self.redirectCheck.setText(_translate("Form", "Redirect")) - self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms")) - self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection")) - self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY")) -from ..widgets.GraphicsView import GraphicsView -from ..widgets.TreeWidget import TreeWidget -from .CanvasManager import CanvasCombo diff --git a/pyqtgraph/canvas/CanvasTemplate_pyside2.py b/pyqtgraph/canvas/CanvasTemplate_pyside2.py deleted file mode 100644 index de9c632..0000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyside2.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'CanvasTemplate.ui' -# -# Created: Sun Sep 18 19:18:22 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(490, 414) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.view = GraphicsView(self.splitter) - self.view.setObjectName("view") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.autoRangeBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - self.autoRangeBtn.setObjectName("autoRangeBtn") - self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.redirectCheck = QtWidgets.QCheckBox(self.layoutWidget) - self.redirectCheck.setObjectName("redirectCheck") - self.horizontalLayout.addWidget(self.redirectCheck) - self.redirectCombo = CanvasCombo(self.layoutWidget) - self.redirectCombo.setObjectName("redirectCombo") - self.horizontalLayout.addWidget(self.redirectCombo) - self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2) - self.itemList = TreeWidget(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy) - self.itemList.setHeaderHidden(True) - self.itemList.setObjectName("itemList") - self.itemList.headerItem().setText(0, "1") - self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2) - self.ctrlLayout = QtWidgets.QGridLayout() - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setObjectName("ctrlLayout") - self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2) - self.resetTransformsBtn = QtWidgets.QPushButton(self.layoutWidget) - self.resetTransformsBtn.setObjectName("resetTransformsBtn") - self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1) - self.mirrorSelectionBtn = QtWidgets.QPushButton(self.layoutWidget) - self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") - self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1) - self.reflectSelectionBtn = QtWidgets.QPushButton(self.layoutWidget) - self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") - self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) - self.autoRangeBtn.setText(QtWidgets.QApplication.translate("Form", "Auto Range", None, -1)) - self.redirectCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, -1)) - self.redirectCheck.setText(QtWidgets.QApplication.translate("Form", "Redirect", None, -1)) - self.resetTransformsBtn.setText(QtWidgets.QApplication.translate("Form", "Reset Transforms", None, -1)) - self.mirrorSelectionBtn.setText(QtWidgets.QApplication.translate("Form", "Mirror Selection", None, -1)) - self.reflectSelectionBtn.setText(QtWidgets.QApplication.translate("Form", "MirrorXY", None, -1)) - -from ..widgets.TreeWidget import TreeWidget -from CanvasManager import CanvasCombo -from ..widgets.GraphicsView import GraphicsView diff --git a/pyqtgraph/canvas/CanvasTemplate_pyside6.py b/pyqtgraph/canvas/CanvasTemplate_pyside6.py deleted file mode 100644 index a872510..0000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyside6.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'CanvasTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from ..widgets.TreeWidget import TreeWidget -from ..widgets.GraphicsView import GraphicsView -from .CanvasManager import CanvasCombo - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(821, 578) - self.gridLayout_2 = QGridLayout(Form) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.splitter = QSplitter(Form) - self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Horizontal) - self.view = GraphicsView(self.splitter) - self.view.setObjectName(u"view") - self.splitter.addWidget(self.view) - self.vsplitter = QSplitter(self.splitter) - self.vsplitter.setObjectName(u"vsplitter") - self.vsplitter.setOrientation(Qt.Vertical) - self.canvasCtrlWidget = QWidget(self.vsplitter) - self.canvasCtrlWidget.setObjectName(u"canvasCtrlWidget") - self.gridLayout = QGridLayout(self.canvasCtrlWidget) - self.gridLayout.setObjectName(u"gridLayout") - self.autoRangeBtn = QPushButton(self.canvasCtrlWidget) - self.autoRangeBtn.setObjectName(u"autoRangeBtn") - sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - - self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.redirectCheck = QCheckBox(self.canvasCtrlWidget) - self.redirectCheck.setObjectName(u"redirectCheck") - - self.horizontalLayout.addWidget(self.redirectCheck) - - self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) - self.redirectCombo.setObjectName(u"redirectCombo") - - self.horizontalLayout.addWidget(self.redirectCombo) - - - self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) - - self.itemList = TreeWidget(self.canvasCtrlWidget) - __qtreewidgetitem = QTreeWidgetItem() - __qtreewidgetitem.setText(0, u"1"); - self.itemList.setHeaderItem(__qtreewidgetitem) - self.itemList.setObjectName(u"itemList") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(100) - sizePolicy1.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy1) - self.itemList.setHeaderHidden(True) - - self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) - - self.resetTransformsBtn = QPushButton(self.canvasCtrlWidget) - self.resetTransformsBtn.setObjectName(u"resetTransformsBtn") - - self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) - - self.mirrorSelectionBtn = QPushButton(self.canvasCtrlWidget) - self.mirrorSelectionBtn.setObjectName(u"mirrorSelectionBtn") - - self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) - - self.reflectSelectionBtn = QPushButton(self.canvasCtrlWidget) - self.reflectSelectionBtn.setObjectName(u"reflectSelectionBtn") - - self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) - - self.vsplitter.addWidget(self.canvasCtrlWidget) - self.canvasItemCtrl = QWidget(self.vsplitter) - self.canvasItemCtrl.setObjectName(u"canvasItemCtrl") - self.ctrlLayout = QGridLayout(self.canvasItemCtrl) - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setContentsMargins(0, 0, 0, 0) - self.ctrlLayout.setObjectName(u"ctrlLayout") - self.vsplitter.addWidget(self.canvasItemCtrl) - self.splitter.addWidget(self.vsplitter) - - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.autoRangeBtn.setText(QCoreApplication.translate("Form", u"Auto Range", None)) -#if QT_CONFIG(tooltip) - self.redirectCheck.setToolTip(QCoreApplication.translate("Form", u"Check to display all local items in a remote canvas.", None)) -#endif // QT_CONFIG(tooltip) - self.redirectCheck.setText(QCoreApplication.translate("Form", u"Redirect", None)) - self.resetTransformsBtn.setText(QCoreApplication.translate("Form", u"Reset Transforms", None)) - self.mirrorSelectionBtn.setText(QCoreApplication.translate("Form", u"Mirror Selection", None)) - self.reflectSelectionBtn.setText(QCoreApplication.translate("Form", u"MirrorXY", None)) - # retranslateUi - diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py b/pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py deleted file mode 100644 index 2af0499..0000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyqt5.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'pyqtgraph/canvas/TransformGuiTemplate.ui' -# -# Created by: PyQt5 UI code generator 5.5.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(224, 117) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QtWidgets.QVBoxLayout(Form) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setObjectName("verticalLayout") - self.translateLabel = QtWidgets.QLabel(Form) - self.translateLabel.setObjectName("translateLabel") - self.verticalLayout.addWidget(self.translateLabel) - self.rotateLabel = QtWidgets.QLabel(Form) - self.rotateLabel.setObjectName("rotateLabel") - self.verticalLayout.addWidget(self.rotateLabel) - self.scaleLabel = QtWidgets.QLabel(Form) - self.scaleLabel.setObjectName("scaleLabel") - self.verticalLayout.addWidget(self.scaleLabel) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.mirrorImageBtn = QtWidgets.QPushButton(Form) - self.mirrorImageBtn.setToolTip("") - self.mirrorImageBtn.setObjectName("mirrorImageBtn") - self.horizontalLayout.addWidget(self.mirrorImageBtn) - self.reflectImageBtn = QtWidgets.QPushButton(Form) - self.reflectImageBtn.setObjectName("reflectImageBtn") - self.horizontalLayout.addWidget(self.reflectImageBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.translateLabel.setText(_translate("Form", "Translate:")) - self.rotateLabel.setText(_translate("Form", "Rotate:")) - self.scaleLabel.setText(_translate("Form", "Scale:")) - self.mirrorImageBtn.setText(_translate("Form", "Mirror")) - self.reflectImageBtn.setText(_translate("Form", "Reflect")) - diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py b/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py deleted file mode 100644 index 4b0d988..0000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py +++ /dev/null @@ -1,55 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\canvas\TransformGuiTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(224, 117) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QtWidgets.QVBoxLayout(Form) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setObjectName("verticalLayout") - self.translateLabel = QtWidgets.QLabel(Form) - self.translateLabel.setObjectName("translateLabel") - self.verticalLayout.addWidget(self.translateLabel) - self.rotateLabel = QtWidgets.QLabel(Form) - self.rotateLabel.setObjectName("rotateLabel") - self.verticalLayout.addWidget(self.rotateLabel) - self.scaleLabel = QtWidgets.QLabel(Form) - self.scaleLabel.setObjectName("scaleLabel") - self.verticalLayout.addWidget(self.scaleLabel) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.mirrorImageBtn = QtWidgets.QPushButton(Form) - self.mirrorImageBtn.setToolTip("") - self.mirrorImageBtn.setObjectName("mirrorImageBtn") - self.horizontalLayout.addWidget(self.mirrorImageBtn) - self.reflectImageBtn = QtWidgets.QPushButton(Form) - self.reflectImageBtn.setObjectName("reflectImageBtn") - self.horizontalLayout.addWidget(self.reflectImageBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.translateLabel.setText(_translate("Form", "Translate:")) - self.rotateLabel.setText(_translate("Form", "Rotate:")) - self.scaleLabel.setText(_translate("Form", "Scale:")) - self.mirrorImageBtn.setText(_translate("Form", "Mirror")) - self.reflectImageBtn.setText(_translate("Form", "Reflect")) diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyside2.py b/pyqtgraph/canvas/TransformGuiTemplate_pyside2.py deleted file mode 100644 index e05ceb1..0000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyside2.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'TransformGuiTemplate.ui' -# -# Created: Sun Sep 18 19:18:41 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(224, 117) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QtWidgets.QVBoxLayout(Form) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.translateLabel = QtWidgets.QLabel(Form) - self.translateLabel.setObjectName("translateLabel") - self.verticalLayout.addWidget(self.translateLabel) - self.rotateLabel = QtWidgets.QLabel(Form) - self.rotateLabel.setObjectName("rotateLabel") - self.verticalLayout.addWidget(self.rotateLabel) - self.scaleLabel = QtWidgets.QLabel(Form) - self.scaleLabel.setObjectName("scaleLabel") - self.verticalLayout.addWidget(self.scaleLabel) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.mirrorImageBtn = QtWidgets.QPushButton(Form) - self.mirrorImageBtn.setToolTip("") - self.mirrorImageBtn.setObjectName("mirrorImageBtn") - self.horizontalLayout.addWidget(self.mirrorImageBtn) - self.reflectImageBtn = QtWidgets.QPushButton(Form) - self.reflectImageBtn.setObjectName("reflectImageBtn") - self.horizontalLayout.addWidget(self.reflectImageBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) - self.translateLabel.setText(QtWidgets.QApplication.translate("Form", "Translate:", None, -1)) - self.rotateLabel.setText(QtWidgets.QApplication.translate("Form", "Rotate:", None, -1)) - self.scaleLabel.setText(QtWidgets.QApplication.translate("Form", "Scale:", None, -1)) - self.mirrorImageBtn.setText(QtWidgets.QApplication.translate("Form", "Mirror", None, -1)) - self.reflectImageBtn.setText(QtWidgets.QApplication.translate("Form", "Reflect", None, -1)) - diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyside6.py b/pyqtgraph/canvas/TransformGuiTemplate_pyside6.py deleted file mode 100644 index 5ec15bd..0000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyside6.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'TransformGuiTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(224, 117) - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QVBoxLayout(Form) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName(u"verticalLayout") - self.translateLabel = QLabel(Form) - self.translateLabel.setObjectName(u"translateLabel") - - self.verticalLayout.addWidget(self.translateLabel) - - self.rotateLabel = QLabel(Form) - self.rotateLabel.setObjectName(u"rotateLabel") - - self.verticalLayout.addWidget(self.rotateLabel) - - self.scaleLabel = QLabel(Form) - self.scaleLabel.setObjectName(u"scaleLabel") - - self.verticalLayout.addWidget(self.scaleLabel) - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.mirrorImageBtn = QPushButton(Form) - self.mirrorImageBtn.setObjectName(u"mirrorImageBtn") - - self.horizontalLayout.addWidget(self.mirrorImageBtn) - - self.reflectImageBtn = QPushButton(Form) - self.reflectImageBtn.setObjectName(u"reflectImageBtn") - - self.horizontalLayout.addWidget(self.reflectImageBtn) - - - self.verticalLayout.addLayout(self.horizontalLayout) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.translateLabel.setText(QCoreApplication.translate("Form", u"Translate:", None)) - self.rotateLabel.setText(QCoreApplication.translate("Form", u"Rotate:", None)) - self.scaleLabel.setText(QCoreApplication.translate("Form", u"Scale:", None)) -#if QT_CONFIG(tooltip) - self.mirrorImageBtn.setToolTip("") -#endif // QT_CONFIG(tooltip) - self.mirrorImageBtn.setText(QCoreApplication.translate("Form", u"Mirror", None)) - self.reflectImageBtn.setText(QCoreApplication.translate("Form", u"Reflect", None)) - # retranslateUi - diff --git a/pyqtgraph/canvas/__init__.py b/pyqtgraph/canvas/__init__.py deleted file mode 100644 index f649d0a..0000000 --- a/pyqtgraph/canvas/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -from .Canvas import * -from .CanvasItem import * \ No newline at end of file diff --git a/pyqtgraph/canvas/__pycache__/Canvas.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/Canvas.cpython-36.pyc deleted file mode 100644 index 19dcc80..0000000 Binary files a/pyqtgraph/canvas/__pycache__/Canvas.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasItem.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasItem.cpython-36.pyc deleted file mode 100644 index 5cfb876..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasManager.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasManager.cpython-36.pyc deleted file mode 100644 index 4120fc9..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasManager.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index 1b07857..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index ea7c5e9..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside2.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index 449020d..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside6.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index f5f10b9..0000000 Binary files a/pyqtgraph/canvas/__pycache__/CanvasTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index a896441..0000000 Binary files a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index eab28b2..0000000 Binary files a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside2.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index 54ea080..0000000 Binary files a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside6.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index db821de..0000000 Binary files a/pyqtgraph/canvas/__pycache__/TransformGuiTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/canvas/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/canvas/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index d5e39dd..0000000 Binary files a/pyqtgraph/canvas/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py deleted file mode 100644 index 2ca414a..0000000 --- a/pyqtgraph/colormap.py +++ /dev/null @@ -1,449 +0,0 @@ -import numpy as np -from .Qt import QtGui, QtCore -from .python2_3 import basestring -from .functions import mkColor, eq -from os import path, listdir -import collections - -_mapCache = {} - -def listMaps(source=None): - """ - Warning, highly experimental, subject to change. - - List available color maps - =============== ================================================================= - **Arguments:** - source 'matplotlib' lists maps that can be imported from MatPlotLib - 'colorcet' lists maps that can be imported from ColorCET - otherwise local maps are listed - =============== ================================================================= - """ - if source is None: - pathname = path.join(path.dirname(__file__), 'colors','maps') - files = listdir( pathname ) - list_of_maps = [] - for filename in files: - if filename[-4:] == '.csv': - list_of_maps.append(filename[:-4]) - return list_of_maps - elif source.lower() == 'matplotlib': - try: - import matplotlib.pyplot as mpl_plt - list_of_maps = mpl_plt.colormaps() - return list_of_maps - except ModuleNotFoundError: - return [] - elif source.lower() == 'colorcet': - try: - import colorcet - list_of_maps = list( colorcet.palette.keys() ) - list_of_maps.sort() - return list_of_maps - except ModuleNotFoundError: - return [] - return [] - - -def get(name, source=None, skipCache=False): - """ - Warning, highly experimental, subject to change. - - Returns a ColorMap object from a local definition or imported from another library - =============== ================================================================= - **Arguments:** - name Name of color map. Can be a path to a defining file. - source 'matplotlib' imports a map defined by Matplotlib - 'colorcet' imports a maps defined by ColorCET - otherwise local data is used - =============== ================================================================= - """ - if not skipCache and name in _mapCache: - return _mapCache[name] - if source is None: - return _get_from_file(name) - elif source == 'matplotlib': - return _get_from_matplotlib(name) - elif source == 'colorcet': - return _get_from_colorcet(name) - return None - -def _get_from_file(name): - filename = name - if filename[0] !='.': # load from built-in directory - dirname = path.dirname(__file__) - filename = path.join(dirname, 'colors/maps/'+filename) - if not path.isfile( filename ): # try suffixes if file is not found: - if path.isfile( filename+'.csv' ): filename += '.csv' - elif path.isfile( filename+'.txt' ): filename += '.txt' - with open(filename,'r') as fh: - idx = 0 - color_list = [] - if filename[-4:].lower() != '.txt': - csv_mode = True - else: - csv_mode = False - for line in fh: - name = None - line = line.strip() - if len(line) == 0: continue # empty line - if line[0] == ';': continue # comment - parts = line.split(sep=';', maxsplit=1) # split into color and names/comments - if csv_mode: - comp = parts[0].split(',') - if len( comp ) < 3: continue # not enough components given - color_tuple = tuple( [ int(255*float(c)+0.5) for c in comp ] ) - else: - hex_str = parts[0] - if hex_str[0] == '#': - hex_str = hex_str[1:] # strip leading # - if len(hex_str) < 3: continue # not enough information - if len(hex_str) == 3: # parse as abbreviated RGB - hex_str = 2*hex_str[0] + 2*hex_str[1] + 2*hex_str[2] - elif len(hex_str) == 4: # parse as abbreviated RGBA - hex_str = 2*hex_str[0] + 2*hex_str[1] + 2*hex_str[2] + 2*hex_str[3] - if len(hex_str) < 6: continue # not enough information - color_tuple = tuple( bytes.fromhex( hex_str ) ) - color_list.append( color_tuple ) - idx += 1 - # end of line reading loop - # end of open - cm = ColorMap( - pos=np.linspace(0.0, 1.0, len(color_list)), - color=color_list) #, names=color_names) - _mapCache[name] = cm - return cm - -def _get_from_matplotlib(name): - """ import colormap from matplotlib definition """ - # inspired and informed by "mpl_cmaps_in_ImageItem.py", published by Sebastian Hoefer at - # https://github.com/honkomonk/pyqtgraph_sandbox/blob/master/mpl_cmaps_in_ImageItem.py - try: - import matplotlib.pyplot as mpl_plt - except ModuleNotFoundError: - return None - cm = None - col_map = mpl_plt.get_cmap(name) - if hasattr(col_map, '_segmentdata'): # handle LinearSegmentedColormap - data = col_map._segmentdata - if ('red' in data) and isinstance(data['red'], collections.Sequence): - positions = set() # super-set of handle positions in individual channels - for key in ['red','green','blue']: - for tup in data[key]: - positions.add(tup[0]) - col_data = np.zeros((len(positions),4 )) - col_data[:,-1] = sorted(positions) - for idx, key in enumerate(['red','green','blue']): - positions = np.zeros( len(data[key] ) ) - comp_vals = np.zeros( len(data[key] ) ) - for idx2, tup in enumerate( data[key] ): - positions[idx2] = tup[0] - comp_vals[idx2] = tup[1] # these are sorted in the raw data - col_data[:,idx] = np.interp(col_data[:,3], positions, comp_vals) - cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5) - # some color maps (gnuplot in particular) are defined by RGB component functions: - elif ('red' in data) and isinstance(data['red'], collections.Callable): - col_data = np.zeros((64, 4)) - col_data[:,-1] = np.linspace(0., 1., 64) - for idx, key in enumerate(['red','green','blue']): - col_data[:,idx] = np.clip( data[key](col_data[:,-1]), 0, 1) - cm = ColorMap(pos=col_data[:,-1], color=255*col_data[:,:3]+0.5) - elif hasattr(col_map, 'colors'): # handle ListedColormap - col_data = np.array(col_map.colors) - cm = ColorMap(pos=np.linspace(0.0, 1.0, col_data.shape[0]), color=255*col_data[:,:3]+0.5 ) - if cm is not None: - _mapCache[name] = cm - return cm - -def _get_from_colorcet(name): - """ import colormap from colorcet definition """ - try: - import colorcet - except ModuleNotFoundError: - return None - color_strings = colorcet.palette[name] - color_list = [] - for hex_str in color_strings: - if hex_str[0] != '#': continue - if len(hex_str) != 7: - raise ValueError('Invalid color string '+str(hex_str)+' in colorcet import.') - color_tuple = tuple( bytes.fromhex( hex_str[1:] ) ) - color_list.append( color_tuple ) - if len(color_list) == 0: - return None - cm = ColorMap( - pos=np.linspace(0.0, 1.0, len(color_list)), - color=color_list) #, names=color_names) - _mapCache[name] = cm - return cm - - -class ColorMap(object): - """ - A ColorMap defines a relationship between a scalar value and a range of colors. - ColorMaps are commonly used for false-coloring monochromatic images, coloring - scatter-plot points, and coloring surface plots by height. - - Each color map is defined by a set of colors, each corresponding to a - particular scalar value. For example: - - | 0.0 -> black - | 0.2 -> red - | 0.6 -> yellow - | 1.0 -> white - - The colors for intermediate values are determined by interpolating between - the two nearest colors in either RGB or HSV color space. - - To provide user-defined color mappings, see :class:`GradientWidget `. - """ - - ## color interpolation modes - RGB = 1 - HSV_POS = 2 - HSV_NEG = 3 - - ## mapping modes - CLIP = 1 - REPEAT = 2 - MIRROR = 3 - DIVERGING = 4 - - ## return types - BYTE = 1 - FLOAT = 2 - QCOLOR = 3 - - enumMap = { - 'rgb': RGB, - # 'hsv+': HSV_POS, - # 'hsv-': HSV_NEG, - # 'clip': CLIP, - # 'repeat': REPEAT, - 'byte': BYTE, - 'float': FLOAT, - 'qcolor': QCOLOR, - } - - def __init__(self, pos, color, mode=None, mapping=None): #, names=None): - """ - =============== ================================================================= - **Arguments:** - pos Array of positions where each color is defined - color Array of colors. - Values are interpreted via - :func:`mkColor() `. - mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG) - indicating the color space that should be used when - interpolating between stops. Note that the last mode value is - ignored. By default, the mode is entirely RGB. - mapping Mapping mode (ColorMap.CLIP, REPEAT, MIRROR, or DIVERGING) - controlling mapping of relative index to color. - CLIP maps colors to [0.0;1.0] - REPEAT maps colors to repeating intervals [0.0;1.0];[1.0-2.0],... - MIRROR maps colors to [0.0;-1.0] and [0.0;+1.0] identically - DIVERGING maps colors to [-1.0;+1.0] - =============== ================================================================= - """ - self.pos = np.array(pos) - order = np.argsort(self.pos) - self.pos = self.pos[order] - self.color = np.apply_along_axis( - func1d = lambda x: mkColor(x).getRgb(), - axis = -1, - arr = color, - )[order] - if mode is None: - mode = np.ones(len(pos)) - self.mode = mode - - if mapping is None: - self.mapping_mode = self.CLIP - elif mapping == self.REPEAT: - self.mapping_mode = self.REPEAT - elif mapping == self.DIVERGING: - self.mapping_mode = self.DIVERGING - elif mapping == self.MIRROR: - self.mapping_mode = self.MIRROR - else: - self.mapping_mode = self.CLIP - - self.stopsCache = {} - - def __getitem__(self, key): - """ Convenient shorthand access to palette colors """ - if isinstance(key, int): # access by color index - return self.getByIndex(key) - # otherwise access by map - try: # accept any numerical format that converts to float - float_idx = float(key) - return self.mapToQColor(float_idx) - except ValueError: pass - return None - - def map(self, data, mode='byte'): - """ - Return an array of colors corresponding to the values in *data*. - Data must be either a scalar position or an array (any shape) of positions. - - The *mode* argument determines the type of data returned: - - =========== =============================================================== - byte (default) Values are returned as 0-255 unsigned bytes. - float Values are returned as 0.0-1.0 floats. - qcolor Values are returned as an array of QColor objects. - =========== =============================================================== - """ - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - if mode == self.QCOLOR: - pos, color = self.getStops(self.BYTE) - else: - pos, color = self.getStops(mode) - - # Interpolate - # TODO: is griddata faster? - # interp = scipy.interpolate.griddata(pos, color, data) - if np.isscalar(data): - interp = np.empty((color.shape[1],), dtype=color.dtype) - else: - if not isinstance(data, np.ndarray): - data = np.array(data) - interp = np.empty(data.shape + (color.shape[1],), dtype=color.dtype) - - if self.mapping_mode != self.CLIP: - if self.mapping_mode == self.REPEAT: - data = data % 1.0 - elif self.mapping_mode == self.DIVERGING: - data = (data/2)+0.5 - elif self.mapping_mode == self.MIRROR: - data = abs(data) - - for i in range(color.shape[1]): - interp[...,i] = np.interp(data, pos, color[:,i]) - - # Convert to QColor if requested - if mode == self.QCOLOR: - if np.isscalar(data): - return QtGui.QColor(*interp) - else: - return [QtGui.QColor(*x) for x in interp] - else: - return interp - - def mapToQColor(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.QCOLOR) - - def mapToByte(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.BYTE) - - def mapToFloat(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.FLOAT) - - def getByIndex(self, idx): - """Retrieve palette QColor by index""" - return QtGui.QColor( *self.color[idx] ) - - def getGradient(self, p1=None, p2=None): - """Return a QLinearGradient object spanning from QPoints p1 to p2.""" - if p1 == None: - p1 = QtCore.QPointF(0,0) - if p2 == None: - p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0) - g = QtGui.QLinearGradient(p1, p2) - - pos, color = self.getStops(mode=self.BYTE) - color = [QtGui.QColor(*x) for x in color] - g.setStops(list(zip(pos, color))) - return g - - def getColors(self, mode=None): - """Return list of all color stops converted to the specified mode. - If mode is None, then no conversion is done.""" - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - color = self.color - if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f': - color = (color * 255).astype(np.ubyte) - elif mode == self.FLOAT and color.dtype.kind != 'f': - color = color.astype(float) / 255. - - if mode == self.QCOLOR: - color = [QtGui.QColor(*x) for x in color] - - return color - - def getStops(self, mode): - ## Get fully-expanded set of RGBA stops in either float or byte mode. - if mode not in self.stopsCache: - color = self.color - if mode == self.BYTE and color.dtype.kind == 'f': - color = (color * 255).astype(np.ubyte) - elif mode == self.FLOAT and color.dtype.kind != 'f': - color = color.astype(float) / 255. - - ## to support HSV mode, we need to do a little more work.. - self.stopsCache[mode] = (self.pos, color) - return self.stopsCache[mode] - - def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'): - """ - Return an RGB(A) lookup table (ndarray). - - =============== ============================================================================= - **Arguments:** - start The starting value in the lookup table (default=0.0) - stop The final value in the lookup table (default=1.0) - nPts The number of points in the returned lookup table. - alpha True, False, or None - Specifies whether or not alpha values are included - in the table. If alpha is None, it will be automatically determined. - mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'. - See :func:`map() `. - =============== ============================================================================= - """ - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - if alpha is None: - alpha = self.usesAlpha() - - x = np.linspace(start, stop, nPts) - table = self.map(x, mode) - - if not alpha and mode != self.QCOLOR: - return table[:,:3] - else: - return table - - def usesAlpha(self): - """Return True if any stops have an alpha < 255""" - max = 1.0 if self.color.dtype.kind == 'f' else 255 - return np.any(self.color[:,3] != max) - - def isMapTrivial(self): - """ - Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0. - """ - if len(self.pos) != 2: - return False - if self.pos[0] != 0.0 or self.pos[1] != 1.0: - return False - if self.color.dtype.kind == 'f': - return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]])) - else: - return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]])) - - def __repr__(self): - pos = repr(self.pos).replace('\n', '') - color = repr(self.color).replace('\n', '') - return "ColorMap(%s, %s)" % (pos, color) - - def __eq__(self, other): - if other is None: - return False - return eq(self.pos, other.pos) and eq(self.color, other.color) diff --git a/pyqtgraph/configfile.py b/pyqtgraph/configfile.py deleted file mode 100644 index a4ad919..0000000 --- a/pyqtgraph/configfile.py +++ /dev/null @@ -1,216 +0,0 @@ -# -*- coding: utf-8 -*- -""" -configfile.py - Human-readable text configuration file library -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -Used for reading and writing dictionary objects to a python-like configuration -file format. Data structures may be nested and contain any data type as long -as it can be converted to/from a string using repr and eval. -""" - -import re, os, sys, datetime -import numpy -from collections import OrderedDict -from . import units -from .python2_3 import asUnicode, basestring -from .Qt import QtCore -from .Point import Point -from .colormap import ColorMap -GLOBAL_PATH = None # so not thread safe. - - -class ParseError(Exception): - def __init__(self, message, lineNum, line, fileName=None): - self.lineNum = lineNum - self.line = line - self.message = message - self.fileName = fileName - Exception.__init__(self, message) - - def __str__(self): - if self.fileName is None: - msg = "Error parsing string at line %d:\n" % self.lineNum - else: - msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) - msg += "%s\n%s" % (self.line, Exception.__str__(self)) - return msg - - -def writeConfigFile(data, fname): - s = genString(data) - with open(fname, 'w') as fd: - fd.write(s) - - -def readConfigFile(fname): - #cwd = os.getcwd() - global GLOBAL_PATH - if GLOBAL_PATH is not None: - fname2 = os.path.join(GLOBAL_PATH, fname) - if os.path.exists(fname2): - fname = fname2 - - GLOBAL_PATH = os.path.dirname(os.path.abspath(fname)) - - try: - #os.chdir(newDir) ## bad. - with open(fname) as fd: - s = asUnicode(fd.read()) - s = s.replace("\r\n", "\n") - s = s.replace("\r", "\n") - data = parseString(s)[1] - except ParseError: - sys.exc_info()[1].fileName = fname - raise - except: - print("Error while reading config file %s:"% fname) - raise - #finally: - #os.chdir(cwd) - return data - -def appendConfigFile(data, fname): - s = genString(data) - with open(fname, 'a') as fd: - fd.write(s) - - -def genString(data, indent=''): - s = '' - for k in data: - sk = str(k) - if len(sk) == 0: - print(data) - raise Exception('blank dict keys not allowed (see data above)') - if sk[0] == ' ' or ':' in sk: - print(data) - raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) - if isinstance(data[k], dict): - s += indent + sk + ':\n' - s += genString(data[k], indent + ' ') - else: - s += indent + sk + ': ' + repr(data[k]).replace("\n", "\\\n") + '\n' - return s - -def parseString(lines, start=0): - - data = OrderedDict() - if isinstance(lines, basestring): - lines = lines.replace("\\\n", "") - lines = lines.split('\n') - lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines - - indent = measureIndent(lines[start]) - ln = start - 1 - - try: - while True: - ln += 1 - #print ln - if ln >= len(lines): - break - - l = lines[ln] - - ## Skip blank lines or lines starting with # - if re.match(r'\s*#', l) or not re.search(r'\S', l): - continue - - ## Measure line indentation, make sure it is correct for this level - lineInd = measureIndent(l) - if lineInd < indent: - ln -= 1 - break - if lineInd > indent: - #print lineInd, indent - raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) - - - if ':' not in l: - raise ParseError('Missing colon', ln+1, l) - - (k, p, v) = l.partition(':') - k = k.strip() - v = v.strip() - - ## set up local variables to use for eval - local = units.allUnits.copy() - local['OrderedDict'] = OrderedDict - local['readConfigFile'] = readConfigFile - local['Point'] = Point - local['QtCore'] = QtCore - local['ColorMap'] = ColorMap - local['datetime'] = datetime - # Needed for reconstructing numpy arrays - local['array'] = numpy.array - for dtype in ['int8', 'uint8', - 'int16', 'uint16', 'float16', - 'int32', 'uint32', 'float32', - 'int64', 'uint64', 'float64']: - local[dtype] = getattr(numpy, dtype) - - if len(k) < 1: - raise ParseError('Missing name preceding colon', ln+1, l) - if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it. - try: - k1 = eval(k, local) - if type(k1) is tuple: - k = k1 - except: - pass - if re.search(r'\S', v) and v[0] != '#': ## eval the value - try: - val = eval(v, local) - except: - ex = sys.exc_info()[1] - raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) - else: - if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: - #print "blank dict" - val = {} - else: - #print "Going deeper..", ln+1 - (ln, val) = parseString(lines, start=ln+1) - data[k] = val - #print k, repr(val) - except ParseError: - raise - except: - ex = sys.exc_info()[1] - raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) - #print "Returning shallower..", ln+1 - return (ln, data) - -def measureIndent(s): - n = 0 - while n < len(s) and s[n] == ' ': - n += 1 - return n - - - -if __name__ == '__main__': - import tempfile - cf = """ -key: 'value' -key2: ##comment - ##comment - key21: 'value' ## comment - ##comment - key22: [1,2,3] - key23: 234 #comment - """ - fn = tempfile.mktemp() - with open(fn, 'w') as tf: - tf.write(cf) - print("=== Test:===") - num = 1 - for line in cf.split('\n'): - print("%02d %s" % (num, line)) - num += 1 - print(cf) - print("============") - data = readConfigFile(fn) - print(data) - os.remove(fn) diff --git a/pyqtgraph/console/CmdInput.py b/pyqtgraph/console/CmdInput.py deleted file mode 100644 index 1148e28..0000000 --- a/pyqtgraph/console/CmdInput.py +++ /dev/null @@ -1,40 +0,0 @@ -from ..Qt import QtCore, QtGui -from ..python2_3 import asUnicode - -class CmdInput(QtGui.QLineEdit): - - sigExecuteCmd = QtCore.Signal(object) - - def __init__(self, parent): - QtGui.QLineEdit.__init__(self, parent) - self.history = [""] - self.ptr = 0 - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Up: - if self.ptr < len(self.history) - 1: - self.setHistory(self.ptr+1) - ev.accept() - return - elif ev.key() == QtCore.Qt.Key_Down: - if self.ptr > 0: - self.setHistory(self.ptr-1) - ev.accept() - return - elif ev.key() == QtCore.Qt.Key_Return: - self.execCmd() - else: - super().keyPressEvent(ev) - self.history[0] = asUnicode(self.text()) - - def execCmd(self): - cmd = asUnicode(self.text()) - if len(self.history) == 1 or cmd != self.history[1]: - self.history.insert(1, cmd) - self.history[0] = "" - self.setHistory(0) - self.sigExecuteCmd.emit(cmd) - - def setHistory(self, num): - self.ptr = num - self.setText(self.history[self.ptr]) diff --git a/pyqtgraph/console/Console.py b/pyqtgraph/console/Console.py deleted file mode 100644 index 579984c..0000000 --- a/pyqtgraph/console/Console.py +++ /dev/null @@ -1,495 +0,0 @@ -# -*- coding: utf-8 -*- -import sys, re, os, time, traceback, subprocess -import pickle - -from ..Qt import QtCore, QtGui, QT_LIB -from ..python2_3 import basestring -from .. import exceptionHandling as exceptionHandling -from .. import getConfigOption -from ..functions import SignalBlock -import importlib -ui_template = importlib.import_module( - f'.template_{QT_LIB.lower()}', package=__package__) - - -class ConsoleWidget(QtGui.QWidget): - """ - Widget displaying console output and accepting command input. - Implements: - - - eval python expressions / exec python statements - - storable history of commands - - exception handling allowing commands to be interpreted in the context of any level in the exception stack frame - - Why not just use python in an interactive shell (or ipython) ? There are a few reasons: - - - pyside does not yet allow Qt event processing and interactive shell at the same time - - on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can - be baffling and frustrating to users since it would appear the program has frozen. - - some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces - - ability to add extra features like exception stack introspection - - ability to have multiple interactive prompts, including for spawned sub-processes - """ - _threadException = QtCore.Signal(object) - - def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None): - """ - ============== ============================================================================ - **Arguments:** - namespace dictionary containing the initial variables present in the default namespace - historyFile optional file for storing command history - text initial text to display in the console window - editor optional string for invoking code editor (called when stack trace entries are - double-clicked). May contain {fileName} and {lineNum} format keys. Example:: - - editorCommand --loadfile {fileName} --gotoline {lineNum} - ============== ============================================================================= - """ - QtGui.QWidget.__init__(self, parent) - if namespace is None: - namespace = {} - namespace['__console__'] = self - self.localNamespace = namespace - self.editor = editor - self.multiline = None - self.inCmd = False - self.frames = [] # stack frames to access when an item in the stack list is selected - - self.ui = ui_template.Ui_Form() - self.ui.setupUi(self) - self.output = self.ui.output - self.input = self.ui.input - self.input.setFocus() - - if text is not None: - self.output.setPlainText(text) - - self.historyFile = historyFile - - history = self.loadHistory() - if history is not None: - self.input.history = [""] + history - self.ui.historyList.addItems(history[::-1]) - self.ui.historyList.hide() - self.ui.exceptionGroup.hide() - - self.input.sigExecuteCmd.connect(self.runCmd) - self.ui.historyBtn.toggled.connect(self.ui.historyList.setVisible) - self.ui.historyList.itemClicked.connect(self.cmdSelected) - self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked) - self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible) - - self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions) - self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException) - self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked) - self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked) - self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked) - self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace) - - self.currentTraceback = None - - # send exceptions raised in non-gui threads back to the main thread by signal. - self._threadException.connect(self._threadExceptionHandler) - - def loadHistory(self): - """Return the list of previously-invoked command strings (or None).""" - if self.historyFile is not None: - with open(self.historyFile, 'rb') as pf: - return pickle.load(pf) - - def saveHistory(self, history): - """Store the list of previously-invoked command strings.""" - if self.historyFile is not None: - with open(self.historyFile, 'wb') as pf: - pickle.dump(pf, history) - - def runCmd(self, cmd): - #cmd = str(self.input.lastCmd) - - orig_stdout = sys.stdout - orig_stderr = sys.stderr - encCmd = re.sub(r'>', '>', re.sub(r'<', '<', cmd)) - encCmd = re.sub(r' ', ' ', encCmd) - - self.ui.historyList.addItem(cmd) - self.saveHistory(self.input.history[1:100]) - - try: - sys.stdout = self - sys.stderr = self - if self.multiline is not None: - self.write("
%s\n"%encCmd, html=True, scrollToBottom=True) - self.execMulti(cmd) - else: - self.write("
%s\n"%encCmd, html=True, scrollToBottom=True) - self.inCmd = True - self.execSingle(cmd) - - if not self.inCmd: - self.write("
\n", html=True, scrollToBottom=True) - - finally: - sys.stdout = orig_stdout - sys.stderr = orig_stderr - - sb = self.ui.historyList.verticalScrollBar() - sb.setValue(sb.maximum()) - - def globals(self): - frame = self.currentFrame() - if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): - return self.currentFrame().f_globals - else: - return self.localNamespace - - def locals(self): - frame = self.currentFrame() - if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): - return self.currentFrame().f_locals - else: - return self.localNamespace - - def currentFrame(self): - ## Return the currently selected exception stack frame (or None if there is no exception) - index = self.ui.exceptionStackList.currentRow() - if index >= 0 and index < len(self.frames): - return self.frames[index] - else: - return None - - def execSingle(self, cmd): - try: - output = eval(cmd, self.globals(), self.locals()) - self.write(repr(output) + '\n') - return - except SyntaxError: - pass - except: - self.displayException() - return - - # eval failed with syntax error; try exec instead - try: - exec(cmd, self.globals(), self.locals()) - except SyntaxError as exc: - if 'unexpected EOF' in exc.msg: - self.multiline = cmd - else: - self.displayException() - except: - self.displayException() - - def execMulti(self, nextLine): - if nextLine.strip() != '': - self.multiline += "\n" + nextLine - return - else: - cmd = self.multiline - - try: - output = eval(cmd, self.globals(), self.locals()) - self.write(str(output) + '\n') - self.multiline = None - return - except SyntaxError: - pass - except: - self.displayException() - self.multiline = None - return - - # eval failed with syntax error; try exec instead - try: - exec(cmd, self.globals(), self.locals()) - self.multiline = None - except SyntaxError as exc: - if 'unexpected EOF' in exc.msg: - self.multiline = cmd - else: - self.displayException() - self.multiline = None - except: - self.displayException() - self.multiline = None - - - def write(self, strn, html=False, scrollToBottom='auto'): - """Write a string into the console. - - If scrollToBottom is 'auto', then the console is automatically scrolled - to fit the new text only if it was already at the bottom. - """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if not isGuiThread: - sys.__stdout__.write(strn) - return - - sb = self.output.verticalScrollBar() - scroll = sb.value() - if scrollToBottom == 'auto': - atBottom = scroll == sb.maximum() - scrollToBottom = atBottom - - self.output.moveCursor(QtGui.QTextCursor.End) - if html: - self.output.textCursor().insertHtml(strn) - else: - if self.inCmd: - self.inCmd = False - self.output.textCursor().insertHtml("
") - self.output.insertPlainText(strn) - - if scrollToBottom: - sb.setValue(sb.maximum()) - else: - sb.setValue(scroll) - - - def fileno(self): - # Need to implement this since we temporarily occlude sys.stdout, and someone may be looking for it (faulthandler, for example) - return 1 - - def displayException(self): - """ - Display the current exception and stack. - """ - tb = traceback.format_exc() - lines = [] - indent = 4 - prefix = '' - for l in tb.split('\n'): - lines.append(" "*indent + prefix + l) - self.write('\n'.join(lines)) - self.exceptionHandler(*sys.exc_info()) - - def cmdSelected(self, item): - index = -(self.ui.historyList.row(item)+1) - self.input.setHistory(index) - self.input.setFocus() - - def cmdDblClicked(self, item): - index = -(self.ui.historyList.row(item)+1) - self.input.setHistory(index) - self.input.execCmd() - - def flush(self): - pass - - def catchAllExceptions(self, catch=True): - """ - If True, the console will catch all unhandled exceptions and display the stack - trace. Each exception caught clears the last. - """ - with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions): - self.ui.catchAllExceptionsBtn.setChecked(catch) - - if catch: - with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException): - self.ui.catchNextExceptionBtn.setChecked(False) - self.enableExceptionHandling() - self.ui.exceptionBtn.setChecked(True) - else: - self.disableExceptionHandling() - - def catchNextException(self, catch=True): - """ - If True, the console will catch the next unhandled exception and display the stack - trace. - """ - with SignalBlock(self.ui.catchNextExceptionBtn.toggled, self.catchNextException): - self.ui.catchNextExceptionBtn.setChecked(catch) - if catch: - with SignalBlock(self.ui.catchAllExceptionsBtn.toggled, self.catchAllExceptions): - self.ui.catchAllExceptionsBtn.setChecked(False) - self.enableExceptionHandling() - self.ui.exceptionBtn.setChecked(True) - else: - self.disableExceptionHandling() - - def enableExceptionHandling(self): - exceptionHandling.register(self.exceptionHandler) - self.updateSysTrace() - - def disableExceptionHandling(self): - exceptionHandling.unregister(self.exceptionHandler) - self.updateSysTrace() - - def clearExceptionClicked(self): - self.currentTraceback = None - self.frames = [] - self.ui.exceptionInfoLabel.setText("[No current exception]") - self.ui.exceptionStackList.clear() - self.ui.clearExceptionBtn.setEnabled(False) - - def stackItemClicked(self, item): - pass - - def stackItemDblClicked(self, item): - editor = self.editor - if editor is None: - editor = getConfigOption('editorCommand') - if editor is None: - return - tb = self.currentFrame() - lineNum = tb.f_lineno - fileName = tb.f_code.co_filename - subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True) - - def updateSysTrace(self): - ## Install or uninstall sys.settrace handler - - if not self.ui.catchNextExceptionBtn.isChecked() and not self.ui.catchAllExceptionsBtn.isChecked(): - if sys.gettrace() == self.systrace: - sys.settrace(None) - return - - if self.ui.onlyUncaughtCheck.isChecked(): - if sys.gettrace() == self.systrace: - sys.settrace(None) - else: - if sys.gettrace() is not None and sys.gettrace() != self.systrace: - self.ui.onlyUncaughtCheck.setChecked(False) - raise Exception("sys.settrace is in use; cannot monitor for caught exceptions.") - else: - sys.settrace(self.systrace) - - def exceptionHandler(self, excType, exc, tb, systrace=False, frame=None): - if frame is None: - frame = sys._getframe() - - # exceptions raised in non-gui threads must be handled separately - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if not isGuiThread: - # sending a frame from one thread to another.. probably not safe, but better than just - # dropping the exception? - self._threadException.emit((excType, exc, tb, systrace, frame.f_back)) - return - - if self.ui.catchNextExceptionBtn.isChecked(): - self.ui.catchNextExceptionBtn.setChecked(False) - elif not self.ui.catchAllExceptionsBtn.isChecked(): - return - - self.currentTraceback = tb - - excMessage = ''.join(traceback.format_exception_only(excType, exc)) - self.ui.exceptionInfoLabel.setText(excMessage) - - if systrace: - # exceptions caught using systrace don't need the usual - # call stack + traceback handling - self.setStack(frame.f_back.f_back) - else: - self.setStack(frame=frame.f_back, tb=tb) - - def _threadExceptionHandler(self, args): - self.exceptionHandler(*args) - - def setStack(self, frame=None, tb=None): - """Display a call stack and exception traceback. - - This allows the user to probe the contents of any frame in the given stack. - - *frame* may either be a Frame instance or None, in which case the current - frame is retrieved from ``sys._getframe()``. - - If *tb* is provided then the frames in the traceback will be appended to - the end of the stack list. If *tb* is None, then sys.exc_info() will - be checked instead. - """ - self.ui.clearExceptionBtn.setEnabled(True) - - if frame is None: - frame = sys._getframe().f_back - - if tb is None: - tb = sys.exc_info()[2] - - self.ui.exceptionStackList.clear() - self.frames = [] - - # Build stack up to this point - for index, line in enumerate(traceback.extract_stack(frame)): - # extract_stack return value changed in python 3.5 - if 'FrameSummary' in str(type(line)): - line = (line.filename, line.lineno, line.name, line._line) - - self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) - while frame is not None: - self.frames.insert(0, frame) - frame = frame.f_back - - if tb is None: - return - - self.ui.exceptionStackList.addItem('-- exception caught here: --') - item = self.ui.exceptionStackList.item(self.ui.exceptionStackList.count()-1) - item.setBackground(QtGui.QBrush(QtGui.QColor(200, 200, 200))) - item.setForeground(QtGui.QBrush(QtGui.QColor(50, 50, 50))) - self.frames.append(None) - - # And finish the rest of the stack up to the exception - for index, line in enumerate(traceback.extract_tb(tb)): - # extract_stack return value changed in python 3.5 - if 'FrameSummary' in str(type(line)): - line = (line.filename, line.lineno, line.name, line._line) - - self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) - while tb is not None: - self.frames.append(tb.tb_frame) - tb = tb.tb_next - - def systrace(self, frame, event, arg): - if event == 'exception' and self.checkException(*arg): - self.exceptionHandler(*arg, systrace=True) - return self.systrace - - def checkException(self, excType, exc, tb): - ## Return True if the exception is interesting; False if it should be ignored. - - filename = tb.tb_frame.f_code.co_filename - function = tb.tb_frame.f_code.co_name - - filterStr = str(self.ui.filterText.text()) - if filterStr != '': - if isinstance(exc, Exception): - msg = traceback.format_exception_only(type(exc), exc) - elif isinstance(exc, basestring): - msg = exc - else: - msg = repr(exc) - match = re.search(filterStr, "%s:%s:%s" % (filename, function, msg)) - return match is not None - - ## Go through a list of common exception points we like to ignore: - if excType is GeneratorExit or excType is StopIteration: - return False - if excType is KeyError: - if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'): - return False - if filename.endswith('python2.7/copy.py') and function == '_keep_alive': - return False - if excType is AttributeError: - if filename.endswith('python2.7/collections.py') and function == '__init__': - return False - if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'): - return False - if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'): - return False - if filename.endswith('MetaArray.py') and function == '__getattr__': - for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array - if name in exc: - return False - if filename.endswith('flowchart/eq.py'): - return False - if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage': - return False - if excType is TypeError: - if filename.endswith('numpy/lib/function_base.py') and function == 'iterable': - return False - if excType is ZeroDivisionError: - if filename.endswith('python2.7/traceback.py'): - return False - - return True - diff --git a/pyqtgraph/console/__init__.py b/pyqtgraph/console/__init__.py deleted file mode 100644 index 16436ab..0000000 --- a/pyqtgraph/console/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .Console import ConsoleWidget \ No newline at end of file diff --git a/pyqtgraph/console/__pycache__/CmdInput.cpython-36.pyc b/pyqtgraph/console/__pycache__/CmdInput.cpython-36.pyc deleted file mode 100644 index 5d5aaf3..0000000 Binary files a/pyqtgraph/console/__pycache__/CmdInput.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/Console.cpython-36.pyc b/pyqtgraph/console/__pycache__/Console.cpython-36.pyc deleted file mode 100644 index 3cd2afd..0000000 Binary files a/pyqtgraph/console/__pycache__/Console.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/console/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index fa08d59..0000000 Binary files a/pyqtgraph/console/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/template_pyqt5.cpython-36.pyc b/pyqtgraph/console/__pycache__/template_pyqt5.cpython-36.pyc deleted file mode 100644 index cf3c366..0000000 Binary files a/pyqtgraph/console/__pycache__/template_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/template_pyqt6.cpython-36.pyc b/pyqtgraph/console/__pycache__/template_pyqt6.cpython-36.pyc deleted file mode 100644 index ed01df4..0000000 Binary files a/pyqtgraph/console/__pycache__/template_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/template_pyside2.cpython-36.pyc b/pyqtgraph/console/__pycache__/template_pyside2.cpython-36.pyc deleted file mode 100644 index d53421e..0000000 Binary files a/pyqtgraph/console/__pycache__/template_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/__pycache__/template_pyside6.cpython-36.pyc b/pyqtgraph/console/__pycache__/template_pyside6.cpython-36.pyc deleted file mode 100644 index 2e6be4a..0000000 Binary files a/pyqtgraph/console/__pycache__/template_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/console/template_pyqt5.py b/pyqtgraph/console/template_pyqt5.py deleted file mode 100644 index c8c2cba..0000000 --- a/pyqtgraph/console/template_pyqt5.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'pyqtgraph/console/template.ui' -# -# Created by: PyQt5 UI code generator 5.5.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(739, 497) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) - self.verticalLayout.setObjectName("verticalLayout") - self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) - font = QtGui.QFont() - font.setFamily("Monospace") - self.output.setFont(font) - self.output.setReadOnly(True) - self.output.setObjectName("output") - self.verticalLayout.addWidget(self.output) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName("input") - self.horizontalLayout.addWidget(self.input) - self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) - self.historyBtn.setCheckable(True) - self.historyBtn.setObjectName("historyBtn") - self.horizontalLayout.addWidget(self.historyBtn) - self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) - self.exceptionBtn.setCheckable(True) - self.exceptionBtn.setObjectName("exceptionBtn") - self.horizontalLayout.addWidget(self.exceptionBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - self.historyList = QtWidgets.QListWidget(self.splitter) - font = QtGui.QFont() - font.setFamily("Monospace") - self.historyList.setFont(font) - self.historyList.setObjectName("historyList") - self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) - self.exceptionGroup.setObjectName("exceptionGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.gridLayout_2.setHorizontalSpacing(2) - self.gridLayout_2.setVerticalSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setEnabled(False) - self.clearExceptionBtn.setObjectName("clearExceptionBtn") - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) - self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setCheckable(True) - self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setCheckable(True) - self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setChecked(True) - self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) - self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) - self.exceptionStackList.setAlternatingRowColors(True) - self.exceptionStackList.setObjectName("exceptionStackList") - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) - self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setChecked(True) - self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) - self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setWordWrap(True) - self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) - self.label = QtWidgets.QLabel(self.exceptionGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) - self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) - self.filterText.setObjectName("filterText") - self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Console")) - self.historyBtn.setText(_translate("Form", "History..")) - self.exceptionBtn.setText(_translate("Form", "Exceptions..")) - self.exceptionGroup.setTitle(_translate("Form", "Exception Handling")) - self.clearExceptionBtn.setText(_translate("Form", "Clear Stack")) - self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions")) - self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception")) - self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions")) - self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame")) - self.exceptionInfoLabel.setText(_translate("Form", "Stack Trace")) - self.label.setText(_translate("Form", "Filter (regex):")) - -from .CmdInput import CmdInput diff --git a/pyqtgraph/console/template_pyqt6.py b/pyqtgraph/console/template_pyqt6.py deleted file mode 100644 index 0208f12..0000000 --- a/pyqtgraph/console/template_pyqt6.py +++ /dev/null @@ -1,115 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\console\template.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(739, 497) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Orientations.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) - font = QtGui.QFont() - font.setFamily("Monospace") - self.output.setFont(font) - self.output.setReadOnly(True) - self.output.setObjectName("output") - self.verticalLayout.addWidget(self.output) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName("input") - self.horizontalLayout.addWidget(self.input) - self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) - self.historyBtn.setCheckable(True) - self.historyBtn.setObjectName("historyBtn") - self.horizontalLayout.addWidget(self.historyBtn) - self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) - self.exceptionBtn.setCheckable(True) - self.exceptionBtn.setObjectName("exceptionBtn") - self.horizontalLayout.addWidget(self.exceptionBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - self.historyList = QtWidgets.QListWidget(self.splitter) - font = QtGui.QFont() - font.setFamily("Monospace") - self.historyList.setFont(font) - self.historyList.setObjectName("historyList") - self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) - self.exceptionGroup.setObjectName("exceptionGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.gridLayout_2.setHorizontalSpacing(2) - self.gridLayout_2.setVerticalSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setEnabled(False) - self.clearExceptionBtn.setObjectName("clearExceptionBtn") - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) - self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setCheckable(True) - self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setCheckable(True) - self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setChecked(True) - self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) - self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) - self.exceptionStackList.setAlternatingRowColors(True) - self.exceptionStackList.setObjectName("exceptionStackList") - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) - self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setChecked(True) - self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) - self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setWordWrap(True) - self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) - self.label = QtWidgets.QLabel(self.exceptionGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) - self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) - self.filterText.setObjectName("filterText") - self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Console")) - self.historyBtn.setText(_translate("Form", "History..")) - self.exceptionBtn.setText(_translate("Form", "Exceptions..")) - self.exceptionGroup.setTitle(_translate("Form", "Exception Handling")) - self.clearExceptionBtn.setText(_translate("Form", "Clear Stack")) - self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions")) - self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception")) - self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions")) - self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame")) - self.exceptionInfoLabel.setText(_translate("Form", "Stack Trace")) - self.label.setText(_translate("Form", "Filter (regex):")) -from .CmdInput import CmdInput diff --git a/pyqtgraph/console/template_pyside2.py b/pyqtgraph/console/template_pyside2.py deleted file mode 100644 index c8662c7..0000000 --- a/pyqtgraph/console/template_pyside2.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'template.ui' -# -# Created: Sun Sep 18 19:19:10 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(694, 497) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) - font = QtGui.QFont() - font.setFamily("Monospace") - self.output.setFont(font) - self.output.setReadOnly(True) - self.output.setObjectName("output") - self.verticalLayout.addWidget(self.output) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName("input") - self.horizontalLayout.addWidget(self.input) - self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) - self.historyBtn.setCheckable(True) - self.historyBtn.setObjectName("historyBtn") - self.horizontalLayout.addWidget(self.historyBtn) - self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) - self.exceptionBtn.setCheckable(True) - self.exceptionBtn.setObjectName("exceptionBtn") - self.horizontalLayout.addWidget(self.exceptionBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - self.historyList = QtWidgets.QListWidget(self.splitter) - font = QtGui.QFont() - font.setFamily("Monospace") - self.historyList.setFont(font) - self.historyList.setObjectName("historyList") - self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) - self.exceptionGroup.setObjectName("exceptionGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setEnabled(False) - self.clearExceptionBtn.setObjectName("clearExceptionBtn") - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) - self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setCheckable(True) - self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setCheckable(True) - self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setChecked(True) - self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) - self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) - self.exceptionStackList.setAlternatingRowColors(True) - self.exceptionStackList.setObjectName("exceptionStackList") - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) - self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setChecked(True) - self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) - self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) - self.label = QtWidgets.QLabel(self.exceptionGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) - self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) - self.filterText.setObjectName("filterText") - self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Console", None, -1)) - self.historyBtn.setText(QtWidgets.QApplication.translate("Form", "History..", None, -1)) - self.exceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Exceptions..", None, -1)) - self.exceptionGroup.setTitle(QtWidgets.QApplication.translate("Form", "Exception Handling", None, -1)) - self.clearExceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Clear Exception", None, -1)) - self.catchAllExceptionsBtn.setText(QtWidgets.QApplication.translate("Form", "Show All Exceptions", None, -1)) - self.catchNextExceptionBtn.setText(QtWidgets.QApplication.translate("Form", "Show Next Exception", None, -1)) - self.onlyUncaughtCheck.setText(QtWidgets.QApplication.translate("Form", "Only Uncaught Exceptions", None, -1)) - self.runSelectedFrameCheck.setText(QtWidgets.QApplication.translate("Form", "Run commands in selected stack frame", None, -1)) - self.exceptionInfoLabel.setText(QtWidgets.QApplication.translate("Form", "Exception Info", None, -1)) - self.label.setText(QtWidgets.QApplication.translate("Form", "Filter (regex):", None, -1)) - -from .CmdInput import CmdInput diff --git a/pyqtgraph/console/template_pyside6.py b/pyqtgraph/console/template_pyside6.py deleted file mode 100644 index 22a2aba..0000000 --- a/pyqtgraph/console/template_pyside6.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'template.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from .CmdInput import CmdInput - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(739, 497) - self.gridLayout = QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName(u"gridLayout") - self.splitter = QSplitter(Form) - self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Vertical) - self.layoutWidget = QWidget(self.splitter) - self.layoutWidget.setObjectName(u"layoutWidget") - self.verticalLayout = QVBoxLayout(self.layoutWidget) - self.verticalLayout.setObjectName(u"verticalLayout") - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.output = QPlainTextEdit(self.layoutWidget) - self.output.setObjectName(u"output") - font = QFont() - font.setFamily(u"Monospace") - self.output.setFont(font) - self.output.setReadOnly(True) - - self.verticalLayout.addWidget(self.output) - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName(u"input") - - self.horizontalLayout.addWidget(self.input) - - self.historyBtn = QPushButton(self.layoutWidget) - self.historyBtn.setObjectName(u"historyBtn") - self.historyBtn.setCheckable(True) - - self.horizontalLayout.addWidget(self.historyBtn) - - self.exceptionBtn = QPushButton(self.layoutWidget) - self.exceptionBtn.setObjectName(u"exceptionBtn") - self.exceptionBtn.setCheckable(True) - - self.horizontalLayout.addWidget(self.exceptionBtn) - - - self.verticalLayout.addLayout(self.horizontalLayout) - - self.splitter.addWidget(self.layoutWidget) - self.historyList = QListWidget(self.splitter) - self.historyList.setObjectName(u"historyList") - self.historyList.setFont(font) - self.splitter.addWidget(self.historyList) - self.exceptionGroup = QGroupBox(self.splitter) - self.exceptionGroup.setObjectName(u"exceptionGroup") - self.gridLayout_2 = QGridLayout(self.exceptionGroup) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.gridLayout_2.setHorizontalSpacing(2) - self.gridLayout_2.setVerticalSpacing(0) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.clearExceptionBtn = QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setObjectName(u"clearExceptionBtn") - self.clearExceptionBtn.setEnabled(False) - - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) - - self.catchAllExceptionsBtn = QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setObjectName(u"catchAllExceptionsBtn") - self.catchAllExceptionsBtn.setCheckable(True) - - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - - self.catchNextExceptionBtn = QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setObjectName(u"catchNextExceptionBtn") - self.catchNextExceptionBtn.setCheckable(True) - - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - - self.onlyUncaughtCheck = QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setObjectName(u"onlyUncaughtCheck") - self.onlyUncaughtCheck.setChecked(True) - - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) - - self.exceptionStackList = QListWidget(self.exceptionGroup) - self.exceptionStackList.setObjectName(u"exceptionStackList") - self.exceptionStackList.setAlternatingRowColors(True) - - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) - - self.runSelectedFrameCheck = QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setObjectName(u"runSelectedFrameCheck") - self.runSelectedFrameCheck.setChecked(True) - - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) - - self.exceptionInfoLabel = QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setObjectName(u"exceptionInfoLabel") - self.exceptionInfoLabel.setWordWrap(True) - - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) - - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - - self.gridLayout_2.addItem(self.horizontalSpacer, 0, 5, 1, 1) - - self.label = QLabel(self.exceptionGroup) - self.label.setObjectName(u"label") - - self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) - - self.filterText = QLineEdit(self.exceptionGroup) - self.filterText.setObjectName(u"filterText") - - self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) - - self.splitter.addWidget(self.exceptionGroup) - - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"Console", None)) - self.historyBtn.setText(QCoreApplication.translate("Form", u"History..", None)) - self.exceptionBtn.setText(QCoreApplication.translate("Form", u"Exceptions..", None)) - self.exceptionGroup.setTitle(QCoreApplication.translate("Form", u"Exception Handling", None)) - self.clearExceptionBtn.setText(QCoreApplication.translate("Form", u"Clear Stack", None)) - self.catchAllExceptionsBtn.setText(QCoreApplication.translate("Form", u"Show All Exceptions", None)) - self.catchNextExceptionBtn.setText(QCoreApplication.translate("Form", u"Show Next Exception", None)) - self.onlyUncaughtCheck.setText(QCoreApplication.translate("Form", u"Only Uncaught Exceptions", None)) - self.runSelectedFrameCheck.setText(QCoreApplication.translate("Form", u"Run commands in selected stack frame", None)) - self.exceptionInfoLabel.setText(QCoreApplication.translate("Form", u"Stack Trace", None)) - self.label.setText(QCoreApplication.translate("Form", u"Filter (regex):", None)) - # retranslateUi - diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py deleted file mode 100644 index 1d12e9e..0000000 --- a/pyqtgraph/debug.py +++ /dev/null @@ -1,1226 +0,0 @@ -# -*- coding: utf-8 -*- -""" -debug.py - Functions to aid in debugging -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from __future__ import print_function - -import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading -from . import ptime -from numpy import ndarray -from .Qt import QtCore, QtGui -from .util.mutex import Mutex -from .util import cprint - -__ftraceDepth = 0 -def ftrace(func): - """Decorator used for marking the beginning and end of function calls. - Automatically indents nested calls. - """ - def w(*args, **kargs): - global __ftraceDepth - pfx = " " * __ftraceDepth - print(pfx + func.__name__ + " start") - __ftraceDepth += 1 - try: - rv = func(*args, **kargs) - finally: - __ftraceDepth -= 1 - print(pfx + func.__name__ + " done") - return rv - return w - - -class Tracer(object): - """ - Prints every function enter/exit. Useful for debugging crashes / lockups. - """ - def __init__(self): - self.count = 0 - self.stack = [] - - def trace(self, frame, event, arg): - self.count += 1 - # If it has been a long time since we saw the top of the stack, - # print a reminder - if self.count % 1000 == 0: - print("----- current stack: -----") - for line in self.stack: - print(line) - if event == 'call': - line = " " * len(self.stack) + ">> " + self.frameInfo(frame) - print(line) - self.stack.append(line) - elif event == 'return': - self.stack.pop() - line = " " * len(self.stack) + "<< " + self.frameInfo(frame) - print(line) - if len(self.stack) == 0: - self.count = 0 - - return self.trace - - def stop(self): - sys.settrace(None) - - def start(self): - sys.settrace(self.trace) - - def frameInfo(self, fr): - filename = fr.f_code.co_filename - funcname = fr.f_code.co_name - lineno = fr.f_lineno - callfr = sys._getframe(3) - callline = "%s %d" % (callfr.f_code.co_name, callfr.f_lineno) - args, _, _, value_dict = inspect.getargvalues(fr) - if len(args) and args[0] == 'self': - instance = value_dict.get('self', None) - if instance is not None: - cls = getattr(instance, '__class__', None) - if cls is not None: - funcname = cls.__name__ + "." + funcname - return "%s: %s %s: %s" % (callline, filename, lineno, funcname) - - -def warnOnException(func): - """Decorator that catches/ignores exceptions and prints a stack trace.""" - def w(*args, **kwds): - try: - func(*args, **kwds) - except: - printExc('Ignored exception:') - return w - - -def getExc(indent=4, prefix='| ', skip=1): - lines = formatException(*sys.exc_info(), skip=skip) - lines2 = [] - for l in lines: - lines2.extend(l.strip('\n').split('\n')) - lines3 = [" "*indent + prefix + l for l in lines2] - return '\n'.join(lines3) - - -def printExc(msg='', indent=4, prefix='|'): - """Print an error message followed by an indented exception backtrace - (This function is intended to be called within except: blocks)""" - exc = getExc(indent, prefix + ' ', skip=2) - print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) - print(" "*indent + prefix + '='*30 + '>>') - print(exc) - print(" "*indent + prefix + '='*30 + '<<') - - -def printTrace(msg='', indent=4, prefix='|'): - """Print an error message followed by an indented stack trace""" - trace = backtrace(1) - #exc = getExc(indent, prefix + ' ') - print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) - print(" "*indent + prefix + '='*30 + '>>') - for line in trace.split('\n'): - print(" "*indent + prefix + " " + line) - print(" "*indent + prefix + '='*30 + '<<') - - -def backtrace(skip=0): - return ''.join(traceback.format_stack()[:-(skip+1)]) - - -def formatException(exctype, value, tb, skip=0): - """Return a list of formatted exception strings. - - Similar to traceback.format_exception, but displays the entire stack trace - rather than just the portion downstream of the point where the exception is - caught. In particular, unhandled exceptions that occur during Qt signal - handling do not usually show the portion of the stack that emitted the - signal. - """ - lines = traceback.format_exception(exctype, value, tb) - lines = [lines[0]] + traceback.format_stack()[:-(skip+1)] + [' --- exception caught here ---\n'] + lines[1:] - return lines - - -def printException(exctype, value, traceback): - """Print an exception with its full traceback. - - Set `sys.excepthook = printException` to ensure that exceptions caught - inside Qt signal handlers are printed with their full stack trace. - """ - print(''.join(formatException(exctype, value, traceback, skip=1))) - - -def listObjs(regex='Q', typ=None): - """List all objects managed by python gc with class name matching regex. - Finds 'Q...' classes by default.""" - if typ is not None: - return [x for x in gc.get_objects() if isinstance(x, typ)] - else: - return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] - - - -def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None): - """Determine all paths of object references from startObj to endObj""" - refs = [] - if path is None: - path = [endObj] - if ignore is None: - ignore = {} - ignore[id(sys._getframe())] = None - ignore[id(path)] = None - ignore[id(seen)] = None - prefix = " "*(8-maxLen) - #print prefix + str(map(type, path)) - prefix += " " - if restart: - #gc.collect() - seen.clear() - gc.collect() - newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] - ignore[id(newRefs)] = None - #fo = allFrameObjs() - #newRefs = [] - #for r in gc.get_referrers(endObj): - #try: - #if r not in fo: - #newRefs.append(r) - #except: - #newRefs.append(r) - - for r in newRefs: - #print prefix+"->"+str(type(r)) - if type(r).__name__ in ['frame', 'function', 'listiterator']: - #print prefix+" FRAME" - continue - try: - if any([r is x for x in path]): - #print prefix+" LOOP", objChainString([r]+path) - continue - except: - print(r) - print(path) - raise - if r is startObj: - refs.append([r]) - print(refPathString([startObj]+path)) - continue - if maxLen == 0: - #print prefix+" END:", objChainString([r]+path) - continue - ## See if we have already searched this node. - ## If not, recurse. - tree = None - try: - cache = seen[id(r)] - if cache[0] >= maxLen: - tree = cache[1] - for p in tree: - print(refPathString(p+path)) - except KeyError: - pass - - ignore[id(tree)] = None - if tree is None: - tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) - seen[id(r)] = [maxLen, tree] - ## integrate any returned results - if len(tree) == 0: - #print prefix+" EMPTY TREE" - continue - else: - for p in tree: - refs.append(p+[r]) - #seen[id(r)] = [maxLen, refs] - return refs - - -def objString(obj): - """Return a short but descriptive string for any object""" - try: - if type(obj) in [int, float]: - return str(obj) - elif isinstance(obj, dict): - if len(obj) > 5: - return "" % (",".join(list(obj.keys())[:5])) - else: - return "" % (",".join(list(obj.keys()))) - elif isinstance(obj, str): - if len(obj) > 50: - return '"%s..."' % obj[:50] - else: - return obj[:] - elif isinstance(obj, ndarray): - return "" % (str(obj.dtype), str(obj.shape)) - elif hasattr(obj, '__len__'): - if len(obj) > 5: - return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) - else: - return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) - else: - return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) - except: - return str(type(obj)) - -def refPathString(chain): - """Given a list of adjacent objects in a reference path, print the 'natural' path - names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" - s = objString(chain[0]) - i = 0 - while i < len(chain)-1: - #print " -> ", i - i += 1 - o1 = chain[i-1] - o2 = chain[i] - cont = False - if isinstance(o1, list) or isinstance(o1, tuple): - if any([o2 is x for x in o1]): - s += "[%d]" % o1.index(o2) - continue - #print " not list" - if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: - i += 1 - if i >= len(chain): - s += ".__dict__" - continue - o3 = chain[i] - for k in o2: - if o2[k] is o3: - s += '.%s' % k - cont = True - continue - #print " not __dict__" - if isinstance(o1, dict): - try: - if o2 in o1: - s += "[key:%s]" % objString(o2) - continue - except TypeError: - pass - for k in o1: - if o1[k] is o2: - s += "[%s]" % objString(k) - cont = True - continue - #print " not dict" - #for k in dir(o1): ## Not safe to request attributes like this. - #if getattr(o1, k) is o2: - #s += ".%s" % k - #cont = True - #continue - #print " not attr" - if cont: - continue - s += " ? " - sys.stdout.flush() - return s - - -def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): - """Guess how much memory an object is using""" - ignoreTypes = ['MethodType', 'UnboundMethodType', 'BuiltinMethodType', 'FunctionType', 'BuiltinFunctionType'] - ignoreTypes = [getattr(types, key) for key in ignoreTypes if hasattr(types, key)] - ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') - - - if ignore is None: - ignore = {} - - indent = ' '*depth - - try: - hash(obj) - hsh = obj - except: - hsh = "%s:%d" % (str(type(obj)), id(obj)) - - if hsh in ignore: - return 0 - ignore[hsh] = 1 - - try: - size = sys.getsizeof(obj) - except TypeError: - size = 0 - - if isinstance(obj, ndarray): - try: - size += len(obj.data) - except: - pass - - - if recursive: - if type(obj) in [list, tuple]: - if verbose: - print(indent+"list:") - for o in obj: - s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) - if verbose: - print(indent+' +', s) - size += s - elif isinstance(obj, dict): - if verbose: - print(indent+"list:") - for k in obj: - s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) - if verbose: - print(indent+' +', k, s) - size += s - #elif isinstance(obj, QtCore.QObject): - #try: - #childs = obj.children() - #if verbose: - #print indent+"Qt children:" - #for ch in childs: - #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) - #size += s - #if verbose: - #print indent + ' +', ch.objectName(), s - - #except: - #pass - #if isinstance(obj, types.InstanceType): - gc.collect() - if verbose: - print(indent+'attrs:') - for k in dir(obj): - if k in ['__dict__']: - continue - o = getattr(obj, k) - if type(o) in ignoreTypes: - continue - strtyp = str(type(o)) - if ignoreRegex.search(strtyp): - continue - #if isinstance(o, types.ObjectType) and strtyp == "": - #continue - - #if verbose: - #print indent, k, '?' - refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] - if len(refs) == 1: - s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) - size += s - if verbose: - print(indent + " +", k, s) - #else: - #if verbose: - #print indent + ' -', k, len(refs) - return size - -class GarbageWatcher(object): - """ - Convenient dictionary for holding weak references to objects. - Mainly used to check whether the objects have been collect yet or not. - - Example: - gw = GarbageWatcher() - gw['objName'] = obj - gw['objName2'] = obj2 - gw.check() - - - """ - def __init__(self): - self.objs = weakref.WeakValueDictionary() - self.allNames = [] - - def add(self, obj, name): - self.objs[name] = obj - self.allNames.append(name) - - def __setitem__(self, name, obj): - self.add(obj, name) - - def check(self): - """Print a list of all watched objects and whether they have been collected.""" - gc.collect() - dead = self.allNames[:] - alive = [] - for k in self.objs: - dead.remove(k) - alive.append(k) - print("Deleted objects:", dead) - print("Live objects:", alive) - - def __getitem__(self, item): - return self.objs[item] - - - - -class Profiler(object): - """Simple profiler allowing measurement of multiple time intervals. - - By default, profilers are disabled. To enable profiling, set the - environment variable `PYQTGRAPHPROFILE` to a comma-separated list of - fully-qualified names of profiled functions. - - Calling a profiler registers a message (defaulting to an increasing - counter) that contains the time elapsed since the last call. When the - profiler is about to be garbage-collected, the messages are passed to the - outer profiler if one is running, or printed to stdout otherwise. - - If `delayed` is set to False, messages are immediately printed instead. - - Example: - def function(...): - profiler = Profiler() - ... do stuff ... - profiler('did stuff') - ... do other stuff ... - profiler('did other stuff') - # profiler is garbage-collected and flushed at function end - - If this function is a method of class C, setting `PYQTGRAPHPROFILE` to - "C.function" (without the module name) will enable this profiler. - - For regular functions, use the qualified name of the function, stripping - only the initial "pyqtgraph." prefix from the module. - """ - - _profilers = os.environ.get("PYQTGRAPHPROFILE", None) - _profilers = _profilers.split(",") if _profilers is not None else [] - - _depth = 0 - _msgs = [] - disable = False # set this flag to disable all or individual profilers at runtime - - class DisabledProfiler(object): - def __init__(self, *args, **kwds): - pass - def __call__(self, *args): - pass - def finish(self): - pass - def mark(self, msg=None): - pass - _disabledProfiler = DisabledProfiler() - - def __new__(cls, msg=None, disabled='env', delayed=True): - """Optionally create a new profiler based on caller's qualname. - """ - if disabled is True or (disabled == 'env' and len(cls._profilers) == 0): - return cls._disabledProfiler - - # determine the qualified name of the caller function - caller_frame = sys._getframe(1) - try: - caller_object_type = type(caller_frame.f_locals["self"]) - except KeyError: # we are in a regular function - qualifier = caller_frame.f_globals["__name__"].split(".", 1)[-1] - else: # we are in a method - qualifier = caller_object_type.__name__ - func_qualname = qualifier + "." + caller_frame.f_code.co_name - if disabled == 'env' and func_qualname not in cls._profilers: # don't do anything - return cls._disabledProfiler - # create an actual profiling object - cls._depth += 1 - obj = super(Profiler, cls).__new__(cls) - obj._name = msg or func_qualname - obj._delayed = delayed - obj._markCount = 0 - obj._finished = False - obj._firstTime = obj._lastTime = ptime.time() - obj._newMsg("> Entering " + obj._name) - return obj - - def __call__(self, msg=None): - """Register or print a new message with timing information. - """ - if self.disable: - return - if msg is None: - msg = str(self._markCount) - self._markCount += 1 - newTime = ptime.time() - self._newMsg(" %s: %0.4f ms", - msg, (newTime - self._lastTime) * 1000) - self._lastTime = newTime - - def mark(self, msg=None): - self(msg) - - def _newMsg(self, msg, *args): - msg = " " * (self._depth - 1) + msg - if self._delayed: - self._msgs.append((msg, args)) - else: - self.flush() - print(msg % args) - - def __del__(self): - self.finish() - - def finish(self, msg=None): - """Add a final message; flush the message list if no parent profiler. - """ - if self._finished or self.disable: - return - self._finished = True - if msg is not None: - self(msg) - self._newMsg("< Exiting %s, total time: %0.4f ms", - self._name, (ptime.time() - self._firstTime) * 1000) - type(self)._depth -= 1 - if self._depth < 1: - self.flush() - - def flush(self): - if self._msgs: - print("\n".join([m[0]%m[1] for m in self._msgs])) - type(self)._msgs = [] - - -def profile(code, name='profile_run', sort='cumulative', num=30): - """Common-use for cProfile""" - cProfile.run(code, name) - stats = pstats.Stats(name) - stats.sort_stats(sort) - stats.print_stats(num) - return stats - - - -#### Code for listing (nearly) all objects in the known universe -#### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects -# Recursively expand slist's objects -# into olist, using seen to track -# already processed objects. -def _getr(slist, olist, first=True): - i = 0 - for e in slist: - - oid = id(e) - typ = type(e) - if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys - continue - olist[oid] = e - if first and (i%1000) == 0: - gc.collect() - tl = gc.get_referents(e) - if tl: - _getr(tl, olist, first=False) - i += 1 -# The public function. -def get_all_objects(): - """Return a list of all live Python objects (excluding int and long), not including the list itself.""" - gc.collect() - gcl = gc.get_objects() - olist = {} - _getr(gcl, olist) - - del olist[id(olist)] - del olist[id(gcl)] - del olist[id(sys._getframe())] - return olist - - -def lookup(oid, objects=None): - """Return an object given its ID, if it exists.""" - if objects is None: - objects = get_all_objects() - return objects[oid] - - - - -class ObjTracker(object): - """ - Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. - This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking - its own internal objects. - - Example: - ot = ObjTracker() # takes snapshot of currently existing objects - ... do stuff ... - ot.diff() # prints lists of objects created and deleted since ot was initialized - ... do stuff ... - ot.diff() # prints lists of objects created and deleted since last call to ot.diff() - # also prints list of items that were created since initialization AND have not been deleted yet - # (if done correctly, this list can tell you about objects that were leaked) - - arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) - ## that were considered persistent when the last diff() was run - - describeObj(arrays[0]) ## See if we can determine who has references to this array - """ - - - allObjs = {} ## keep track of all objects created and stored within class instances - allObjs[id(allObjs)] = None - - def __init__(self): - self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} - ## (If it is not possible to weakref the object, then the value is None) - self.startCount = {} - self.newRefs = {} ## list of objects that have been created since initialization - self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called - self.objTypes = {} - - ObjTracker.allObjs[id(self)] = None - self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] - self.objs.append(self.objs) - for v in self.objs: - ObjTracker.allObjs[id(v)] = None - - self.start() - - def findNew(self, regex): - """Return all objects matching regex that were considered 'new' when the last diff() was run.""" - return self.findTypes(self.newRefs, regex) - - def findPersistent(self, regex): - """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" - return self.findTypes(self.persistentRefs, regex) - - - def start(self): - """ - Remember the current set of objects as the comparison for all future calls to diff() - Called automatically on init, but can be called manually as well. - """ - refs, count, objs = self.collect() - for r in self.startRefs: - self.forgetRef(self.startRefs[r]) - self.startRefs.clear() - self.startRefs.update(refs) - for r in refs: - self.rememberRef(r) - self.startCount.clear() - self.startCount.update(count) - #self.newRefs.clear() - #self.newRefs.update(refs) - - def diff(self, **kargs): - """ - Compute all differences between the current object set and the reference set. - Print a set of reports for created, deleted, and persistent objects - """ - refs, count, objs = self.collect() ## refs contains the list of ALL objects - - ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) - delRefs = {} - for i in list(self.startRefs.keys()): - if i not in refs: - delRefs[i] = self.startRefs[i] - del self.startRefs[i] - self.forgetRef(delRefs[i]) - for i in list(self.newRefs.keys()): - if i not in refs: - delRefs[i] = self.newRefs[i] - del self.newRefs[i] - self.forgetRef(delRefs[i]) - #print "deleted:", len(delRefs) - - ## Which refs have appeared since call to start() or diff() - persistentRefs = {} ## created since start(), but before last diff() - createRefs = {} ## created since last diff() - for o in refs: - if o not in self.startRefs: - if o not in self.newRefs: - createRefs[o] = refs[o] ## object has been created since last diff() - else: - persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) - #print "new:", len(newRefs) - - ## self.newRefs holds the entire set of objects created since start() - for r in self.newRefs: - self.forgetRef(self.newRefs[r]) - self.newRefs.clear() - self.newRefs.update(persistentRefs) - self.newRefs.update(createRefs) - for r in self.newRefs: - self.rememberRef(self.newRefs[r]) - #print "created:", len(createRefs) - - ## self.persistentRefs holds all objects considered persistent. - self.persistentRefs.clear() - self.persistentRefs.update(persistentRefs) - - - print("----------- Count changes since start: ----------") - c1 = count.copy() - for k in self.startCount: - c1[k] = c1.get(k, 0) - self.startCount[k] - typs = list(c1.keys()) - typs.sort(key=lambda a: c1[a]) - for t in typs: - if c1[t] == 0: - continue - num = "%d" % c1[t] - print(" " + num + " "*(10-len(num)) + str(t)) - - print("----------- %d Deleted since last diff: ------------" % len(delRefs)) - self.report(delRefs, objs, **kargs) - print("----------- %d Created since last diff: ------------" % len(createRefs)) - self.report(createRefs, objs, **kargs) - print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) - self.report(persistentRefs, objs, **kargs) - - - def __del__(self): - self.startRefs.clear() - self.startCount.clear() - self.newRefs.clear() - self.persistentRefs.clear() - - del ObjTracker.allObjs[id(self)] - for v in self.objs: - del ObjTracker.allObjs[id(v)] - - @classmethod - def isObjVar(cls, o): - return type(o) is cls or id(o) in cls.allObjs - - def collect(self): - print("Collecting list of all objects...") - gc.collect() - objs = get_all_objects() - frame = sys._getframe() - del objs[id(frame)] ## ignore the current frame - del objs[id(frame.f_code)] - - ignoreTypes = [int] - refs = {} - count = {} - for k in objs: - o = objs[k] - typ = type(o) - oid = id(o) - if ObjTracker.isObjVar(o) or typ in ignoreTypes: - continue - - try: - ref = weakref.ref(obj) - except: - ref = None - refs[oid] = ref - typ = type(o) - typStr = typeStr(o) - self.objTypes[oid] = typStr - ObjTracker.allObjs[id(typStr)] = None - count[typ] = count.get(typ, 0) + 1 - - print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) - return refs, count, objs - - def forgetRef(self, ref): - if ref is not None: - del ObjTracker.allObjs[id(ref)] - - def rememberRef(self, ref): - ## Record the address of the weakref object so it is not included in future object counts. - if ref is not None: - ObjTracker.allObjs[id(ref)] = None - - - def lookup(self, oid, ref, objs=None): - if ref is None or ref() is None: - try: - obj = lookup(oid, objects=objs) - except: - obj = None - else: - obj = ref() - return obj - - - def report(self, refs, allobjs=None, showIDs=False): - if allobjs is None: - allobjs = get_all_objects() - - count = {} - rev = {} - for oid in refs: - obj = self.lookup(oid, refs[oid], allobjs) - if obj is None: - typ = "[del] " + self.objTypes[oid] - else: - typ = typeStr(obj) - if typ not in rev: - rev[typ] = [] - rev[typ].append(oid) - c = count.get(typ, [0,0]) - count[typ] = [c[0]+1, c[1]+objectSize(obj)] - typs = list(count.keys()) - typs.sort(key=lambda a: count[a][1]) - - for t in typs: - line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) - if showIDs: - line += "\t"+",".join(map(str,rev[t])) - print(line) - - def findTypes(self, refs, regex): - allObjs = get_all_objects() - ids = {} - objs = [] - r = re.compile(regex) - for k in refs: - if r.search(self.objTypes[k]): - objs.append(self.lookup(k, refs[k], allObjs)) - return objs - - - - -def describeObj(obj, depth=4, path=None, ignore=None): - """ - Trace all reference paths backward, printing a list of different ways this object can be accessed. - Attempts to answer the question "who has a reference to this object" - """ - if path is None: - path = [obj] - if ignore is None: - ignore = {} ## holds IDs of objects used within the function. - ignore[id(sys._getframe())] = None - ignore[id(path)] = None - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - printed=False - for ref in refs: - if id(ref) in ignore: - continue - if id(ref) in list(map(id, path)): - print("Cyclic reference: " + refPathString([ref]+path)) - printed = True - continue - newPath = [ref]+path - if len(newPath) >= depth: - refStr = refPathString(newPath) - if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell - print(refStr) - printed = True - else: - describeObj(ref, depth, newPath, ignore) - printed = True - if not printed: - print("Dead end: " + refPathString(path)) - - - -def typeStr(obj): - """Create a more useful type string by making types report their class.""" - typ = type(obj) - if typ == getattr(types, 'InstanceType', None): - return "" % obj.__class__.__name__ - else: - return str(typ) - -def searchRefs(obj, *args): - """Pseudo-interactive function for tracing references backward. - **Arguments:** - - obj: The initial object from which to start searching - args: A set of string or int arguments. - each integer selects one of obj's referrers to be the new 'obj' - each string indicates an action to take on the current 'obj': - t: print the types of obj's referrers - l: print the lengths of obj's referrers (if they have __len__) - i: print the IDs of obj's referrers - o: print obj - ro: return obj - rr: return list of obj's referrers - - Examples:: - - searchRefs(obj, 't') ## Print types of all objects referring to obj - searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers - searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers - searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object - - """ - ignore = {id(sys._getframe()): None} - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - refs = [r for r in refs if id(r) not in ignore] - for a in args: - - #fo = allFrameObjs() - #refs = [r for r in refs if r not in fo] - - if type(a) is int: - obj = refs[a] - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - refs = [r for r in refs if id(r) not in ignore] - elif a == 't': - print(list(map(typeStr, refs))) - elif a == 'i': - print(list(map(id, refs))) - elif a == 'l': - def slen(o): - if hasattr(o, '__len__'): - return len(o) - else: - return None - print(list(map(slen, refs))) - elif a == 'o': - print(obj) - elif a == 'ro': - return obj - elif a == 'rr': - return refs - -def allFrameObjs(): - """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" - f = sys._getframe() - objs = [] - while f is not None: - objs.append(f) - objs.append(f.f_code) - #objs.append(f.f_locals) - #objs.append(f.f_globals) - #objs.append(f.f_builtins) - f = f.f_back - return objs - - -def findObj(regex): - """Return a list of objects whose typeStr matches regex""" - allObjs = get_all_objects() - objs = [] - r = re.compile(regex) - for i in allObjs: - obj = allObjs[i] - if r.search(typeStr(obj)): - objs.append(obj) - return objs - - - -def listRedundantModules(): - """List modules that have been imported more than once via different paths.""" - mods = {} - for name, mod in sys.modules.items(): - if not hasattr(mod, '__file__'): - continue - mfile = os.path.abspath(mod.__file__) - if mfile[-1] == 'c': - mfile = mfile[:-1] - if mfile in mods: - print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) - else: - mods[mfile] = name - - -def walkQObjectTree(obj, counts=None, verbose=False, depth=0): - """ - Walk through a tree of QObjects, doing nothing to them. - The purpose of this function is to find dead objects and generate a crash - immediately rather than stumbling upon them later. - Prints a count of the objects encountered, for fun. (or is it?) - """ - - if verbose: - print(" "*depth + typeStr(obj)) - report = False - if counts is None: - counts = {} - report = True - typ = str(type(obj)) - try: - counts[typ] += 1 - except KeyError: - counts[typ] = 1 - for child in obj.children(): - walkQObjectTree(child, counts, verbose, depth+1) - - return counts - -QObjCache = {} -def qObjectReport(verbose=False): - """Generate a report counting all QObjects and their types""" - global qObjCache - count = {} - for obj in findObj('PyQt'): - if isinstance(obj, QtCore.QObject): - oid = id(obj) - if oid not in QObjCache: - QObjCache[oid] = typeStr(obj) + " " + obj.objectName() - try: - QObjCache[oid] += " " + obj.parent().objectName() - QObjCache[oid] += " " + obj.text() - except: - pass - print("check obj", oid, str(QObjCache[oid])) - if obj.parent() is None: - walkQObjectTree(obj, count, verbose) - - typs = list(count.keys()) - typs.sort() - for t in typs: - print(count[t], "\t", t) - - -class PrintDetector(object): - """Find code locations that print to stdout.""" - def __init__(self): - self.stdout = sys.stdout - sys.stdout = self - - def remove(self): - sys.stdout = self.stdout - - def __del__(self): - self.remove() - - def write(self, x): - self.stdout.write(x) - traceback.print_stack() - - def flush(self): - self.stdout.flush() - - -def listQThreads(): - """Prints Thread IDs (Qt's, not OS's) for all QThreads.""" - thr = findObj('[Tt]hread') - thr = [t for t in thr if isinstance(t, QtCore.QThread)] - try: - from PyQt5 import sip - except ImportError: - import sip - for t in thr: - print("--> ", t) - print(" Qt ID: 0x%x" % sip.unwrapinstance(t)) - - -def pretty(data, indent=''): - """Format nested dict/list/tuple structures into a more human-readable string - This function is a bit better than pprint for displaying OrderedDicts. - """ - ret = "" - ind2 = indent + " " - if isinstance(data, dict): - ret = indent+"{\n" - for k, v in data.items(): - ret += ind2 + repr(k) + ": " + pretty(v, ind2).strip() + "\n" - ret += indent+"}\n" - elif isinstance(data, list) or isinstance(data, tuple): - s = repr(data) - if len(s) < 40: - ret += indent + s - else: - if isinstance(data, list): - d = '[]' - else: - d = '()' - ret = indent+d[0]+"\n" - for i, v in enumerate(data): - ret += ind2 + str(i) + ": " + pretty(v, ind2).strip() + "\n" - ret += indent+d[1]+"\n" - else: - ret += indent + repr(data) - return ret - - -class ThreadTrace(object): - """ - Used to debug freezing by starting a new thread that reports on the - location of other threads periodically. - """ - def __init__(self, interval=10.0): - self.interval = interval - self.lock = Mutex() - self._stop = False - self.start() - - def stop(self): - with self.lock: - self._stop = True - - def start(self, interval=None): - if interval is not None: - self.interval = interval - self._stop = False - self.thread = threading.Thread(target=self.run) - self.thread.daemon = True - self.thread.start() - - def run(self): - while True: - with self.lock: - if self._stop is True: - return - - print("\n============= THREAD FRAMES: ================") - for id, frame in sys._current_frames().items(): - if id == threading.current_thread().ident: - continue - - # try to determine a thread name - try: - name = threading._active.get(id, None) - except: - name = None - if name is None: - try: - # QThread._names must be manually set by thread creators. - name = QtCore.QThread._names.get(id) - except: - name = None - if name is None: - name = "???" - - print("<< thread %d \"%s\" >>" % (id, name)) - traceback.print_stack(frame) - print("===============================================\n") - - time.sleep(self.interval) - - -class ThreadColor(object): - """ - Wrapper on stdout/stderr that colors text by the current thread ID. - - *stream* must be 'stdout' or 'stderr'. - """ - colors = {} - lock = Mutex() - - def __init__(self, stream): - self.stream = getattr(sys, stream) - self.err = stream == 'stderr' - setattr(sys, stream, self) - - def write(self, msg): - with self.lock: - cprint.cprint(self.stream, self.color(), msg, -1, stderr=self.err) - - def flush(self): - with self.lock: - self.stream.flush() - - def color(self): - tid = threading.current_thread() - if tid not in self.colors: - c = (len(self.colors) % 15) + 1 - self.colors[tid] = c - return self.colors[tid] - - -def enableFaulthandler(): - """ Enable faulthandler for all threads. - - If the faulthandler package is available, this function disables and then - re-enables fault handling for all threads (this is necessary to ensure any - new threads are handled correctly), and returns True. - - If faulthandler is not available, then returns False. - """ - try: - import faulthandler - # necessary to disable first or else new threads may not be handled. - faulthandler.disable() - faulthandler.enable(all_threads=True) - return True - except ImportError: - return False - diff --git a/pyqtgraph/dockarea/Container.py b/pyqtgraph/dockarea/Container.py deleted file mode 100644 index 04b775f..0000000 --- a/pyqtgraph/dockarea/Container.py +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui, QtWidgets -import weakref - -class Container(object): - #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. - - def __init__(self, area): - object.__init__(self) - self.area = area - self._container = None - self._stretch = (10, 10) - self.stretches = weakref.WeakKeyDictionary() - - def container(self): - return self._container - - def containerChanged(self, c): - self._container = c - if c is None: - self.area = None - else: - self.area = c.area - - def type(self): - return None - - def insert(self, new, pos=None, neighbor=None): - if not isinstance(new, list): - new = [new] - for n in new: - # remove from existing parent first - n.setParent(None) - if neighbor is None: - if pos == 'before': - index = 0 - else: - index = self.count() - else: - index = self.indexOf(neighbor) - if index == -1: - index = 0 - if pos == 'after': - index += 1 - - for n in new: - #print "insert", n, " -> ", self, index - self._insertItem(n, index) - #print "change container", n, " -> ", self - n.containerChanged(self) - index += 1 - n.sigStretchChanged.connect(self.childStretchChanged) - #print "child added", self - self.updateStretch() - - def apoptose(self, propagate=True): - # if there is only one (or zero) item in this container, disappear. - # if propagate is True, then also attempt to apoptose parent containers. - cont = self._container - c = self.count() - if c > 1: - return - if c == 1: ## if there is one item, give it to the parent container (unless this is the top) - ch = self.widget(0) - if (self.area is not None and self is self.area.topContainer and not isinstance(ch, Container)) or self.container() is None: - return - self.container().insert(ch, 'before', self) - #print "apoptose:", self - self.close() - if propagate and cont is not None: - cont.apoptose() - - def close(self): - self.setParent(None) - if self.area is not None and self.area.topContainer is self: - self.area.topContainer = None - self.containerChanged(None) - - def childEvent_(self, ev): - # NOTE: this method has been renamed to avoid having the same method name as - # QSplitter.childEvent() - # this causes problems for PyQt6 since SplitContainer inherits from - # Container and QSplitter. - ch = ev.child() - if ev.removed() and hasattr(ch, 'sigStretchChanged'): - #print "Child", ev.child(), "removed, updating", self - try: - ch.sigStretchChanged.disconnect(self.childStretchChanged) - except: - pass - self.updateStretch() - - def childStretchChanged(self): - #print "child", QtCore.QObject.sender(self), "changed shape, updating", self - self.updateStretch() - - def setStretch(self, x=None, y=None): - #print "setStretch", self, x, y - self._stretch = (x, y) - self.sigStretchChanged.emit() - - def updateStretch(self): - ###Set the stretch values for this container to reflect its contents - pass - - def stretch(self): - """Return the stretch factors for this container""" - return self._stretch - - -class SplitContainer(Container, QtGui.QSplitter): - """Horizontal or vertical splitter with some changes: - - save/restore works correctly - """ - sigStretchChanged = QtCore.Signal() - - def __init__(self, area, orientation): - QtGui.QSplitter.__init__(self) - self.setOrientation(orientation) - Container.__init__(self, area) - #self.splitterMoved.connect(self.restretchChildren) - - def _insertItem(self, item, index): - self.insertWidget(index, item) - item.show() ## need to show since it may have been previously hidden by tab - - def saveState(self): - sizes = self.sizes() - if all([x == 0 for x in sizes]): - sizes = [10] * len(sizes) - return {'sizes': sizes} - - def restoreState(self, state): - sizes = state['sizes'] - self.setSizes(sizes) - for i in range(len(sizes)): - self.setStretchFactor(i, sizes[i]) - - def childEvent(self, ev): - super().childEvent(ev) # call QSplitter.childEvent() - Container.childEvent_(self, ev) - - #def restretchChildren(self): - #sizes = self.sizes() - #tot = sum(sizes) - - - - -class HContainer(SplitContainer): - def __init__(self, area): - SplitContainer.__init__(self, area, QtCore.Qt.Horizontal) - - def type(self): - return 'horizontal' - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - #print "updateStretch", self - x = 0 - y = 0 - sizes = [] - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - x += wx - y = max(y, wy) - sizes.append(wx) - #print " child", self.widget(i), wx, wy - self.setStretch(x, y) - #print sizes - - tot = float(sum(sizes)) - if tot == 0: - scale = 1.0 - else: - scale = self.width() / tot - self.setSizes([int(s*scale) for s in sizes]) - - - -class VContainer(SplitContainer): - def __init__(self, area): - SplitContainer.__init__(self, area, QtCore.Qt.Vertical) - - def type(self): - return 'vertical' - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - #print "updateStretch", self - x = 0 - y = 0 - sizes = [] - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - y += wy - x = max(x, wx) - sizes.append(wy) - #print " child", self.widget(i), wx, wy - self.setStretch(x, y) - - #print sizes - tot = float(sum(sizes)) - if tot == 0: - scale = 1.0 - else: - scale = self.height() / tot - self.setSizes([int(s*scale) for s in sizes]) - - -class StackedWidget(QtWidgets.QStackedWidget): - def __init__(self, *, container): - super().__init__() - self.container = container - - def childEvent(self, ev): - super().childEvent(ev) - self.container.childEvent_(ev) - - -class TContainer(Container, QtGui.QWidget): - sigStretchChanged = QtCore.Signal() - def __init__(self, area): - QtGui.QWidget.__init__(self) - Container.__init__(self, area) - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.layout.setContentsMargins(0,0,0,0) - self.setLayout(self.layout) - - self.hTabLayout = QtGui.QHBoxLayout() - self.hTabBox = QtGui.QWidget() - self.hTabBox.setLayout(self.hTabLayout) - self.hTabLayout.setSpacing(2) - self.hTabLayout.setContentsMargins(0,0,0,0) - self.layout.addWidget(self.hTabBox, 0, 1) - - self.stack = StackedWidget(container=self) - self.layout.addWidget(self.stack, 1, 1) - - - self.setLayout(self.layout) - for n in ['count', 'widget', 'indexOf']: - setattr(self, n, getattr(self.stack, n)) - - - def _insertItem(self, item, index): - if not isinstance(item, Dock.Dock): - raise Exception("Tab containers may hold only docks, not other containers.") - self.stack.insertWidget(index, item) - self.hTabLayout.insertWidget(index, item.label) - #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) - item.label.sigClicked.connect(self.tabClicked) - self.tabClicked(item.label) - - def tabClicked(self, tab, ev=None): - if ev is None or ev.button() == QtCore.Qt.LeftButton: - for i in range(self.count()): - w = self.widget(i) - if w is tab.dock: - w.label.setDim(False) - self.stack.setCurrentIndex(i) - else: - w.label.setDim(True) - - def raiseDock(self, dock): - """Move *dock* to the top of the stack""" - self.stack.currentWidget().label.setDim(True) - self.stack.setCurrentWidget(dock) - dock.label.setDim(False) - - - def type(self): - return 'tab' - - def saveState(self): - return {'index': self.stack.currentIndex()} - - def restoreState(self, state): - self.stack.setCurrentIndex(state['index']) - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - x = 0 - y = 0 - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - x = max(x, wx) - y = max(y, wy) - self.setStretch(x, y) - -from . import Dock diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py deleted file mode 100644 index 7a5e09d..0000000 --- a/pyqtgraph/dockarea/Dock.py +++ /dev/null @@ -1,363 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui - -from .DockDrop import * -from ..widgets.VerticalLabel import VerticalLabel -from ..python2_3 import asUnicode - - -class Dock(QtGui.QWidget, DockDrop): - - sigStretchChanged = QtCore.Signal() - sigClosed = QtCore.Signal(object) - - def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False, fontSize="12px"): - QtGui.QWidget.__init__(self) - DockDrop.__init__(self) - self._container = None - self._name = name - self.area = area - self.label = DockLabel(name, self, closable, fontSize) - if closable: - self.label.sigCloseClicked.connect(self.close) - self.labelHidden = False - self.moveLabel = True ## If false, the dock is no longer allowed to move the label. - self.autoOrient = autoOrientation - self.orientation = 'horizontal' - #self.label.setAlignment(QtCore.Qt.AlignHCenter) - self.topLayout = QtGui.QGridLayout() - self.topLayout.setContentsMargins(0, 0, 0, 0) - self.topLayout.setSpacing(0) - self.setLayout(self.topLayout) - self.topLayout.addWidget(self.label, 0, 1) - self.widgetArea = QtGui.QWidget() - self.topLayout.addWidget(self.widgetArea, 1, 1) - self.layout = QtGui.QGridLayout() - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(0) - self.widgetArea.setLayout(self.layout) - self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.widgets = [] - self._container = None - self.currentRow = 0 - #self.titlePos = 'top' - self.raiseOverlay() - self.hStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - border-top-left-radius: 0px; - border-top-right-radius: 0px; - border-top-width: 0px; - }""" - self.vStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0px; - }""" - self.nStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - }""" - self.dragStyle = """ - Dock > QWidget { - border: 4px solid #00F; - border-radius: 5px; - }""" - self.setAutoFillBackground(False) - self.widgetArea.setStyleSheet(self.hStyle) - - self.setStretch(*size) - - if widget is not None: - self.addWidget(widget) - - if hideTitle: - self.hideTitleBar() - - def implements(self, name=None): - if name is None: - return ['dock'] - else: - return name == 'dock' - - def setStretch(self, x=None, y=None): - """ - Set the 'target' size for this Dock. - The actual size will be determined by comparing this Dock's - stretch value to the rest of the docks it shares space with. - """ - if x is None: - x = 0 - if y is None: - y = 0 - self._stretch = (x, y) - self.sigStretchChanged.emit() - - def stretch(self): - return self._stretch - - def hideTitleBar(self): - """ - Hide the title bar for this Dock. - This will prevent the Dock being moved by the user. - """ - self.label.hide() - self.labelHidden = True - if 'center' in self.allowedAreas: - self.allowedAreas.remove('center') - self.updateStyle() - - def showTitleBar(self): - """ - Show the title bar for this Dock. - """ - self.label.show() - self.labelHidden = False - self.allowedAreas.add('center') - self.updateStyle() - - def title(self): - """ - Gets the text displayed in the title bar for this dock. - """ - return asUnicode(self.label.text()) - - def setTitle(self, text): - """ - Sets the text displayed in title bar for this Dock. - """ - self.label.setText(text) - - def setOrientation(self, o='auto', force=False): - """ - Sets the orientation of the title bar for this Dock. - Must be one of 'auto', 'horizontal', or 'vertical'. - By default ('auto'), the orientation is determined - based on the aspect ratio of the Dock. - """ - # setOrientation may be called before the container is set in some cases - # (via resizeEvent), so there's no need to do anything here until called - # again by containerChanged - if self.container() is None: - return - - if o == 'auto' and self.autoOrient: - if self.container().type() == 'tab': - o = 'horizontal' - elif self.width() > self.height()*1.5: - o = 'vertical' - else: - o = 'horizontal' - if force or self.orientation != o: - self.orientation = o - self.label.setOrientation(o) - self.updateStyle() - - def updateStyle(self): - ## updates orientation and appearance of title bar - if self.labelHidden: - self.widgetArea.setStyleSheet(self.nStyle) - elif self.orientation == 'vertical': - self.label.setOrientation('vertical') - if self.moveLabel: - self.topLayout.addWidget(self.label, 1, 0) - self.widgetArea.setStyleSheet(self.vStyle) - else: - self.label.setOrientation('horizontal') - if self.moveLabel: - self.topLayout.addWidget(self.label, 0, 1) - self.widgetArea.setStyleSheet(self.hStyle) - - def resizeEvent(self, ev): - self.setOrientation() - self.resizeOverlay(self.size()) - - def name(self): - return self._name - - def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): - """ - Add a new widget to the interior of this Dock. - Each Dock uses a QGridLayout to arrange widgets within. - """ - if row is None: - row = self.currentRow - self.currentRow = max(row+1, self.currentRow) - self.widgets.append(widget) - self.layout.addWidget(widget, row, col, rowspan, colspan) - self.raiseOverlay() - - def startDrag(self): - self.drag = QtGui.QDrag(self) - mime = QtCore.QMimeData() - self.drag.setMimeData(mime) - self.widgetArea.setStyleSheet(self.dragStyle) - self.update() - action = self.drag.exec_() if hasattr(self.drag, 'exec_') else self.drag.exec() - self.updateStyle() - - def float(self): - self.area.floatDock(self) - - def container(self): - return self._container - - def containerChanged(self, c): - if self._container is not None: - # ask old container to close itself if it is no longer needed - self._container.apoptose() - self._container = c - if c is None: - self.area = None - else: - self.area = c.area - if c.type() != 'tab': - self.moveLabel = True - self.label.setDim(False) - else: - self.moveLabel = False - - self.setOrientation(force=True) - - def raiseDock(self): - """If this Dock is stacked underneath others, raise it to the top.""" - self.container().raiseDock(self) - - def close(self): - """Remove this dock from the DockArea it lives inside.""" - self.setParent(None) - QtGui.QLabel.close(self.label) - self.label.setParent(None) - self._container.apoptose() - self._container = None - self.sigClosed.emit(self) - - def __repr__(self): - return "" % (self.name(), self.stretch()) - - ## PySide bug: We need to explicitly redefine these methods - ## or else drag/drop events will not be delivered. - def dragEnterEvent(self, *args): - DockDrop.dragEnterEvent(self, *args) - - def dragMoveEvent(self, *args): - DockDrop.dragMoveEvent(self, *args) - - def dragLeaveEvent(self, *args): - DockDrop.dragLeaveEvent(self, *args) - - def dropEvent(self, *args): - DockDrop.dropEvent(self, *args) - - -class DockLabel(VerticalLabel): - - sigClicked = QtCore.Signal(object, object) - sigCloseClicked = QtCore.Signal() - - def __init__(self, text, dock, showCloseButton, fontSize): - self.dim = False - self.fixedWidth = False - self.fontSize = fontSize - VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) - self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter) - self.dock = dock - self.updateStyle() - self.setAutoFillBackground(False) - self.mouseMoved = False - - self.closeButton = None - if showCloseButton: - self.closeButton = QtGui.QToolButton(self) - self.closeButton.clicked.connect(self.sigCloseClicked) - self.closeButton.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton)) - - def updateStyle(self): - r = '3px' - if self.dim: - fg = '#aaa' - bg = '#44a' - border = '#339' - else: - fg = '#fff' - bg = '#66c' - border = '#55B' - - if self.orientation == 'vertical': - self.vStyle = """DockLabel { - background-color : %s; - color : %s; - border-top-right-radius: 0px; - border-top-left-radius: %s; - border-bottom-right-radius: 0px; - border-bottom-left-radius: %s; - border-width: 0px; - border-right: 2px solid %s; - padding-top: 3px; - padding-bottom: 3px; - font-size: %s; - }""" % (bg, fg, r, r, border, self.fontSize) - self.setStyleSheet(self.vStyle) - else: - self.hStyle = """DockLabel { - background-color : %s; - color : %s; - border-top-right-radius: %s; - border-top-left-radius: %s; - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; - border-width: 0px; - border-bottom: 2px solid %s; - padding-left: 3px; - padding-right: 3px; - font-size: %s; - }""" % (bg, fg, r, r, border, self.fontSize) - self.setStyleSheet(self.hStyle) - - def setDim(self, d): - if self.dim != d: - self.dim = d - self.updateStyle() - - def setOrientation(self, o): - VerticalLabel.setOrientation(self, o) - self.updateStyle() - - def mousePressEvent(self, ev): - self.pressPos = ev.localPos() - self.mouseMoved = False - ev.accept() - - def mouseMoveEvent(self, ev): - if not self.mouseMoved: - self.mouseMoved = (ev.localPos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance() - - if self.mouseMoved and ev.buttons() == QtCore.Qt.LeftButton: - self.dock.startDrag() - ev.accept() - - def mouseReleaseEvent(self, ev): - ev.accept() - if not self.mouseMoved: - self.sigClicked.emit(self, ev) - - def mouseDoubleClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - self.dock.float() - - def resizeEvent (self, ev): - if self.closeButton: - if self.orientation == 'vertical': - size = ev.size().width() - pos = QtCore.QPoint(0, 0) - else: - size = ev.size().height() - pos = QtCore.QPoint(ev.size().width() - size, 0) - self.closeButton.setFixedSize(QtCore.QSize(size, size)) - self.closeButton.move(pos) - super(DockLabel,self).resizeEvent(ev) diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py deleted file mode 100644 index ee9a5f5..0000000 --- a/pyqtgraph/dockarea/DockArea.py +++ /dev/null @@ -1,385 +0,0 @@ -# -*- coding: utf-8 -*- -import weakref -from ..Qt import QtCore, QtGui -from .Container import * -from .DockDrop import * -from .Dock import Dock -from .. import debug as debug -from ..python2_3 import basestring - - -class DockArea(Container, QtGui.QWidget, DockDrop): - def __init__(self, parent=None, temporary=False, home=None): - Container.__init__(self, self) - QtGui.QWidget.__init__(self, parent=parent) - DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) - self.layout = QtGui.QVBoxLayout() - self.layout.setContentsMargins(0,0,0,0) - self.layout.setSpacing(0) - self.setLayout(self.layout) - self.docks = weakref.WeakValueDictionary() - self.topContainer = None - self.raiseOverlay() - self.temporary = temporary - self.tempAreas = [] - self.home = home - - def type(self): - return "top" - - def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): - """Adds a dock to this area. - - ============== ================================================================= - **Arguments:** - dock The new Dock object to add. If None, then a new Dock will be - created. - position 'bottom', 'top', 'left', 'right', 'above', or 'below' - relativeTo If relativeTo is None, then the new Dock is added to fill an - entire edge of the window. If relativeTo is another Dock, then - the new Dock is placed adjacent to it (or in a tabbed - configuration for 'above' and 'below'). - ============== ================================================================= - - All extra keyword arguments are passed to Dock.__init__() if *dock* is - None. - """ - if dock is None: - dock = Dock(**kwds) - - # store original area that the dock will return to when un-floated - if not self.temporary: - dock.orig_area = self - - - ## Determine the container to insert this dock into. - ## If there is no neighbor, then the container is the top. - if relativeTo is None or relativeTo is self: - if self.topContainer is None: - container = self - neighbor = None - else: - container = self.topContainer - neighbor = None - else: - if isinstance(relativeTo, basestring): - relativeTo = self.docks[relativeTo] - container = self.getContainer(relativeTo) - if container is None: - raise TypeError("Dock %s is not contained in a DockArea; cannot add another dock relative to it." % relativeTo) - neighbor = relativeTo - - ## what container type do we need? - neededContainer = { - 'bottom': 'vertical', - 'top': 'vertical', - 'left': 'horizontal', - 'right': 'horizontal', - 'above': 'tab', - 'below': 'tab' - }[position] - - ## Can't insert new containers into a tab container; insert outside instead. - if neededContainer != container.type() and container.type() == 'tab': - neighbor = container - container = container.container() - - ## Decide if the container we have is suitable. - ## If not, insert a new container inside. - if neededContainer != container.type(): - if neighbor is None: - container = self.addContainer(neededContainer, self.topContainer) - else: - container = self.addContainer(neededContainer, neighbor) - - ## Insert the new dock before/after its neighbor - insertPos = { - 'bottom': 'after', - 'top': 'before', - 'left': 'before', - 'right': 'after', - 'above': 'before', - 'below': 'after' - }[position] - #print "request insert", dock, insertPos, neighbor - old = dock.container() - container.insert(dock, insertPos, neighbor) - self.docks[dock.name()] = dock - if old is not None: - old.apoptose() - - return dock - - def moveDock(self, dock, position, neighbor): - """ - Move an existing Dock to a new location. - """ - ## Moving to the edge of a tabbed dock causes a drop outside the tab box - if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': - neighbor = neighbor.container() - self.addDock(dock, position, neighbor) - - def getContainer(self, obj): - if obj is None: - return self - return obj.container() - - def makeContainer(self, typ): - if typ == 'vertical': - new = VContainer(self) - elif typ == 'horizontal': - new = HContainer(self) - elif typ == 'tab': - new = TContainer(self) - return new - - def addContainer(self, typ, obj): - """Add a new container around obj""" - new = self.makeContainer(typ) - - container = self.getContainer(obj) - container.insert(new, 'before', obj) - #print "Add container:", new, " -> ", container - if obj is not None: - new.insert(obj) - self.raiseOverlay() - return new - - def insert(self, new, pos=None, neighbor=None): - if self.topContainer is not None: - # Adding new top-level container; addContainer() should - # take care of giving the old top container a new home. - self.topContainer.containerChanged(None) - self.layout.addWidget(new) - new.containerChanged(self) - self.topContainer = new - self.raiseOverlay() - - def count(self): - if self.topContainer is None: - return 0 - return 1 - - def resizeEvent(self, ev): - self.resizeOverlay(self.size()) - - def addTempArea(self): - if self.home is None: - area = DockArea(temporary=True, home=self) - self.tempAreas.append(area) - win = TempAreaWindow(area) - area.win = win - win.show() - else: - area = self.home.addTempArea() - #print "added temp area", area, area.window() - return area - - def floatDock(self, dock): - """Removes *dock* from this DockArea and places it in a new window.""" - area = self.addTempArea() - area.win.resize(dock.size()) - area.moveDock(dock, 'top', None) - - def removeTempArea(self, area): - self.tempAreas.remove(area) - #print "close window", area.window() - area.window().close() - - def saveState(self): - """ - Return a serialized (storable) representation of the state of - all Docks in this DockArea.""" - - if self.topContainer is None: - main = None - else: - main = self.childState(self.topContainer) - - state = {'main': main, 'float': []} - for a in self.tempAreas: - geo = a.win.geometry() - geo = (geo.x(), geo.y(), geo.width(), geo.height()) - state['float'].append((a.saveState(), geo)) - return state - - def childState(self, obj): - if isinstance(obj, Dock): - return ('dock', obj.name(), {}) - else: - childs = [] - for i in range(obj.count()): - childs.append(self.childState(obj.widget(i))) - return (obj.type(), childs, obj.saveState()) - - def restoreState(self, state, missing='error', extra='bottom'): - """ - Restore Dock configuration as generated by saveState. - - This function does not create any Docks--it will only - restore the arrangement of an existing set of Docks. - - By default, docks that are described in *state* but do not exist - in the dock area will cause an exception to be raised. This behavior - can be changed by setting *missing* to 'ignore' or 'create'. - - Extra docks that are in the dockarea but that are not mentioned in - *state* will be added to the bottom of the dockarea, unless otherwise - specified by the *extra* argument. - """ - - ## 1) make dict of all docks and list of existing containers - containers, docks = self.findAll() - oldTemps = self.tempAreas[:] - #print "found docks:", docks - - ## 2) create container structure, move docks into new containers - if state['main'] is not None: - self.buildFromState(state['main'], docks, self, missing=missing) - - ## 3) create floating areas, populate - for s in state['float']: - a = self.addTempArea() - a.buildFromState(s[0]['main'], docks, a, missing=missing) - a.win.setGeometry(*s[1]) - a.apoptose() # ask temp area to close itself if it is empty - - ## 4) Add any remaining docks to a float - for d in docks.values(): - if extra == 'float': - a = self.addTempArea() - a.addDock(d, 'below') - else: - self.moveDock(d, extra, None) - - #print "\nKill old containers:" - ## 5) kill old containers - for c in containers: - c.close() - for a in oldTemps: - a.apoptose() - - def buildFromState(self, state, docks, root, depth=0, missing='error'): - typ, contents, state = state - pfx = " " * depth - if typ == 'dock': - try: - obj = docks[contents] - del docks[contents] - except KeyError: - if missing == 'error': - raise Exception('Cannot restore dock state; no dock with name "%s"' % contents) - elif missing == 'create': - obj = Dock(name=contents) - elif missing == 'ignore': - return - else: - raise ValueError('"missing" argument must be one of "error", "create", or "ignore".') - - else: - obj = self.makeContainer(typ) - - root.insert(obj, 'after') - #print pfx+"Add:", obj, " -> ", root - - if typ != 'dock': - for o in contents: - self.buildFromState(o, docks, obj, depth+1, missing=missing) - # remove this container if possible. (there are valid situations when a restore will - # generate empty containers, such as when using missing='ignore') - obj.apoptose(propagate=False) - obj.restoreState(state) ## this has to be done later? - - def findAll(self, obj=None, c=None, d=None): - if obj is None: - obj = self.topContainer - - ## check all temp areas first - if c is None: - c = [] - d = {} - for a in self.tempAreas: - c1, d1 = a.findAll() - c.extend(c1) - d.update(d1) - - if isinstance(obj, Dock): - d[obj.name()] = obj - elif obj is not None: - c.append(obj) - for i in range(obj.count()): - o2 = obj.widget(i) - c2, d2 = self.findAll(o2) - c.extend(c2) - d.update(d2) - return (c, d) - - def apoptose(self, propagate=True): - # remove top container if possible, close this area if it is temporary. - #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() - if self.topContainer is None or self.topContainer.count() == 0: - self.topContainer = None - if self.temporary and self.home is not None: - self.home.removeTempArea(self) - #self.close() - - def clear(self): - docks = self.findAll()[1] - for dock in docks.values(): - dock.close() - - ## PySide bug: We need to explicitly redefine these methods - ## or else drag/drop events will not be delivered. - def dragEnterEvent(self, *args): - DockDrop.dragEnterEvent(self, *args) - - def dragMoveEvent(self, *args): - DockDrop.dragMoveEvent(self, *args) - - def dragLeaveEvent(self, *args): - DockDrop.dragLeaveEvent(self, *args) - - def dropEvent(self, *args): - DockDrop.dropEvent(self, *args) - - def printState(self, state=None, name='Main'): - # for debugging - if state is None: - state = self.saveState() - print("=== %s dock area ===" % name) - if state['main'] is None: - print(" (empty)") - else: - self._printAreaState(state['main']) - for i, float in enumerate(state['float']): - self.printState(float[0], name='float %d' % i) - - def _printAreaState(self, area, indent=0): - if area[0] == 'dock': - print(" " * indent + area[0] + " " + str(area[1:])) - return - else: - print(" " * indent + area[0]) - for ch in area[1]: - self._printAreaState(ch, indent+1) - - - -class TempAreaWindow(QtGui.QWidget): - def __init__(self, area, **kwargs): - QtGui.QWidget.__init__(self, **kwargs) - self.layout = QtGui.QGridLayout() - self.setLayout(self.layout) - self.layout.setContentsMargins(0, 0, 0, 0) - self.dockarea = area - self.layout.addWidget(area) - - def closeEvent(self, *args): - # restore docks to their original area - docks = self.dockarea.findAll()[1] - for dock in docks.values(): - if hasattr(dock, 'orig_area'): - dock.orig_area.addDock(dock, ) - # clear dock area, and close remaining docks - self.dockarea.clear() - super().closeEvent(*args) diff --git a/pyqtgraph/dockarea/DockDrop.py b/pyqtgraph/dockarea/DockDrop.py deleted file mode 100644 index b7f0c2d..0000000 --- a/pyqtgraph/dockarea/DockDrop.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui - -class DockDrop(object): - """Provides dock-dropping methods""" - def __init__(self, allowedAreas=None): - object.__init__(self) - if allowedAreas is None: - allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] - self.allowedAreas = set(allowedAreas) - self.setAcceptDrops(True) - self.dropArea = None - self.overlay = DropAreaOverlay(self) - self.overlay.raise_() - - def resizeOverlay(self, size): - self.overlay.resize(size) - - def raiseOverlay(self): - self.overlay.raise_() - - def dragEnterEvent(self, ev): - src = ev.source() - if hasattr(src, 'implements') and src.implements('dock'): - #print "drag enter accept" - ev.accept() - else: - #print "drag enter ignore" - ev.ignore() - - def dragMoveEvent(self, ev): - #print "drag move" - # QDragMoveEvent inherits QDropEvent which provides posF() - # PyQt6 provides only position() - posF = ev.posF() if hasattr(ev, 'posF') else ev.position() - ld = posF.x() - rd = self.width() - ld - td = posF.y() - bd = self.height() - td - - mn = min(ld, rd, td, bd) - if mn > 30: - self.dropArea = "center" - elif (ld == mn or td == mn) and mn > self.height()/3.: - self.dropArea = "center" - elif (rd == mn or ld == mn) and mn > self.width()/3.: - self.dropArea = "center" - - elif rd == mn: - self.dropArea = "right" - elif ld == mn: - self.dropArea = "left" - elif td == mn: - self.dropArea = "top" - elif bd == mn: - self.dropArea = "bottom" - - if ev.source() is self and self.dropArea == 'center': - #print " no self-center" - self.dropArea = None - ev.ignore() - elif self.dropArea not in self.allowedAreas: - #print " not allowed" - self.dropArea = None - ev.ignore() - else: - #print " ok" - ev.accept() - self.overlay.setDropArea(self.dropArea) - - def dragLeaveEvent(self, ev): - self.dropArea = None - self.overlay.setDropArea(self.dropArea) - - def dropEvent(self, ev): - area = self.dropArea - if area is None: - return - if area == 'center': - area = 'above' - self.area.moveDock(ev.source(), area, self) - self.dropArea = None - self.overlay.setDropArea(self.dropArea) - - - -class DropAreaOverlay(QtGui.QWidget): - """Overlay widget that draws drop areas during a drag-drop operation""" - - def __init__(self, parent): - QtGui.QWidget.__init__(self, parent) - self.dropArea = None - self.hide() - self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) - - def setDropArea(self, area): - self.dropArea = area - if area is None: - self.hide() - else: - ## Resize overlay to just the region where drop area should be displayed. - ## This works around a Qt bug--can't display transparent widgets over QGLWidget - prgn = self.parent().rect() - rgn = QtCore.QRect(prgn) - w = min(30, prgn.width()/3.) - h = min(30, prgn.height()/3.) - - if self.dropArea == 'left': - rgn.setWidth(w) - elif self.dropArea == 'right': - rgn.setLeft(rgn.left() + prgn.width() - w) - elif self.dropArea == 'top': - rgn.setHeight(h) - elif self.dropArea == 'bottom': - rgn.setTop(rgn.top() + prgn.height() - h) - elif self.dropArea == 'center': - rgn.adjust(w, h, -w, -h) - self.setGeometry(rgn) - self.show() - - self.update() - - def paintEvent(self, ev): - if self.dropArea is None: - return - p = QtGui.QPainter(self) - rgn = self.rect() - - p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) - p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) - p.drawRect(rgn) diff --git a/pyqtgraph/dockarea/__init__.py b/pyqtgraph/dockarea/__init__.py deleted file mode 100644 index f67c50c..0000000 --- a/pyqtgraph/dockarea/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .DockArea import DockArea -from .Dock import Dock \ No newline at end of file diff --git a/pyqtgraph/dockarea/__pycache__/Container.cpython-36.pyc b/pyqtgraph/dockarea/__pycache__/Container.cpython-36.pyc deleted file mode 100644 index 06b8253..0000000 Binary files a/pyqtgraph/dockarea/__pycache__/Container.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/dockarea/__pycache__/Dock.cpython-36.pyc b/pyqtgraph/dockarea/__pycache__/Dock.cpython-36.pyc deleted file mode 100644 index 4894661..0000000 Binary files a/pyqtgraph/dockarea/__pycache__/Dock.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/dockarea/__pycache__/DockArea.cpython-36.pyc b/pyqtgraph/dockarea/__pycache__/DockArea.cpython-36.pyc deleted file mode 100644 index 4682c0e..0000000 Binary files a/pyqtgraph/dockarea/__pycache__/DockArea.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/dockarea/__pycache__/DockDrop.cpython-36.pyc b/pyqtgraph/dockarea/__pycache__/DockDrop.cpython-36.pyc deleted file mode 100644 index 4d9169f..0000000 Binary files a/pyqtgraph/dockarea/__pycache__/DockDrop.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/dockarea/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/dockarea/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 749f116..0000000 Binary files a/pyqtgraph/dockarea/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/Arrow.py b/pyqtgraph/examples/Arrow.py deleted file mode 100644 index bd0b6b0..0000000 --- a/pyqtgraph/examples/Arrow.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Display an animated arrowhead following a curve. -This example uses the CurveArrow class, which is a combination -of ArrowItem and CurvePoint. - -To place a static arrow anywhere in a scene, use ArrowItem. -To attach other types of item to a curve, use CurvePoint. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = pg.mkQApp("Arrow Example") - -w = QtGui.QMainWindow() -cw = pg.GraphicsLayoutWidget() -w.show() -w.resize(400,600) -w.setCentralWidget(cw) -w.setWindowTitle('pyqtgraph example: Arrow') - -p = cw.addPlot(row=0, col=0) -p2 = cw.addPlot(row=1, col=0) - -## variety of arrow shapes -a1 = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}) -a2 = pg.ArrowItem(angle=-120, tipAngle=30, baseAngle=20, headLen=40, tailLen=40, tailWidth=8, pen=None, brush='y') -a3 = pg.ArrowItem(angle=-60, baseAngle=20, headLen=40, headWidth=20, tailLen=None, brush=None) -a4 = pg.ArrowItem(angle=-20, tipAngle=30, baseAngle=-30, headLen=40, tailLen=None) -a2.setPos(10,0) -a3.setPos(20,0) -a4.setPos(30,0) -p.addItem(a1) -p.addItem(a2) -p.addItem(a3) -p.addItem(a4) -p.setRange(QtCore.QRectF(-20, -10, 60, 20)) - - -## Animated arrow following curve -c = p2.plot(x=np.sin(np.linspace(0, 2*np.pi, 1000)), y=np.cos(np.linspace(0, 6*np.pi, 1000))) -a = pg.CurveArrow(c) -a.setStyle(headLen=40) -p2.addItem(a) -anim = a.makeAnimation(loop=-1) -anim.start() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/BarGraphItem.py b/pyqtgraph/examples/BarGraphItem.py deleted file mode 100644 index 6caa886..0000000 --- a/pyqtgraph/examples/BarGraphItem.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple example using BarGraphItem -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.plot() -win.setWindowTitle('pyqtgraph example: BarGraphItem') - -x = np.arange(10) -y1 = np.sin(x) -y2 = 1.1 * np.sin(x+1) -y3 = 1.2 * np.sin(x+2) - -bg1 = pg.BarGraphItem(x=x, height=y1, width=0.3, brush='r') -bg2 = pg.BarGraphItem(x=x+0.33, height=y2, width=0.3, brush='g') -bg3 = pg.BarGraphItem(x=x+0.66, height=y3, width=0.3, brush='b') - -win.addItem(bg1) -win.addItem(bg2) -win.addItem(bg3) - - -# Final example shows how to handle mouse clicks: -class BarGraph(pg.BarGraphItem): - def mouseClickEvent(self, event): - print("clicked") - - -bg = BarGraph(x=x, y=y1*0.3+2, height=0.4+y1*0.2, width=0.8) -win.addItem(bg) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/CLIexample.py b/pyqtgraph/examples/CLIexample.py deleted file mode 100644 index f32cf81..0000000 --- a/pyqtgraph/examples/CLIexample.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Display a plot and an image with minimal setup. - -pg.plot() and pg.image() are indended to be used from an interactive prompt -to allow easy data inspection (but note that PySide unfortunately does not -call the Qt event loop while the interactive prompt is running, in this case -it is necessary to call QApplication.exec_() to make the windows appear). -""" -import initExample ## Add path to library (just for examples; you do not need this) - - -import numpy as np -import pyqtgraph as pg - -data = np.random.normal(size=1000) -pg.plot(data, title="Simplest possible plotting example") - -data = np.random.normal(size=(500,500)) -pg.image(data, title="Simplest possible image example") - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'): - pg.QtGui.QApplication.exec_() diff --git a/pyqtgraph/examples/ColorButton.py b/pyqtgraph/examples/ColorButton.py deleted file mode 100644 index e9df975..0000000 --- a/pyqtgraph/examples/ColorButton.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple example demonstrating a button which displays a colored rectangle -and allows the user to select a new color by clicking on the button. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -app = pg.mkQApp("ColorButton Example") -win = QtGui.QMainWindow() -btn = pg.ColorButton() -win.setCentralWidget(btn) -win.show() -win.setWindowTitle('pyqtgraph example: ColorButton') - -def change(btn): - print("change", btn.color()) -def done(btn): - print("done", btn.color()) - -btn.sigColorChanging.connect(change) -btn.sigColorChanged.connect(done) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ConsoleWidget.py b/pyqtgraph/examples/ConsoleWidget.py deleted file mode 100644 index 8234269..0000000 --- a/pyqtgraph/examples/ConsoleWidget.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ConsoleWidget is used to allow execution of user-supplied python commands -in an application. It also includes a command history and functionality for trapping -and inspecting stack traces. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import pyqtgraph.console - -app = pg.mkQApp() - -## build an initial namespace for console commands to be executed in (this is optional; -## the user can always import these modules manually) -namespace = {'pg': pg, 'np': np} - -## initial text to display in the console -text = """ -This is an interactive python console. The numpy and pyqtgraph modules have already been imported -as 'np' and 'pg'. - -Go, play. -""" -c = pyqtgraph.console.ConsoleWidget(namespace=namespace, text=text) -c.show() -c.setWindowTitle('pyqtgraph example: ConsoleWidget') - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/CustomGraphItem.py b/pyqtgraph/examples/CustomGraphItem.py deleted file mode 100644 index 8e494c3..0000000 --- a/pyqtgraph/examples/CustomGraphItem.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple example of subclassing GraphItem. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -# Enable antialiasing for prettier plots -pg.setConfigOptions(antialias=True) - -w = pg.GraphicsLayoutWidget(show=True) -w.setWindowTitle('pyqtgraph example: CustomGraphItem') -v = w.addViewBox() -v.setAspectLocked() - -class Graph(pg.GraphItem): - def __init__(self): - self.dragPoint = None - self.dragOffset = None - self.textItems = [] - pg.GraphItem.__init__(self) - self.scatter.sigClicked.connect(self.clicked) - - def setData(self, **kwds): - self.text = kwds.pop('text', []) - self.data = kwds - if 'pos' in self.data: - npts = self.data['pos'].shape[0] - self.data['data'] = np.empty(npts, dtype=[('index', int)]) - self.data['data']['index'] = np.arange(npts) - self.setTexts(self.text) - self.updateGraph() - - def setTexts(self, text): - for i in self.textItems: - i.scene().removeItem(i) - self.textItems = [] - for t in text: - item = pg.TextItem(t) - self.textItems.append(item) - item.setParentItem(self) - - def updateGraph(self): - pg.GraphItem.setData(self, **self.data) - for i,item in enumerate(self.textItems): - item.setPos(*self.data['pos'][i]) - - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - return - - if ev.isStart(): - # We are already one step into the drag. - # Find the point(s) at the mouse cursor when the button was first - # pressed: - pos = ev.buttonDownPos() - pts = self.scatter.pointsAt(pos) - if len(pts) == 0: - ev.ignore() - return - self.dragPoint = pts[0] - ind = pts[0].data()[0] - self.dragOffset = self.data['pos'][ind] - pos - elif ev.isFinish(): - self.dragPoint = None - return - else: - if self.dragPoint is None: - ev.ignore() - return - - ind = self.dragPoint.data()[0] - self.data['pos'][ind] = ev.pos() + self.dragOffset - self.updateGraph() - ev.accept() - - def clicked(self, pts): - print("clicked: %s" % pts) - - -g = Graph() -v.addItem(g) - -## Define positions of nodes -pos = np.array([ - [0,0], - [10,0], - [0,10], - [10,10], - [5,5], - [15,5] - ], dtype=float) - -## Define the set of connections in the graph -adj = np.array([ - [0,1], - [1,3], - [3,2], - [2,0], - [1,5], - [3,5], - ]) - -## Define the symbol to use for each node (this is optional) -symbols = ['o','o','o','o','t','+'] - -## Define the line style for each connection (this is optional) -lines = np.array([ - (255,0,0,255,1), - (255,0,255,255,2), - (255,0,255,255,3), - (255,255,0,255,2), - (255,0,0,255,1), - (255,255,255,255,4), - ], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)]) - -## Define text to show next to each symbol -texts = ["Point %d" % i for i in range(6)] - -## Update the graph -g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False, text=texts) - - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/DataSlicing.py b/pyqtgraph/examples/DataSlicing.py deleted file mode 100644 index 8bd1e04..0000000 --- a/pyqtgraph/examples/DataSlicing.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrate a simple data-slicing task: given 3D data (displayed at top), select -a 2D plane and interpolate data along that plane to generate a slice image -(displayed at bottom). - - -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg - -app = pg.mkQApp("Data Slicing Example") - -## Create window with two ImageView widgets -win = QtGui.QMainWindow() -win.resize(800,800) -win.setWindowTitle('pyqtgraph example: DataSlicing') -cw = QtGui.QWidget() -win.setCentralWidget(cw) -l = QtGui.QGridLayout() -cw.setLayout(l) -imv1 = pg.ImageView() -imv2 = pg.ImageView() -l.addWidget(imv1, 0, 0) -l.addWidget(imv2, 1, 0) -win.show() - -roi = pg.LineSegmentROI([[10, 64], [120,64]], pen='r') -imv1.addItem(roi) - -x1 = np.linspace(-30, 10, 128)[:, np.newaxis, np.newaxis] -x2 = np.linspace(-20, 20, 128)[:, np.newaxis, np.newaxis] -y = np.linspace(-30, 10, 128)[np.newaxis, :, np.newaxis] -z = np.linspace(-20, 20, 128)[np.newaxis, np.newaxis, :] -d1 = np.sqrt(x1**2 + y**2 + z**2) -d2 = 2*np.sqrt(x1[::-1]**2 + y**2 + z**2) -d3 = 4*np.sqrt(x2**2 + y[:,::-1]**2 + z**2) -data = (np.sin(d1) / d1**2) + (np.sin(d2) / d2**2) + (np.sin(d3) / d3**2) - -def update(): - global data, imv1, imv2 - d2 = roi.getArrayRegion(data, imv1.imageItem, axes=(1,2)) - imv2.setImage(d2) - -roi.sigRegionChanged.connect(update) - - -## Display the data -imv1.setImage(data) -imv1.setHistogramRange(-0.01, 0.01) -imv1.setLevels(-0.003, 0.003) - -update() - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/DataTreeWidget.py b/pyqtgraph/examples/DataTreeWidget.py deleted file mode 100644 index 47a5f32..0000000 --- a/pyqtgraph/examples/DataTreeWidget.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Simple use of DataTreeWidget to display a structure of nested dicts, lists, and arrays -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - -# for generating a traceback object to display -def some_func1(): - return some_func2() -def some_func2(): - try: - raise Exception() - except: - import sys - return sys.exc_info()[2] - - -app = pg.mkQApp("DataTreeWidget Example") -d = { - 'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], - 'a dict': { - 'x': 1, - 'y': 2, - 'z': 'three' - }, - 'an array': np.random.randint(10, size=(40,10)), - 'a traceback': some_func1(), - 'a function': some_func1, - 'a class': pg.DataTreeWidget, -} - -tree = pg.DataTreeWidget(data=d) -tree.show() -tree.setWindowTitle('pyqtgraph example: DataTreeWidget') -tree.resize(600,600) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file diff --git a/pyqtgraph/examples/DateAxisItem.py b/pyqtgraph/examples/DateAxisItem.py deleted file mode 100644 index d789308..0000000 --- a/pyqtgraph/examples/DateAxisItem.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Demonstrates the usage of DateAxisItem to display properly-formatted -timestamps on x-axis which automatically adapt to current zoom level. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import time -from datetime import datetime, timedelta - -import numpy as np -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui - -app = pg.mkQApp("DateAxisItem Example") - -# Create a plot with a date-time axis -w = pg.PlotWidget(axisItems = {'bottom': pg.DateAxisItem()}) -w.showGrid(x=True, y=True) - -# Plot sin(1/x^2) with timestamps in the last 100 years -now = time.time() -x = np.linspace(2*np.pi, 1000*2*np.pi, 8301) -w.plot(now-(2*np.pi/x)**2*100*np.pi*1e7, np.sin(x), symbol='o') - -w.setWindowTitle('pyqtgraph example: DateAxisItem') -w.show() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - app.exec_() diff --git a/pyqtgraph/examples/DateAxisItem_QtDesigner.py b/pyqtgraph/examples/DateAxisItem_QtDesigner.py deleted file mode 100644 index d92a750..0000000 --- a/pyqtgraph/examples/DateAxisItem_QtDesigner.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Demonstrates the usage of DateAxisItem in a layout created with Qt Designer. - -The spotlight here is on the 'setAxisItems' method, without which -one would have to subclass plotWidget in order to attach a dateaxis to it. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import sys -import time -import os - -import numpy as np -import pyqtgraph as pg -from pyqtgraph.Qt import QtWidgets, QtCore, loadUiType - -pg.setConfigOption('background', 'w') -pg.setConfigOption('foreground', 'k') - -BLUE = pg.mkPen('#1f77b4') - -path = os.path.dirname(os.path.abspath(__file__)) -uiFile = os.path.join(path, 'DateAxisItem_QtDesigner.ui') -Design, _ = loadUiType(uiFile) - -class ExampleApp(QtWidgets.QMainWindow, Design): - def __init__(self): - super().__init__() - self.setupUi(self) - now = time.time() - # Plot random values with timestamps in the last 6 months - timestamps = np.linspace(now - 6*30*24*3600, now, 100) - self.curve = self.plotWidget.plot(x=timestamps, y=np.random.rand(100), - symbol='o', symbolSize=5, pen=BLUE) - # 'o' circle 't' triangle 'd' diamond '+' plus 's' square - self.plotWidget.setAxisItems({'bottom': pg.DateAxisItem()}) - self.plotWidget.showGrid(x=True, y=True) - -app = pg.mkQApp("DateAxisItem_QtDesigner Example") -window = ExampleApp() -window.setWindowTitle('pyqtgraph example: DateAxisItem_QtDesigner') -window.show() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - app.exec_() diff --git a/pyqtgraph/examples/DiffTreeWidget.py b/pyqtgraph/examples/DiffTreeWidget.py deleted file mode 100644 index 780e1ea..0000000 --- a/pyqtgraph/examples/DiffTreeWidget.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Simple use of DiffTreeWidget to display differences between structures of -nested dicts, lists, and arrays. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - -app = pg.mkQApp("DiffTreeWidget Example") -A = { - 'a list': [1,2,2,4,5,6, {'nested1': 'aaaa', 'nested2': 'bbbbb'}, "seven"], - 'a dict': { - 'x': 1, - 'y': 2, - 'z': 'three' - }, - 'an array': np.random.randint(10, size=(40,10)), - #'a traceback': some_func1(), - #'a function': some_func1, - #'a class': pg.DataTreeWidget, -} - -B = { - 'a list': [1,2,3,4,5,5, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"], - 'a dict': { - 'x': 2, - 'y': 2, - 'z': 'three', - 'w': 5 - }, - 'another dict': {1:2, 2:3, 3:4}, - 'an array': np.random.randint(10, size=(40,10)), -} - -tree = pg.DiffTreeWidget() -tree.setData(A, B) -tree.show() -tree.setWindowTitle('pyqtgraph example: DiffTreeWidget') -tree.resize(1000, 800) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file diff --git a/pyqtgraph/examples/Draw.py b/pyqtgraph/examples/Draw.py deleted file mode 100644 index 1401c39..0000000 --- a/pyqtgraph/examples/Draw.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrate ability of ImageItem to be used as a canvas for painting with -the mouse. - -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Draw Example") - -## Create window with GraphicsView widget -w = pg.GraphicsView() -w.show() -w.resize(800,800) -w.setWindowTitle('pyqtgraph example: Draw') - -view = pg.ViewBox() -w.setCentralItem(view) - -## lock the aspect ratio -view.setAspectLocked(True) - -## Create image item -img = pg.ImageItem(np.zeros((200,200))) -view.addItem(img) - -## Set initial view bounds -view.setRange(QtCore.QRectF(0, 0, 200, 200)) - -## start drawing with 3x3 brush -kern = np.array([ - [0.0, 0.5, 0.0], - [0.5, 1.0, 0.5], - [0.0, 0.5, 0.0] -]) -img.setDrawKernel(kern, mask=kern, center=(1,1), mode='add') -img.setLevels([0, 10]) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ErrorBarItem.py b/pyqtgraph/examples/ErrorBarItem.py deleted file mode 100644 index cd576d5..0000000 --- a/pyqtgraph/examples/ErrorBarItem.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates basic use of ErrorBarItem - -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np - -import pyqtgraph as pg -import numpy as np - -pg.setConfigOptions(antialias=True) - -x = np.arange(10) -y = np.arange(10) %3 -top = np.linspace(1.0, 3.0, 10) -bottom = np.linspace(2, 0.5, 10) - -plt = pg.plot() -plt.setWindowTitle('pyqtgraph example: ErrorBarItem') -err = pg.ErrorBarItem(x=x, y=y, top=top, bottom=bottom, beam=0.5) -plt.addItem(err) -plt.plot(x, y, symbol='o', pen={'color': 0.8, 'width': 2}) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ExampleApp.py b/pyqtgraph/examples/ExampleApp.py deleted file mode 100644 index c54d79b..0000000 --- a/pyqtgraph/examples/ExampleApp.py +++ /dev/null @@ -1,399 +0,0 @@ -import keyword -import os -import re -import sys -import subprocess -from argparse import Namespace -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore, QT_LIB -from collections import OrderedDict -from .utils import examples - -path = os.path.abspath(os.path.dirname(__file__)) -sys.path.insert(0, path) -app = pg.mkQApp() - -import importlib -ui_template = importlib.import_module( - f'exampleLoaderTemplate_{QT_LIB.lower()}') - - -# based on https://github.com/art1415926535/PyQt5-syntax-highlighting - -QRegularExpression = QtCore.QRegularExpression - -QFont = QtGui.QFont -QColor = QtGui.QColor -QTextCharFormat = QtGui.QTextCharFormat -QSyntaxHighlighter = QtGui.QSyntaxHighlighter - - -def format(color, style=''): - """ - Return a QTextCharFormat with the given attributes. - """ - _color = QColor() - if type(color) is not str: - _color.setRgb(color[0], color[1], color[2]) - else: - _color.setNamedColor(color) - - _format = QTextCharFormat() - _format.setForeground(_color) - if 'bold' in style: - _format.setFontWeight(QFont.Bold) - if 'italic' in style: - _format.setFontItalic(True) - - return _format - - -class LightThemeColors: - - Red = "#B71C1C" - Pink = "#FCE4EC" - Purple = "#4A148C" - DeepPurple = "#311B92" - Indigo = "#1A237E" - Blue = "#0D47A1" - LightBlue = "#01579B" - Cyan = "#006064" - Teal = "#004D40" - Green = "#1B5E20" - LightGreen = "#33691E" - Lime = "#827717" - Yellow = "#F57F17" - Amber = "#FF6F00" - Orange = "#E65100" - DeepOrange = "#BF360C" - Brown = "#3E2723" - Grey = "#212121" - BlueGrey = "#263238" - - -class DarkThemeColors: - - Red = "#F44336" - Pink = "#F48FB1" - Purple = "#CE93D8" - DeepPurple = "#B39DDB" - Indigo = "#9FA8DA" - Blue = "#90CAF9" - LightBlue = "#81D4FA" - Cyan = "#80DEEA" - Teal = "#80CBC4" - Green = "#A5D6A7" - LightGreen = "#C5E1A5" - Lime = "#E6EE9C" - Yellow = "#FFF59D" - Amber = "#FFE082" - Orange = "#FFCC80" - DeepOrange = "#FFAB91" - Brown = "#BCAAA4" - Grey = "#EEEEEE" - BlueGrey = "#B0BEC5" - - -LIGHT_STYLES = { - 'keyword': format(LightThemeColors.Blue, 'bold'), - 'operator': format(LightThemeColors.Red, 'bold'), - 'brace': format(LightThemeColors.Purple), - 'defclass': format(LightThemeColors.Indigo, 'bold'), - 'string': format(LightThemeColors.Amber), - 'string2': format(LightThemeColors.DeepPurple), - 'comment': format(LightThemeColors.Green, 'italic'), - 'self': format(LightThemeColors.Blue, 'bold'), - 'numbers': format(LightThemeColors.Teal), -} - - -DARK_STYLES = { - 'keyword': format(DarkThemeColors.Blue, 'bold'), - 'operator': format(DarkThemeColors.Red, 'bold'), - 'brace': format(DarkThemeColors.Purple), - 'defclass': format(DarkThemeColors.Indigo, 'bold'), - 'string': format(DarkThemeColors.Amber), - 'string2': format(DarkThemeColors.DeepPurple), - 'comment': format(DarkThemeColors.Green, 'italic'), - 'self': format(DarkThemeColors.Blue, 'bold'), - 'numbers': format(DarkThemeColors.Teal), -} - - -class PythonHighlighter(QSyntaxHighlighter): - """Syntax highlighter for the Python language. - """ - # Python keywords - keywords = keyword.kwlist - - # Python operators - operators = [ - r'=', - # Comparison - r'==', r'!=', r'<', r'<=', r'>', r'>=', - # Arithmetic - r'\+', r"-", r'\*', r'/', r'//', r'%', r'\*\*', - # In-place - r'\+=', r'-=', r'\*=', r'/=', r'\%=', - # Bitwise - r'\^', r'\|', r'&', r'~', r'>>', r'<<', - ] - - # Python braces - braces = [ - r'\{', r'\}', r'\(', r'\)', r'\[', r'\]', - ] - - def __init__(self, document): - QSyntaxHighlighter.__init__(self, document) - - # Multi-line strings (expression, flag, style) - self.tri_single = (QRegularExpression("'''"), 1, 'string2') - self.tri_double = (QRegularExpression('"""'), 2, 'string2') - - rules = [] - - # Keyword, operator, and brace rules - rules += [(r'\b%s\b' % w, 0, 'keyword') - for w in PythonHighlighter.keywords] - rules += [(o, 0, 'operator') - for o in PythonHighlighter.operators] - rules += [(b, 0, 'brace') - for b in PythonHighlighter.braces] - - # All other rules - rules += [ - # 'self' - (r'\bself\b', 0, 'self'), - - # 'def' followed by an identifier - (r'\bdef\b\s*(\w+)', 1, 'defclass'), - # 'class' followed by an identifier - (r'\bclass\b\s*(\w+)', 1, 'defclass'), - - # Numeric literals - (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), - (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), - (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), - - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), - - # From '#' until a newline - (r'#[^\n]*', 0, 'comment'), - ] - self.rules = rules - - @property - def styles(self): - app = QtGui.QApplication.instance() - return DARK_STYLES if app.property('darkMode') else LIGHT_STYLES - - def highlightBlock(self, text): - """Apply syntax highlighting to the given block of text. - """ - # Do other syntax formatting - for expression, nth, format in self.rules: - format = self.styles[format] - - for n, match in enumerate(re.finditer(expression, text)): - if n < nth: - continue - start = match.start() - length = match.end() - start - self.setFormat(start, length, format) - self.setCurrentBlockState(0) - - # Do multi-line strings - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - - def match_multiline(self, text, delimiter, in_state, style): - """Do highlighting of multi-line strings. - - =========== ========================================================== - delimiter (QRegularExpression) for triple-single-quotes or - triple-double-quotes - in_state (int) to represent the corresponding state changes when - inside those strings. Returns True if we're still inside a - multi-line string when this function is finished. - style (str) representation of the kind of style to use - =========== ========================================================== - """ - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - match = delimiter.match(text) - start = match.capturedStart() - # Move past this match - add = match.capturedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - match = delimiter.match(text, start + add) - end = match.capturedEnd() - # Ending delimiter on this line? - if end >= add: - length = end - start + add + match.capturedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, self.styles[style]) - # Look for the next match - match = delimiter.match(text, start + length) - start = match.capturedStart() - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False - - - -class ExampleLoader(QtGui.QMainWindow): - def __init__(self): - QtGui.QMainWindow.__init__(self) - self.ui = ui_template.Ui_Form() - self.cw = QtGui.QWidget() - self.setCentralWidget(self.cw) - self.ui.setupUi(self.cw) - self.setWindowTitle("PyQtGraph Examples") - self.codeBtn = QtGui.QPushButton('Run Edited Code') - self.codeLayout = QtGui.QGridLayout() - self.ui.codeView.setLayout(self.codeLayout) - self.hl = PythonHighlighter(self.ui.codeView.document()) - app = QtGui.QApplication.instance() - app.paletteChanged.connect(self.updateTheme) - self.codeLayout.addItem(QtGui.QSpacerItem(100,100,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding), 0, 0) - self.codeLayout.addWidget(self.codeBtn, 1, 1) - self.codeBtn.hide() - - global examples - self.itemCache = [] - self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples) - self.ui.exampleTree.expandAll() - - self.resize(1000,500) - self.show() - self.ui.splitter.setSizes([250,750]) - self.ui.loadBtn.clicked.connect(self.loadFile) - self.ui.exampleTree.currentItemChanged.connect(self.showFile) - self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile) - self.ui.codeView.textChanged.connect(self.codeEdited) - self.codeBtn.clicked.connect(self.runEditedCode) - - def simulate_black_mode(self): - """ - used to simulate MacOS "black mode" on other platforms - intended for debug only, as it manage only the QPlainTextEdit - """ - # first, a dark background - c = QtGui.QColor('#171717') - p = self.ui.codeView.palette() - p.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base, c) - p.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Base, c) - self.ui.codeView.setPalette(p) - # then, a light font - f = QtGui.QTextCharFormat() - f.setForeground(QtGui.QColor('white')) - self.ui.codeView.setCurrentCharFormat(f) - # finally, override application automatic detection - app = QtGui.QApplication.instance() - app.setProperty('darkMode', True) - - def updateTheme(self): - self.hl = PythonHighlighter(self.ui.codeView.document()) - - def populateTree(self, root, examples): - bold_font = None - for key, val in examples.items(): - item = QtGui.QTreeWidgetItem([key]) - self.itemCache.append(item) # PyQt 4.9.6 no longer keeps references to these wrappers, - # so we need to make an explicit reference or else the .file - # attribute will disappear. - if isinstance(val, OrderedDict): - self.populateTree(item, val) - elif isinstance(val, Namespace): - item.file = val.filename - if 'recommended' in val: - if bold_font is None: - bold_font = item.font(0) - bold_font.setBold(True) - item.setFont(0, bold_font) - else: - item.file = val - root.addChild(item) - - def currentFile(self): - item = self.ui.exampleTree.currentItem() - if hasattr(item, 'file'): - global path - return os.path.join(path, item.file) - return None - - def loadFile(self, edited=False): - - qtLib = str(self.ui.qtLibCombo.currentText()) - - env = None - if qtLib != 'default': - env = dict(os.environ, PYQTGRAPH_QT_LIB=qtLib) - - if edited: - path = os.path.abspath(os.path.dirname(__file__)) - proc = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE, cwd=path, env=env) - code = str(self.ui.codeView.toPlainText()).encode('UTF-8') - proc.stdin.write(code) - proc.stdin.close() - else: - fn = self.currentFile() - if fn is None: - return - if sys.platform.startswith('win'): - args = [os.P_NOWAIT, sys.executable, '"'+sys.executable+'"', '"' + fn + '"'] - else: - args = [os.P_NOWAIT, sys.executable, sys.executable, fn] - if env is None: - os.spawnl(*args) - else: - args.append(env) - os.spawnle(*args) - - def showFile(self): - fn = self.currentFile() - if fn is None: - self.ui.codeView.clear() - return - if os.path.isdir(fn): - fn = os.path.join(fn, '__main__.py') - text = open(fn).read() - self.ui.codeView.setPlainText(text) - self.ui.loadedFileLabel.setText(fn) - self.codeBtn.hide() - - def codeEdited(self): - self.codeBtn.show() - - def runEditedCode(self): - self.loadFile(edited=True) - - -def main(): - app = pg.mkQApp() - loader = ExampleLoader() - app.exec_() -# or condition so pytest runs ExampleApp as part of test suite -if __name__ == '__main__': - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - main() diff --git a/pyqtgraph/examples/FillBetweenItem.py b/pyqtgraph/examples/FillBetweenItem.py deleted file mode 100644 index fc91ee3..0000000 --- a/pyqtgraph/examples/FillBetweenItem.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates use of FillBetweenItem to fill the space between two plot curves. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np - -#FIXME: When running on Qt5, not as perfect as on Qt4 - -win = pg.plot() -win.setWindowTitle('pyqtgraph example: FillBetweenItem') -win.setXRange(-10, 10) -win.setYRange(-10, 10) - -N = 200 -x = np.linspace(-10, 10, N) -gauss = np.exp(-x**2 / 20.) -mn = mx = np.zeros(len(x)) -curves = [win.plot(x=x, y=np.zeros(len(x)), pen='k') for i in range(4)] -brushes = [0.5, (100, 100, 255), 0.5] -fills = [pg.FillBetweenItem(curves[i], curves[i+1], brushes[i]) for i in range(3)] -for f in fills: - win.addItem(f) - -def update(): - global mx, mn, curves, gauss, x - a = 5 / abs(np.random.normal(loc=1, scale=0.2)) - y1 = -np.abs(a*gauss + np.random.normal(size=len(x))) - y2 = np.abs(a*gauss + np.random.normal(size=len(x))) - - s = 0.01 - mn = np.where(y1mx, y2, mx) * (1-s) + y2 * s - curves[0].setData(x, mn) - curves[1].setData(x, y1) - curves[2].setData(x, y2) - curves[3].setData(x, mx) - - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(30) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/Flowchart.py b/pyqtgraph/examples/Flowchart.py deleted file mode 100644 index 45e833c..0000000 --- a/pyqtgraph/examples/Flowchart.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates a very basic use of flowcharts: filter data, -displaying both the input and output of the filter. The behavior of -the filter can be reprogrammed by the user. - -Basic steps are: - - create a flowchart and two plots - - input noisy data to the flowchart - - flowchart connects data to the first plot, where it is displayed - - add a gaussian filter to lowpass the data, then display it in the second plot. -""" -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.flowchart import Flowchart -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np -import pyqtgraph.metaarray as metaarray - -app = pg.mkQApp("Flowchart Example") - -## Create main window with grid layout -win = QtGui.QMainWindow() -win.setWindowTitle('pyqtgraph example: Flowchart') -cw = QtGui.QWidget() -win.setCentralWidget(cw) -layout = QtGui.QGridLayout() -cw.setLayout(layout) - -## Create flowchart, define input/output terminals -fc = Flowchart(terminals={ - 'dataIn': {'io': 'in'}, - 'dataOut': {'io': 'out'} -}) -w = fc.widget() - -## Add flowchart control panel to the main window -layout.addWidget(fc.widget(), 0, 0, 2, 1) - -## Add two plot widgets -pw1 = pg.PlotWidget() -pw2 = pg.PlotWidget() -layout.addWidget(pw1, 0, 1) -layout.addWidget(pw2, 1, 1) - -win.show() - -## generate signal data to pass through the flowchart -data = np.random.normal(size=1000) -data[200:300] += 1 -data += np.sin(np.linspace(0, 100, 1000)) -data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}]) - -## Feed data into the input terminal of the flowchart -fc.setInput(dataIn=data) - -## populate the flowchart with a basic set of processing nodes. -## (usually we let the user do this) -plotList = {'Top Plot': pw1, 'Bottom Plot': pw2} - -pw1Node = fc.createNode('PlotWidget', pos=(0, -150)) -pw1Node.setPlotList(plotList) -pw1Node.setPlot(pw1) - -pw2Node = fc.createNode('PlotWidget', pos=(150, -150)) -pw2Node.setPlot(pw2) -pw2Node.setPlotList(plotList) - -fNode = fc.createNode('GaussianFilter', pos=(0, 0)) -fNode.ctrls['sigma'].setValue(5) -fc.connectTerminals(fc['dataIn'], fNode['In']) -fc.connectTerminals(fc['dataIn'], pw1Node['In']) -fc.connectTerminals(fNode['Out'], pw2Node['In']) -fc.connectTerminals(fNode['Out'], fc['dataOut']) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/FlowchartCustomNode.py b/pyqtgraph/examples/FlowchartCustomNode.py deleted file mode 100644 index dc1fd55..0000000 --- a/pyqtgraph/examples/FlowchartCustomNode.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates writing a custom Node subclass for use with flowcharts. - -We implement a couple of simple image processing nodes. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -from pyqtgraph.flowchart import Flowchart, Node -import pyqtgraph.flowchart.library as fclib -from pyqtgraph.flowchart.library.common import CtrlNode -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np - -app = pg.mkQApp("Flowchart Custom Node Example") - -## Create main window with a grid layout inside -win = QtGui.QMainWindow() -win.setWindowTitle('pyqtgraph example: FlowchartCustomNode') -cw = QtGui.QWidget() -win.setCentralWidget(cw) -layout = QtGui.QGridLayout() -cw.setLayout(layout) - -## Create an empty flowchart with a single input and output -fc = Flowchart(terminals={ - 'dataIn': {'io': 'in'}, - 'dataOut': {'io': 'out'} -}) -w = fc.widget() - -layout.addWidget(fc.widget(), 0, 0, 2, 1) - -## Create two ImageView widgets to display the raw and processed data with contrast -## and color control. -v1 = pg.ImageView() -v2 = pg.ImageView() -layout.addWidget(v1, 0, 1) -layout.addWidget(v2, 1, 1) - -win.show() - -## generate random input data -data = np.random.normal(size=(100,100)) -data = 25 * pg.gaussianFilter(data, (5,5)) -data += np.random.normal(size=(100,100)) -data[40:60, 40:60] += 15.0 -data[30:50, 30:50] += 15.0 -#data += np.sin(np.linspace(0, 100, 1000)) -#data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}]) - -## Set the raw data as the input value to the flowchart -fc.setInput(dataIn=data) - - -## At this point, we need some custom Node classes since those provided in the library -## are not sufficient. Each node will define a set of input/output terminals, a -## processing function, and optionally a control widget (to be displayed in the -## flowchart control panel) - -class ImageViewNode(Node): - """Node that displays image data in an ImageView widget""" - nodeName = 'ImageView' - - def __init__(self, name): - self.view = None - ## Initialize node with only a single input terminal - Node.__init__(self, name, terminals={'data': {'io':'in'}}) - - def setView(self, view): ## setView must be called by the program - self.view = view - - def process(self, data, display=True): - ## if process is called with display=False, then the flowchart is being operated - ## in batch processing mode, so we should skip displaying to improve performance. - - if display and self.view is not None: - ## the 'data' argument is the value given to the 'data' terminal - if data is None: - self.view.setImage(np.zeros((1,1))) # give a blank array to clear the view - else: - self.view.setImage(data) - - - - -## We will define an unsharp masking filter node as a subclass of CtrlNode. -## CtrlNode is just a convenience class that automatically creates its -## control widget based on a simple data structure. -class UnsharpMaskNode(CtrlNode): - """Return the input data passed through an unsharp mask.""" - nodeName = "UnsharpMask" - uiTemplate = [ - ('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'bounds': [0.0, None]}), - ('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'bounds': [0.0, None]}), - ] - def __init__(self, name): - ## Define the input / output terminals available on this node - terminals = { - 'dataIn': dict(io='in'), # each terminal needs at least a name and - 'dataOut': dict(io='out'), # to specify whether it is input or output - } # other more advanced options are available - # as well.. - - CtrlNode.__init__(self, name, terminals=terminals) - - def process(self, dataIn, display=True): - # CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget} - sigma = self.ctrls['sigma'].value() - strength = self.ctrls['strength'].value() - output = dataIn - (strength * pg.gaussianFilter(dataIn, (sigma,sigma))) - return {'dataOut': output} - - -## To make our custom node classes available in the flowchart context menu, -## we can either register them with the default node library or make a -## new library. - - -## Method 1: Register to global default library: -#fclib.registerNodeType(ImageViewNode, [('Display',)]) -#fclib.registerNodeType(UnsharpMaskNode, [('Image',)]) - -## Method 2: If we want to make our custom node available only to this flowchart, -## then instead of registering the node type globally, we can create a new -## NodeLibrary: -library = fclib.LIBRARY.copy() # start with the default node set -library.addNodeType(ImageViewNode, [('Display',)]) -# Add the unsharp mask node to two locations in the menu to demonstrate -# that we can create arbitrary menu structures -library.addNodeType(UnsharpMaskNode, [('Image',), - ('Submenu_test','submenu2','submenu3')]) -fc.setLibrary(library) - - -## Now we will programmatically add nodes to define the function of the flowchart. -## Normally, the user will do this manually or by loading a pre-generated -## flowchart file. - -v1Node = fc.createNode('ImageView', pos=(0, -150)) -v1Node.setView(v1) - -v2Node = fc.createNode('ImageView', pos=(150, -150)) -v2Node.setView(v2) - -fNode = fc.createNode('UnsharpMask', pos=(0, 0)) -fc.connectTerminals(fc['dataIn'], fNode['dataIn']) -fc.connectTerminals(fc['dataIn'], v1Node['data']) -fc.connectTerminals(fNode['dataOut'], v2Node['data']) -fc.connectTerminals(fNode['dataOut'], fc['dataOut']) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLBarGraphItem.py b/pyqtgraph/examples/GLBarGraphItem.py deleted file mode 100644 index e593d54..0000000 --- a/pyqtgraph/examples/GLBarGraphItem.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrate use of GLLinePlotItem to draw cross-sections of a surface. - -""" -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.opengl as gl -import pyqtgraph as pg -import numpy as np - -app = pg.mkQApp("GLBarGraphItem Example") -w = gl.GLViewWidget() -w.opts['distance'] = 40 -w.show() -w.setWindowTitle('pyqtgraph example: GLBarGraphItem') - -gx = gl.GLGridItem() -gx.rotate(90, 0, 1, 0) -gx.translate(-10, 0, 10) -w.addItem(gx) -gy = gl.GLGridItem() -gy.rotate(90, 1, 0, 0) -gy.translate(0, -10, 10) -w.addItem(gy) -gz = gl.GLGridItem() -gz.translate(0, 0, 0) -w.addItem(gz) - -# regular grid of starting positions -pos = np.mgrid[0:10, 0:10, 0:1].reshape(3,10,10).transpose(1,2,0) -# fixed widths, random heights -size = np.empty((10,10,3)) -size[...,0:2] = 0.4 -size[...,2] = np.random.normal(size=(10,10)) - -bg = gl.GLBarGraphItem(pos, size) -w.addItem(bg) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLImageItem.py b/pyqtgraph/examples/GLImageItem.py deleted file mode 100644 index 6a31c09..0000000 --- a/pyqtgraph/examples/GLImageItem.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Use GLImageItem to display image data on rectangular planes. - -In this example, the image data is sampled from a volume and the image planes -placed as if they slice through the volume. -""" -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.opengl as gl -import pyqtgraph as pg -import numpy as np - -app = pg.mkQApp("GLImageItem Example") -w = gl.GLViewWidget() -w.opts['distance'] = 200 -w.show() -w.setWindowTitle('pyqtgraph example: GLImageItem') - -## create volume data set to slice three images from -shape = (100,100,70) -data = pg.gaussianFilter(np.random.normal(size=shape), (4,4,4)) -data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15 - -## slice out three planes, convert to RGBA for OpenGL texture -levels = (-0.08, 0.08) -tex1 = pg.makeRGBA(data[shape[0]//2], levels=levels)[0] # yz plane -tex2 = pg.makeRGBA(data[:,shape[1]//2], levels=levels)[0] # xz plane -tex3 = pg.makeRGBA(data[:,:,shape[2]//2], levels=levels)[0] # xy plane -#tex1[:,:,3] = 128 -#tex2[:,:,3] = 128 -#tex3[:,:,3] = 128 - -## Create three image items from textures, add to view -v1 = gl.GLImageItem(tex1) -v1.translate(-shape[1]/2, -shape[2]/2, 0) -v1.rotate(90, 0,0,1) -v1.rotate(-90, 0,1,0) -w.addItem(v1) -v2 = gl.GLImageItem(tex2) -v2.translate(-shape[0]/2, -shape[2]/2, 0) -v2.rotate(-90, 1,0,0) -w.addItem(v2) -v3 = gl.GLImageItem(tex3) -v3.translate(-shape[0]/2, -shape[1]/2, 0) -w.addItem(v3) - -ax = gl.GLAxisItem() -w.addItem(ax) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLIsosurface.py b/pyqtgraph/examples/GLIsosurface.py deleted file mode 100644 index 0beeea6..0000000 --- a/pyqtgraph/examples/GLIsosurface.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example uses the isosurface function to convert a scalar field -(a hydrogen orbital) into a mesh for 3D display. -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -import pyqtgraph.opengl as gl - -app = pg.mkQApp("GLIsosurface Example") -w = gl.GLViewWidget() -w.show() -w.setWindowTitle('pyqtgraph example: GLIsosurface') - -w.setCameraPosition(distance=40) - -g = gl.GLGridItem() -g.scale(2,2,1) -w.addItem(g) - -import numpy as np - -## Define a scalar field from which we will generate an isosurface -def psi(i, j, k, offset=(25, 25, 50)): - x = i-offset[0] - y = j-offset[1] - z = k-offset[2] - th = np.arctan2(z, (x**2+y**2)**0.5) - phi = np.arctan2(y, x) - r = (x**2 + y**2 + z **2)**0.5 - a0 = 1 - #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) - ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - - return ps - - #return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 - - -print("Generating scalar field..") -data = np.abs(np.fromfunction(psi, (50,50,100))) - - -print("Generating isosurface..") -verts, faces = pg.isosurface(data, data.max()/4.) - -md = gl.MeshData(vertexes=verts, faces=faces) - -colors = np.ones((md.faceCount(), 4), dtype=float) -colors[:,3] = 0.2 -colors[:,2] = np.linspace(0, 1, colors.shape[0]) -md.setFaceColors(colors) -m1 = gl.GLMeshItem(meshdata=md, smooth=False, shader='balloon') -m1.setGLOptions('additive') - -#w.addItem(m1) -m1.translate(-25, -25, -20) - -m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='balloon') -m2.setGLOptions('additive') - -w.addItem(m2) -m2.translate(-25, -25, -50) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLLinePlotItem.py b/pyqtgraph/examples/GLLinePlotItem.py deleted file mode 100644 index 0d07b15..0000000 --- a/pyqtgraph/examples/GLLinePlotItem.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrate use of GLLinePlotItem to draw cross-sections of a surface. - -""" -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.opengl as gl -import pyqtgraph as pg -import numpy as np - -app = pg.mkQApp("GLLinePlotItem Example") -w = gl.GLViewWidget() -w.opts['distance'] = 40 -w.show() -w.setWindowTitle('pyqtgraph example: GLLinePlotItem') - -gx = gl.GLGridItem() -gx.rotate(90, 0, 1, 0) -gx.translate(-10, 0, 0) -w.addItem(gx) -gy = gl.GLGridItem() -gy.rotate(90, 1, 0, 0) -gy.translate(0, -10, 0) -w.addItem(gy) -gz = gl.GLGridItem() -gz.translate(0, 0, -10) -w.addItem(gz) - -def fn(x, y): - return np.cos((x**2 + y**2)**0.5) - -n = 51 -y = np.linspace(-10,10,n) -x = np.linspace(-10,10,100) -for i in range(n): - yi = np.array([y[i]]*100) - d = (x**2 + yi**2)**0.5 - z = 10 * np.cos(d) / (d+1) - pts = np.vstack([x,yi,z]).transpose() - plt = gl.GLLinePlotItem(pos=pts, color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True) - w.addItem(plt) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLMeshItem.py b/pyqtgraph/examples/GLMeshItem.py deleted file mode 100644 index 692f8f8..0000000 --- a/pyqtgraph/examples/GLMeshItem.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple examples demonstrating the use of GLMeshItem. - -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -import pyqtgraph.opengl as gl - -app = pg.mkQApp("GLMeshItem Example") -w = gl.GLViewWidget() -w.show() -w.setWindowTitle('pyqtgraph example: GLMeshItem') -w.setCameraPosition(distance=40) - -g = gl.GLGridItem() -g.scale(2,2,1) -w.addItem(g) - -import numpy as np - - -## Example 1: -## Array of vertex positions and array of vertex indexes defining faces -## Colors are specified per-face - -verts = np.array([ - [0, 0, 0], - [2, 0, 0], - [1, 2, 0], - [1, 1, 1], -]) -faces = np.array([ - [0, 1, 2], - [0, 1, 3], - [0, 2, 3], - [1, 2, 3] -]) -colors = np.array([ - [1, 0, 0, 0.3], - [0, 1, 0, 0.3], - [0, 0, 1, 0.3], - [1, 1, 0, 0.3] -]) - -## Mesh item will automatically compute face normals. -m1 = gl.GLMeshItem(vertexes=verts, faces=faces, faceColors=colors, smooth=False) -m1.translate(5, 5, 0) -m1.setGLOptions('additive') -w.addItem(m1) - - -## Example 2: -## Array of vertex positions, three per face -verts = np.empty((36, 3, 3), dtype=np.float32) -theta = np.linspace(0, 2*np.pi, 37)[:-1] -verts[:,0] = np.vstack([2*np.cos(theta), 2*np.sin(theta), [0]*36]).T -verts[:,1] = np.vstack([4*np.cos(theta+0.2), 4*np.sin(theta+0.2), [-1]*36]).T -verts[:,2] = np.vstack([4*np.cos(theta-0.2), 4*np.sin(theta-0.2), [1]*36]).T - -## Colors are specified per-vertex -colors = np.random.random(size=(verts.shape[0], 3, 4)) -m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon', - drawEdges=True, edgeColor=(1, 1, 0, 1)) -m2.translate(-5, 5, 0) -w.addItem(m2) - - - -## Example 3: -## sphere - -md = gl.MeshData.sphere(rows=10, cols=20) -#colors = np.random.random(size=(md.faceCount(), 4)) -#colors[:,3] = 0.3 -#colors[100:] = 0.0 -colors = np.ones((md.faceCount(), 4), dtype=float) -colors[::2,0] = 0 -colors[:,1] = np.linspace(0, 1, colors.shape[0]) -md.setFaceColors(colors) -m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon') - -m3.translate(5, -5, 0) -w.addItem(m3) - - -# Example 4: -# wireframe - -md = gl.MeshData.sphere(rows=4, cols=8) -m4 = gl.GLMeshItem(meshdata=md, smooth=False, drawFaces=False, drawEdges=True, edgeColor=(1,1,1,1)) -m4.translate(0,10,0) -w.addItem(m4) - -# Example 5: -# cylinder -md = gl.MeshData.cylinder(rows=10, cols=20, radius=[1., 2.0], length=5.) -md2 = gl.MeshData.cylinder(rows=10, cols=20, radius=[2., 0.5], length=10.) -colors = np.ones((md.faceCount(), 4), dtype=float) -colors[::2,0] = 0 -colors[:,1] = np.linspace(0, 1, colors.shape[0]) -md.setFaceColors(colors) -m5 = gl.GLMeshItem(meshdata=md, smooth=True, drawEdges=True, edgeColor=(1,0,0,1), shader='balloon') -colors = np.ones((md.faceCount(), 4), dtype=float) -colors[::2,0] = 0 -colors[:,1] = np.linspace(0, 1, colors.shape[0]) -md2.setFaceColors(colors) -m6 = gl.GLMeshItem(meshdata=md2, smooth=True, drawEdges=False, shader='balloon') -m6.translate(0,0,7.5) - -m6.rotate(0., 0, 1, 1) -#m5.translate(-3,3,0) -w.addItem(m5) -w.addItem(m6) - - - - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLScatterPlotItem.py b/pyqtgraph/examples/GLScatterPlotItem.py deleted file mode 100644 index b52e17b..0000000 --- a/pyqtgraph/examples/GLScatterPlotItem.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates use of GLScatterPlotItem with rapidly-updating plots. - -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.opengl as gl -import numpy as np - -app = pg.mkQApp("GLScatterPlotItem Example") -w = gl.GLViewWidget() -w.opts['distance'] = 20 -w.show() -w.setWindowTitle('pyqtgraph example: GLScatterPlotItem') - -g = gl.GLGridItem() -w.addItem(g) - - -## -## First example is a set of points with pxMode=False -## These demonstrate the ability to have points with real size down to a very small scale -## -pos = np.empty((53, 3)) -size = np.empty((53)) -color = np.empty((53, 4)) -pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5) -pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5) -pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5) - -z = 0.5 -d = 6.0 -for i in range(3,53): - pos[i] = (0,0,z) - size[i] = 2./d - color[i] = (0.0, 1.0, 0.0, 0.5) - z *= 0.5 - d *= 2.0 - -sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False) -sp1.translate(5,5,0) -w.addItem(sp1) - - -## -## Second example shows a volume of points with rapidly updating color -## and pxMode=True -## - -pos = np.random.random(size=(100000,3)) -pos *= [10,-10,10] -pos[0] = (0,0,0) -color = np.ones((pos.shape[0], 4)) -d2 = (pos**2).sum(axis=1)**0.5 -size = np.random.random(size=pos.shape[0])*10 -sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size) -phase = 0. - -w.addItem(sp2) - - -## -## Third example shows a grid of points with rapidly updating position -## and pxMode = False -## - -pos3 = np.zeros((100,100,3)) -pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1] -pos3 = pos3.reshape(10000,3) -d3 = (pos3**2).sum(axis=1)**0.5 - -sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False) - -w.addItem(sp3) - - -def update(): - ## update volume colors - global phase, sp2, d2 - s = -np.cos(d2*2+phase) - color = np.empty((len(d2),4), dtype=np.float32) - color[:,3] = np.clip(s * 0.1, 0, 1) - color[:,0] = np.clip(s * 3.0, 0, 1) - color[:,1] = np.clip(s * 1.0, 0, 1) - color[:,2] = np.clip(s ** 3, 0, 1) - sp2.setData(color=color) - phase -= 0.1 - - ## update surface positions and colors - global sp3, d3, pos3 - z = -np.cos(d3*2+phase) - pos3[:,2] = z - color = np.empty((len(d3),4), dtype=np.float32) - color[:,3] = 0.3 - color[:,0] = np.clip(z * 3.0, 0, 1) - color[:,1] = np.clip(z * 1.0, 0, 1) - color[:,2] = np.clip(z ** 3, 0, 1) - sp3.setData(pos=pos3, color=color) - -t = QtCore.QTimer() -t.timeout.connect(update) -t.start(50) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLSurfacePlot.py b/pyqtgraph/examples/GLSurfacePlot.py deleted file mode 100644 index cac8d5f..0000000 --- a/pyqtgraph/examples/GLSurfacePlot.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of GLSurfacePlotItem. -""" - - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -import pyqtgraph.opengl as gl -import numpy as np - -## Create a GL View widget to display data -app = pg.mkQApp("GLSurfacePlot Example") -w = gl.GLViewWidget() -w.show() -w.setWindowTitle('pyqtgraph example: GLSurfacePlot') -w.setCameraPosition(distance=50) - -## Add a grid to the view -g = gl.GLGridItem() -g.scale(2,2,1) -g.setDepthValue(10) # draw grid after surfaces since they may be translucent -w.addItem(g) - - -## Simple surface plot example -## x, y values are not specified, so assumed to be 0:50 -z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1)) -p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1)) -p1.scale(16./49., 16./49., 1.0) -p1.translate(-18, 2, 0) -w.addItem(p1) - - -## Saddle example with x and y specified -x = np.linspace(-8, 8, 50) -y = np.linspace(-8, 8, 50) -z = 0.1 * ((x.reshape(50,1) ** 2) - (y.reshape(1,50) ** 2)) -p2 = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='normalColor') -p2.translate(-10,-10,0) -w.addItem(p2) - - -## Manually specified colors -z = pg.gaussianFilter(np.random.normal(size=(50,50)), (1,1)) -x = np.linspace(-12, 12, 50) -y = np.linspace(-12, 12, 50) -colors = np.ones((50,50,4), dtype=float) -colors[...,0] = np.clip(np.cos(((x.reshape(50,1) ** 2) + (y.reshape(1,50) ** 2)) ** 0.5), 0, 1) -colors[...,1] = colors[...,0] - -p3 = gl.GLSurfacePlotItem(z=z, colors=colors.reshape(50*50,4), shader='shaded', smooth=False) -p3.scale(16./49., 16./49., 1.0) -p3.translate(2, -18, 0) -w.addItem(p3) - - - - -## Animated example -## compute surface vertex data -cols = 90 -rows = 100 -x = np.linspace(-8, 8, cols+1).reshape(cols+1,1) -y = np.linspace(-8, 8, rows+1).reshape(1,rows+1) -d = (x**2 + y**2) * 0.1 -d2 = d ** 0.5 + 0.1 - -## precompute height values for all frames -phi = np.arange(0, np.pi*2, np.pi/20.) -z = np.sin(d[np.newaxis,...] + phi.reshape(phi.shape[0], 1, 1)) / d2[np.newaxis,...] - - -## create a surface plot, tell it to use the 'heightColor' shader -## since this does not require normal vectors to render (thus we -## can set computeNormals=False to save time when the mesh updates) -p4 = gl.GLSurfacePlotItem(x=x[:,0], y = y[0,:], shader='heightColor', computeNormals=False, smooth=False) -p4.shader()['colorMap'] = np.array([0.2, 2, 0.5, 0.2, 1, 1, 0.2, 0, 2]) -p4.translate(10, 10, 0) -w.addItem(p4) - -index = 0 -def update(): - global p4, z, index - index -= 1 - p4.setData(z=z[index%z.shape[0]]) - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(30) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLViewWidget.py b/pyqtgraph/examples/GLViewWidget.py deleted file mode 100644 index d06a6ed..0000000 --- a/pyqtgraph/examples/GLViewWidget.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Very basic 3D graphics example; create a view widget and add a few items. - -""" -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui, mkQApp -import pyqtgraph.opengl as gl - -app = mkQApp("GLViewWidget Example") -w = gl.GLViewWidget() -w.opts['distance'] = 20 -w.show() -w.setWindowTitle('pyqtgraph example: GLViewWidget') - -ax = gl.GLAxisItem() -ax.setSize(5,5,5) -w.addItem(ax) - -b = gl.GLBoxItem() -w.addItem(b) - -ax2 = gl.GLAxisItem() -ax2.setParentItem(b) - -b.translate(1,1,1) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLVolumeItem.py b/pyqtgraph/examples/GLVolumeItem.py deleted file mode 100644 index 628ee97..0000000 --- a/pyqtgraph/examples/GLVolumeItem.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates GLVolumeItem for displaying volumetric data. - -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.opengl as gl - -app = pg.mkQApp("GLVolumeItem Example") -w = gl.GLViewWidget() -w.opts['distance'] = 200 -w.show() -w.setWindowTitle('pyqtgraph example: GLVolumeItem') - -#b = gl.GLBoxItem() -#w.addItem(b) -g = gl.GLGridItem() -g.scale(10, 10, 1) -w.addItem(g) - -import numpy as np -## Hydrogen electron probability density -def psi(i, j, k, offset=(50,50,100)): - x = i-offset[0] - y = j-offset[1] - z = k-offset[2] - th = np.arctan2(z, (x**2+y**2)**0.5) - phi = np.arctan2(y, x) - r = (x**2 + y**2 + z **2)**0.5 - a0 = 2 - #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) - ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - - return ps - - #return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 - - -data = np.fromfunction(psi, (100,100,200)) -positive = np.log(np.clip(data, 0, data.max())**2) -negative = np.log(np.clip(-data, 0, -data.min())**2) - -d2 = np.empty(data.shape + (4,), dtype=np.ubyte) -d2[..., 0] = positive * (255./positive.max()) -d2[..., 1] = negative * (255./negative.max()) -d2[..., 2] = d2[...,1] -d2[..., 3] = d2[..., 0]*0.3 + d2[..., 1]*0.3 -d2[..., 3] = (d2[..., 3].astype(float) / 255.) **2 * 255 - -d2[:, 0, 0] = [255,0,0,100] -d2[0, :, 0] = [0,255,0,100] -d2[0, 0, :] = [0,0,255,100] - -v = gl.GLVolumeItem(d2) -v.translate(-50,-50,-100) -w.addItem(v) - -ax = gl.GLAxisItem() -w.addItem(ax) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GLshaders.py b/pyqtgraph/examples/GLshaders.py deleted file mode 100644 index 86719c6..0000000 --- a/pyqtgraph/examples/GLshaders.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstration of some of the shader programs included with pyqtgraph that can be -used to affect the appearance of a surface. -""" - - - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -import pyqtgraph.opengl as gl - -app = pg.mkQApp("GLShaders Example") -w = gl.GLViewWidget() -w.show() -w.setWindowTitle('pyqtgraph example: GL Shaders') -w.setCameraPosition(distance=15, azimuth=-90) - -g = gl.GLGridItem() -g.scale(2,2,1) -w.addItem(g) - -import numpy as np - - -md = gl.MeshData.sphere(rows=10, cols=20) -x = np.linspace(-8, 8, 6) - -m1 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 0.2), shader='balloon', glOptions='additive') -m1.translate(x[0], 0, 0) -m1.scale(1, 1, 2) -w.addItem(m1) - -m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='normalColor', glOptions='opaque') -m2.translate(x[1], 0, 0) -m2.scale(1, 1, 2) -w.addItem(m2) - -m3 = gl.GLMeshItem(meshdata=md, smooth=True, shader='viewNormalColor', glOptions='opaque') -m3.translate(x[2], 0, 0) -m3.scale(1, 1, 2) -w.addItem(m3) - -m4 = gl.GLMeshItem(meshdata=md, smooth=True, shader='shaded', glOptions='opaque') -m4.translate(x[3], 0, 0) -m4.scale(1, 1, 2) -w.addItem(m4) - -m5 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='edgeHilight', glOptions='opaque') -m5.translate(x[4], 0, 0) -m5.scale(1, 1, 2) -w.addItem(m5) - -m6 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='heightColor', glOptions='opaque') -m6.translate(x[5], 0, 0) -m6.scale(1, 1, 2) -w.addItem(m6) - - - - -#def psi(i, j, k, offset=(25, 25, 50)): - #x = i-offset[0] - #y = j-offset[1] - #z = k-offset[2] - #th = np.arctan2(z, (x**2+y**2)**0.5) - #phi = np.arctan2(y, x) - #r = (x**2 + y**2 + z **2)**0.5 - #a0 = 1 - ##ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) - #ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - - #return ps - - ##return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 - - -#print("Generating scalar field..") -#data = np.abs(np.fromfunction(psi, (50,50,100))) - - -##data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50)); -#print("Generating isosurface..") -#verts = pg.isosurface(data, data.max()/4.) - -#md = gl.MeshData.MeshData(vertexes=verts) - -#colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float) -#colors[:,3] = 0.3 -#colors[:,2] = np.linspace(0, 1, colors.shape[0]) -#m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False) - -#w.addItem(m1) -#m1.translate(-25, -25, -20) - -#m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True) - -#w.addItem(m2) -#m2.translate(-25, -25, -50) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GradientEditor.py b/pyqtgraph/examples/GradientEditor.py deleted file mode 100644 index ad8c8ee..0000000 --- a/pyqtgraph/examples/GradientEditor.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = pg.mkQApp("Gradiant Editor Example") -mw = pg.GraphicsView() -mw.resize(800,800) -mw.show() - -#ts = pg.TickSliderItem() -#mw.setCentralItem(ts) -#ts.addTick(0.5, 'r') -#ts.addTick(0.9, 'b') - -ge = pg.GradientEditorItem() -mw.setCentralItem(ge) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GradientWidget.py b/pyqtgraph/examples/GradientWidget.py deleted file mode 100644 index 623171b..0000000 --- a/pyqtgraph/examples/GradientWidget.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates the appearance / interactivity of GradientWidget -(without actually doing anything useful with it) - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - - -app = pg.mkQApp("Gradiant Widget Example") -w = QtGui.QMainWindow() -w.show() -w.setWindowTitle('pyqtgraph example: GradientWidget') -w.setGeometry(10, 50, 400, 400) -cw = QtGui.QWidget() -w.setCentralWidget(cw) - -l = QtGui.QGridLayout() -l.setSpacing(0) -cw.setLayout(l) - -w1 = pg.GradientWidget(orientation='top') -w2 = pg.GradientWidget(orientation='right', allowAdd=False) -#w2.setTickColor(1, QtGui.QColor(255,255,255)) -w3 = pg.GradientWidget(orientation='bottom', allowAdd=False, allowRemove=False) -w4 = pg.GradientWidget(orientation='left') -w4.loadPreset('spectrum') -label = QtGui.QLabel(""" -- Click a triangle to change its color -- Drag triangles to move -- Right-click a gradient to load triangle presets -- Click in an empty area to add a new color - (adding is disabled for the bottom-side and right-side widgets) -- Right click a triangle to remove - (only possible if more than two triangles are visible) - (removing is disabled for the bottom-side widget) -""") - -l.addWidget(w1, 0, 1) -l.addWidget(w2, 1, 2) -l.addWidget(w3, 2, 1) -l.addWidget(w4, 1, 0) -l.addWidget(label, 1, 1) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - - - diff --git a/pyqtgraph/examples/GraphItem.py b/pyqtgraph/examples/GraphItem.py deleted file mode 100644 index 094b84b..0000000 --- a/pyqtgraph/examples/GraphItem.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple example of GraphItem use. -""" - - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -# Enable antialiasing for prettier plots -pg.setConfigOptions(antialias=True) - -w = pg.GraphicsLayoutWidget(show=True) -w.setWindowTitle('pyqtgraph example: GraphItem') -v = w.addViewBox() -v.setAspectLocked() - -g = pg.GraphItem() -v.addItem(g) - -## Define positions of nodes -pos = np.array([ - [0,0], - [10,0], - [0,10], - [10,10], - [5,5], - [15,5] - ]) - -## Define the set of connections in the graph -adj = np.array([ - [0,1], - [1,3], - [3,2], - [2,0], - [1,5], - [3,5], - ]) - -## Define the symbol to use for each node (this is optional) -symbols = ['o','o','o','o','t','+'] - -## Define the line style for each connection (this is optional) -lines = np.array([ - (255,0,0,255,1), - (255,0,255,255,2), - (255,0,255,255,3), - (255,255,0,255,2), - (255,0,0,255,1), - (255,255,255,255,4), - ], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)]) - -## Update the graph -g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False) - - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GraphicsLayout.py b/pyqtgraph/examples/GraphicsLayout.py deleted file mode 100644 index 74d61c1..0000000 --- a/pyqtgraph/examples/GraphicsLayout.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Demonstrate the use of layouts to control placement of multiple plots / views / -labels - - -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np - -app = pg.mkQApp("Gradiant Layout Example") -view = pg.GraphicsView() -l = pg.GraphicsLayout(border=(100,100,100)) -view.setCentralItem(l) -view.show() -view.setWindowTitle('pyqtgraph example: GraphicsLayout') -view.resize(800,600) - -## Title at top -text = """ -This example demonstrates the use of GraphicsLayout to arrange items in a grid.
-The items added to the layout must be subclasses of QGraphicsWidget (this includes
-PlotItem, ViewBox, LabelItem, and GrphicsLayout itself). -""" -l.addLabel(text, col=1, colspan=4) -l.nextRow() - -## Put vertical label on left side -l.addLabel('Long Vertical Label', angle=-90, rowspan=3) - -## Add 3 plots into the first row (automatic position) -p1 = l.addPlot(title="Plot 1") -p2 = l.addPlot(title="Plot 2") -vb = l.addViewBox(lockAspect=True) -img = pg.ImageItem(np.random.normal(size=(100,100))) -vb.addItem(img) -vb.autoRange() - - -## Add a sub-layout into the second row (automatic position) -## The added item should avoid the first column, which is already filled -l.nextRow() -l2 = l.addLayout(colspan=3, border=(50,0,0)) -l2.setContentsMargins(10, 10, 10, 10) -l2.addLabel("Sub-layout: this layout demonstrates the use of shared axes and axis labels", colspan=3) -l2.nextRow() -l2.addLabel('Vertical Axis Label', angle=-90, rowspan=2) -p21 = l2.addPlot() -p22 = l2.addPlot() -l2.nextRow() -p23 = l2.addPlot() -p24 = l2.addPlot() -l2.nextRow() -l2.addLabel("HorizontalAxisLabel", col=1, colspan=2) - -## hide axes on some plots -p21.hideAxis('bottom') -p22.hideAxis('bottom') -p22.hideAxis('left') -p24.hideAxis('left') -p21.hideButtons() -p22.hideButtons() -p23.hideButtons() -p24.hideButtons() - - -## Add 2 more plots into the third row (manual position) -p4 = l.addPlot(row=3, col=1) -p5 = l.addPlot(row=3, col=2, colspan=2) - -## show some content in the plots -p1.plot([1,3,2,4,3,5]) -p2.plot([1,3,2,4,3,5]) -p4.plot([1,3,2,4,3,5]) -p5.plot([1,3,2,4,3,5]) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/GraphicsScene.py b/pyqtgraph/examples/GraphicsScene.py deleted file mode 100644 index 69be5b5..0000000 --- a/pyqtgraph/examples/GraphicsScene.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -from pyqtgraph.GraphicsScene import GraphicsScene - -app = pg.mkQApp("GraphicsScene Example") -win = pg.GraphicsView() -win.show() - - -class Obj(QtGui.QGraphicsObject): - def __init__(self): - QtGui.QGraphicsObject.__init__(self) - GraphicsScene.registerObject(self) - - def paint(self, p, *args): - p.setPen(pg.mkPen(200,200,200)) - p.drawRect(self.boundingRect()) - - def boundingRect(self): - return QtCore.QRectF(0, 0, 20, 20) - - def mouseClickEvent(self, ev): - if ev.double(): - print("double click") - else: - print("click") - ev.accept() - - #def mouseDragEvent(self, ev): - #print "drag" - #ev.accept() - #self.setPos(self.pos() + ev.pos()-ev.lastPos()) - - - -vb = pg.ViewBox() -win.setCentralItem(vb) - -obj = Obj() -vb.addItem(obj) - -obj2 = Obj() -win.addItem(obj2) - -def clicked(): - print("button click") -btn = QtGui.QPushButton("BTN") -btn.clicked.connect(clicked) -prox = QtGui.QGraphicsProxyWidget() -prox.setWidget(btn) -prox.setPos(100,0) -vb.addItem(prox) - -g = pg.GridItem() -vb.addItem(g) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/HistogramLUT.py b/pyqtgraph/examples/HistogramLUT.py deleted file mode 100644 index 4c9ef4c..0000000 --- a/pyqtgraph/examples/HistogramLUT.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Use a HistogramLUTWidget to control the contrast / coloration of an image. -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = pg.mkQApp("Histogram Lookup Table Example") -win = QtGui.QMainWindow() -win.resize(800,600) -win.show() -win.setWindowTitle('pyqtgraph example: Histogram LUT') - -cw = QtGui.QWidget() -win.setCentralWidget(cw) - -l = QtGui.QGridLayout() -cw.setLayout(l) -l.setSpacing(0) - -v = pg.GraphicsView() -vb = pg.ViewBox() -vb.setAspectLocked() -v.setCentralItem(vb) -l.addWidget(v, 0, 0, 3, 1) - -w = pg.HistogramLUTWidget() -l.addWidget(w, 0, 1) - -monoRadio = QtGui.QRadioButton('mono') -rgbaRadio = QtGui.QRadioButton('rgba') -l.addWidget(monoRadio, 1, 1) -l.addWidget(rgbaRadio, 2, 1) -monoRadio.setChecked(True) - -def setLevelMode(): - mode = 'mono' if monoRadio.isChecked() else 'rgba' - w.setLevelMode(mode) -monoRadio.toggled.connect(setLevelMode) - -data = pg.gaussianFilter(np.random.normal(size=(256, 256, 3)), (20, 20, 0)) -for i in range(32): - for j in range(32): - data[i*8, j*8] += .1 -img = pg.ImageItem(data) -vb.addItem(img) -vb.autoRange() - -w.setImageItem(img) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ImageItem.py b/pyqtgraph/examples/ImageItem.py deleted file mode 100644 index 4914108..0000000 --- a/pyqtgraph/examples/ImageItem.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates very basic use of ImageItem to display image data inside a ViewBox. -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import pyqtgraph as pg -import pyqtgraph.ptime as ptime - -app = pg.mkQApp("ImageItem Example") - -## Create window with GraphicsView widget -win = pg.GraphicsLayoutWidget() -win.show() ## show widget alone in its own window -win.setWindowTitle('pyqtgraph example: ImageItem') -view = win.addViewBox() - -## lock the aspect ratio so pixels are always square -view.setAspectLocked(True) - -## Create image item -img = pg.ImageItem(border='w') -view.addItem(img) - -## Set initial view bounds -view.setRange(QtCore.QRectF(0, 0, 600, 600)) - -## Create random image -data = np.random.normal(size=(15, 600, 600), loc=1024, scale=64).astype(np.uint16) -i = 0 - -updateTime = ptime.time() -fps = 0 - -timer = QtCore.QTimer() -timer.setSingleShot(True) -# not using QTimer.singleShot() because of persistence on PyQt. see PR #1605 - -def updateData(): - global img, data, i, updateTime, fps - - ## Display the data - img.setImage(data[i]) - i = (i+1) % data.shape[0] - - timer.start(1) - now = ptime.time() - fps2 = 1.0 / (now-updateTime) - updateTime = now - fps = fps * 0.9 + fps2 * 0.1 - - #print "%0.1f fps" % fps - -timer.timeout.connect(updateData) -updateData() - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ImageView.py b/pyqtgraph/examples/ImageView.py deleted file mode 100644 index 56ee2d0..0000000 --- a/pyqtgraph/examples/ImageView.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of ImageView with 3-color image stacks. -ImageView is a high-level widget for displaying and analyzing 2D and 3D data. -ImageView provides: - - 1. A zoomable region (ViewBox) for displaying the image - 2. A combination histogram and gradient editor (HistogramLUTItem) for - controlling the visual appearance of the image - 3. A timeline for selecting the currently displayed frame (for 3D data only). - 4. Tools for very basic analysis of image data (see ROI and Norm buttons) - -""" -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg - -# Interpret image data as row-major instead of col-major -pg.setConfigOptions(imageAxisOrder='row-major') - -app = pg.mkQApp("ImageView Example") - -## Create window with ImageView widget -win = QtGui.QMainWindow() -win.resize(800,800) -imv = pg.ImageView() -win.setCentralWidget(imv) -win.show() -win.setWindowTitle('pyqtgraph example: ImageView') - -## Create random 3D data set with time varying signals -dataRed = np.ones((100, 200, 200)) * np.linspace(90, 150, 100)[:, np.newaxis, np.newaxis] -dataRed += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 -dataGrn = np.ones((100, 200, 200)) * np.linspace(90, 180, 100)[:, np.newaxis, np.newaxis] -dataGrn += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 -dataBlu = np.ones((100, 200, 200)) * np.linspace(180, 90, 100)[:, np.newaxis, np.newaxis] -dataBlu += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100 - -data = np.concatenate( - (dataRed[:, :, :, np.newaxis], dataGrn[:, :, :, np.newaxis], dataBlu[:, :, :, np.newaxis]), axis=3 -) - - -## Display the data and assign each frame a time value from 1.0 to 3.0 -imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0])) - -## Set a custom color map -colors = [ - (0, 0, 0), - (45, 5, 61), - (84, 42, 55), - (150, 87, 60), - (208, 171, 141), - (255, 255, 255) -] -cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors) -imv.setColorMap(cmap) - -# Start up with an ROI -imv.ui.roiBtn.setChecked(True) -imv.roiClicked() - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/InfiniteLine.py b/pyqtgraph/examples/InfiniteLine.py deleted file mode 100644 index d90ae14..0000000 --- a/pyqtgraph/examples/InfiniteLine.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates some of the plotting items available in pyqtgraph. -""" - -import initExample ## Add path to library (just for examples; you do not need this) -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - - -app = pg.mkQApp("InfiniteLine Example") -win = pg.GraphicsLayoutWidget(show=True, title="Plotting items examples") -win.resize(1000,600) - -# Enable antialiasing for prettier plots -pg.setConfigOptions(antialias=True) - -# Create a plot with some random data -p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100, scale=10), pen=0.5) -p1.setYRange(-40, 40) - -# Add three infinite lines with labels -inf1 = pg.InfiniteLine(movable=True, angle=90, label='x={value:0.2f}', - labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True}) -inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='y={value:0.2f}mm', - labelOpts={'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)}) -inf3 = pg.InfiniteLine(movable=True, angle=45, pen='g', label='diagonal', - labelOpts={'rotateAxis': [1, 0], 'fill': (0, 200, 0, 100), 'movable': True}) -inf1.setPos([2,2]) -p1.addItem(inf1) -p1.addItem(inf2) -p1.addItem(inf3) - -# Add a linear region with a label -lr = pg.LinearRegionItem(values=[70, 80]) -p1.addItem(lr) -label = pg.InfLineLabel(lr.lines[1], "region 1", position=0.95, rotateAxis=(1,0), anchor=(1, 1)) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/JoystickButton.py b/pyqtgraph/examples/JoystickButton.py deleted file mode 100644 index c696590..0000000 --- a/pyqtgraph/examples/JoystickButton.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" -JoystickButton is a button with x/y values. When the button is depressed and the -mouse dragged, the x/y values change to follow the mouse. -When the mouse button is released, the x/y values change to 0,0 (rather like -letting go of the joystick). -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = pg.mkQApp("Joystick Button Example") -mw = QtGui.QMainWindow() -mw.resize(300,50) -mw.setWindowTitle('pyqtgraph example: JoystickButton') -cw = QtGui.QWidget() -mw.setCentralWidget(cw) -layout = QtGui.QGridLayout() -cw.setLayout(layout) -mw.show() - -l1 = pg.ValueLabel(siPrefix=True, suffix='m') -l2 = pg.ValueLabel(siPrefix=True, suffix='m') -jb = pg.JoystickButton() -jb.setFixedWidth(30) -jb.setFixedHeight(30) - - -layout.addWidget(l1, 0, 0) -layout.addWidget(l2, 0, 1) -layout.addWidget(jb, 0, 2) - -x = 0 -y = 0 -def update(): - global x, y, l1, l2, jb - dx, dy = jb.getState() - x += dx * 1e-3 - y += dy * 1e-3 - l1.setValue(x) - l2.setValue(y) -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(30) - - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/Legend.py b/pyqtgraph/examples/Legend.py deleted file mode 100644 index f3683cd..0000000 --- a/pyqtgraph/examples/Legend.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates basic use of LegendItem - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.plot() -win.setWindowTitle('pyqtgraph example: BarGraphItem') - -# # option1: only for .plot(), following c1,c2 for example----------------------- -# win.addLegend(frame=False, colCount=2) - -# bar graph -x = np.arange(10) -y = np.sin(x+2) * 3 -bg1 = pg.BarGraphItem(x=x, height=y, width=0.3, brush='b', pen='w', name='bar') -win.addItem(bg1) - -# curve -c1 = win.plot([np.random.randint(0,8) for i in range(10)], pen='r', symbol='t', symbolPen='r', symbolBrush='g', name='curve1') -c2 = win.plot([2,1,4,3,1,3,2,4,3,2], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='curve2') - -# scatter plot -s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120), name='scatter') -spots = [{'pos': [i, np.random.randint(-3, 3)], 'data': 1} for i in range(10)] -s1.addPoints(spots) -win.addItem(s1) - -# # option2: generic method------------------------------------------------ -legend = pg.LegendItem((80,60), offset=(70,20)) -legend.setParentItem(win.graphicsItem()) -legend.addItem(bg1, 'bar') -legend.addItem(c1, 'curve1') -legend.addItem(c2, 'curve2') -legend.addItem(s1, 'scatter') - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/LogPlotTest.py b/pyqtgraph/examples/LogPlotTest.py deleted file mode 100644 index 1e6bf66..0000000 --- a/pyqtgraph/examples/LogPlotTest.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple logarithmic plotting test -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Log Plot Example") - -win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples") -win.resize(1000,600) -win.setWindowTitle('pyqtgraph example: LogPlotTest') - - -p5 = win.addPlot(title="Scatter plot, axis labels, log scale") -x = np.random.normal(size=1000) * 1e-5 -y = x*1000 + 0.005 * np.random.normal(size=1000) -y -= y.min()-1.0 -mask = x > 1e-15 -x = x[mask] -y = y[mask] -p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50)) -p5.setLabel('left', "Y Axis", units='A') -p5.setLabel('bottom', "Y Axis", units='s') -p5.setLogMode(x=True, y=False) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/MouseSelection.py b/pyqtgraph/examples/MouseSelection.py deleted file mode 100644 index 3a57375..0000000 --- a/pyqtgraph/examples/MouseSelection.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates selecting plot curves by mouse click -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.plot() -win.setWindowTitle('pyqtgraph example: Plot data selection') - -curves = [ - pg.PlotCurveItem(y=np.sin(np.linspace(0, 20, 1000)), pen='r', clickable=True), - pg.PlotCurveItem(y=np.sin(np.linspace(1, 21, 1000)), pen='g', clickable=True), - pg.PlotCurveItem(y=np.sin(np.linspace(2, 22, 1000)), pen='b', clickable=True), - ] - -def plotClicked(curve): - global curves - for i,c in enumerate(curves): - if c is curve: - c.setPen('rgb'[i], width=3) - else: - c.setPen('rgb'[i], width=1) - - -for c in curves: - win.addItem(c) - c.sigClicked.connect(plotClicked) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/MultiPlotSpeedTest.py b/pyqtgraph/examples/MultiPlotSpeedTest.py deleted file mode 100644 index e8f7ee8..0000000 --- a/pyqtgraph/examples/MultiPlotSpeedTest.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -Test the speed of rapidly updating multiple plot curves -""" - -## Add path to library (just for examples; you do not need this) -import initExample - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg -from pyqtgraph.ptime import time -app = pg.mkQApp("MultiPlot Speed Test") - -plot = pg.plot() -plot.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest') -plot.setLabel('bottom', 'Index', units='B') - -nPlots = 100 -nSamples = 500 -curves = [] -for idx in range(nPlots): - curve = pg.PlotCurveItem(pen=(idx,nPlots*1.3)) - plot.addItem(curve) - curve.setPos(0,idx*6) - curves.append(curve) - -plot.setYRange(0, nPlots*6) -plot.setXRange(0, nSamples) -plot.resize(600,900) - -rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.]) -plot.addItem(rgn) - - -data = np.random.normal(size=(nPlots*23,nSamples)) -ptr = 0 -lastTime = time() -fps = None -count = 0 -def update(): - global curve, data, ptr, plot, lastTime, fps, nPlots, count - count += 1 - - for i in range(nPlots): - curves[i].setData(data[(ptr+i)%data.shape[0]]) - - ptr += nPlots - now = time() - dt = now - lastTime - lastTime = now - if fps is None: - fps = 1.0/dt - else: - s = np.clip(dt*3., 0, 1) - fps = fps * (1-s) + (1.0/dt) * s - plot.setTitle('%0.2f fps' % fps) - #app.processEvents() ## force complete redraw for every plot -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(0) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/MultiPlotWidget.py b/pyqtgraph/examples/MultiPlotWidget.py deleted file mode 100644 index 4802a04..0000000 --- a/pyqtgraph/examples/MultiPlotWidget.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from numpy import linspace -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -from pyqtgraph import MultiPlotWidget -try: - from pyqtgraph.metaarray import * -except: - print("MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)") - exit() - -app = pg.mkQApp("MultiPlot Widget Example") -mw = QtGui.QMainWindow() -mw.resize(800,800) -pw = MultiPlotWidget() -mw.setCentralWidget(pw) -mw.show() - -data = np.random.normal(size=(3, 1000)) * np.array([[0.1], [1e-5], [1]]) -ma = MetaArray(data, info=[ - {'name': 'Signal', 'cols': [ - {'name': 'Col1', 'units': 'V'}, - {'name': 'Col2', 'units': 'A'}, - {'name': 'Col3'}, - ]}, - {'name': 'Time', 'values': linspace(0., 1., 1000), 'units': 's'} - ]) -pw.plot(ma, pen='y') - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - diff --git a/pyqtgraph/examples/MultiplePlotAxes.py b/pyqtgraph/examples/MultiplePlotAxes.py deleted file mode 100644 index 75e0c68..0000000 --- a/pyqtgraph/examples/MultiplePlotAxes.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates a way to put multiple axes around a single plot. - -(This will eventually become a built-in feature of PlotItem) - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -pg.mkQApp() - -pw = pg.PlotWidget() -pw.show() -pw.setWindowTitle('pyqtgraph example: MultiplePlotAxes') -p1 = pw.plotItem -p1.setLabels(left='axis 1') - -## create a new ViewBox, link the right axis to its coordinate system -p2 = pg.ViewBox() -p1.showAxis('right') -p1.scene().addItem(p2) -p1.getAxis('right').linkToView(p2) -p2.setXLink(p1) -p1.getAxis('right').setLabel('axis2', color='#0000ff') - -## create third ViewBox. -## this time we need to create a new axis as well. -p3 = pg.ViewBox() -ax3 = pg.AxisItem('right') -p1.layout.addItem(ax3, 2, 3) -p1.scene().addItem(p3) -ax3.linkToView(p3) -p3.setXLink(p1) -ax3.setZValue(-10000) -ax3.setLabel('axis 3', color='#ff0000') - - -## Handle view resizing -def updateViews(): - ## view has resized; update auxiliary views to match - global p1, p2, p3 - p2.setGeometry(p1.vb.sceneBoundingRect()) - p3.setGeometry(p1.vb.sceneBoundingRect()) - - ## need to re-update linked axes since this was called - ## incorrectly while views had different shapes. - ## (probably this should be handled in ViewBox.resizeEvent) - p2.linkedViewChanged(p1.vb, p2.XAxis) - p3.linkedViewChanged(p1.vb, p3.XAxis) - -updateViews() -p1.vb.sigResized.connect(updateViews) - - -p1.plot([1,2,4,8,16,32]) -p2.addItem(pg.PlotCurveItem([10,20,40,80,40,20], pen='b')) -p3.addItem(pg.PlotCurveItem([3200,1600,800,400,200,100], pen='r')) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/NonUniformImage.py b/pyqtgraph/examples/NonUniformImage.py deleted file mode 100644 index 9d13cc0..0000000 --- a/pyqtgraph/examples/NonUniformImage.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Display a non-uniform image. -This example displays 2-d data as an image with non-uniformly -distributed sample points. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np -from pyqtgraph.graphicsItems.GradientEditorItem import Gradients -from pyqtgraph.graphicsItems.NonUniformImage import NonUniformImage - - -RPM2RADS = 2 * np.pi / 60 -RADS2RPM = 1 / RPM2RADS - -kfric = 1 # [Ws/rad] angular damping coefficient [0;100] -kfric3 = 1.5e-6 # [Ws3/rad3] angular damping coefficient (3rd order) [0;10-3] -psi = 0.2 # [Vs] flux linkage [0.001;10] -res = 5e-3 # [Ohm] resistance [0;100] -v_ref = 200 # [V] reference DC voltage [0;1000] -k_v = 5 # linear voltage coefficient [-100;100] - -# create the (non-uniform) scales -tau = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 220], dtype=np.float32) -w = np.array([0, 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000], dtype=np.float32) * RPM2RADS -v = 380 - -# calculate the power losses -TAU, W = np.meshgrid(tau, w, indexing='ij') -V = np.ones_like(TAU) * v - -P_loss = kfric * W + kfric3 * W ** 3 + (res * (TAU / psi) ** 2) + k_v * (V - v_ref) - -P_mech = TAU * W -P_loss[P_mech > 1.5e5] = np.NaN - -# green - orange - red -Gradients['gor'] = {'ticks': [(0.0, (74, 158, 71)), (0.5, (255, 230, 0)), (1, (191, 79, 76))], 'mode': 'rgb'} - -app = pg.mkQApp("NonUniform Image Example") - -win = QtGui.QMainWindow() -cw = pg.GraphicsLayoutWidget() -win.show() -win.resize(600, 400) -win.setCentralWidget(cw) -win.setWindowTitle('pyqtgraph example: Non-uniform Image') - -p = cw.addPlot(title="Power Losses [W]", row=0, col=0) - -lut = pg.HistogramLUTItem() - -p.setMouseEnabled(x=False, y=False) - -cw.addItem(lut) - -# load the gradient -lut.gradient.loadPreset('gor') - -image = NonUniformImage(w * RADS2RPM, tau, P_loss.T) -image.setLookupTable(lut, autoLevel=True) -image.setZValue(-1) -p.addItem(image) - -h = image.getHistogram() -lut.plot.setData(*h) - -p.showGrid(x=True, y=True) - -p.setLabel(axis='bottom', text='Speed [rpm]') -p.setLabel(axis='left', text='Torque [Nm]') - -# elevate the grid lines -p.axes['bottom']['item'].setZValue(1000) -p.axes['left']['item'].setZValue(1000) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/PColorMeshItem.py b/pyqtgraph/examples/PColorMeshItem.py deleted file mode 100644 index 44604c8..0000000 --- a/pyqtgraph/examples/PColorMeshItem.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates very basic use of PColorMeshItem -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import pyqtgraph as pg -import pyqtgraph.ptime as ptime - -app = pg.mkQApp("PColorMesh Example") - -## Create window with GraphicsView widget -win = pg.GraphicsLayoutWidget() -win.show() ## show widget alone in its own window -win.setWindowTitle('pyqtgraph example: pColorMeshItem') -view = win.addViewBox() - - -## Create data - -# To enhance the non-grid meshing, we randomize the polygon vertices per and -# certain amount -randomness = 5 - -# x and y being the vertices of the polygons, they share the same shape -# However the shape can be different in both dimension -xn = 50 # nb points along x -yn = 40 # nb points along y - - -x = np.repeat(np.arange(1, xn+1), yn).reshape(xn, yn)\ - + np.random.random((xn, yn))*randomness -y = np.tile(np.arange(1, yn+1), xn).reshape(xn, yn)\ - + np.random.random((xn, yn))*randomness -x.sort(axis=0) -y.sort(axis=0) - - -# z being the color of the polygons its shape must be decreased by one in each dimension -z = np.exp(-(x*xn)**2/1000)[:-1,:-1] - -## Create image item -edgecolors = None -antialiasing = False -# edgecolors = {'color':'w', 'width':2} # May be uncommened to see edgecolor effect -# antialiasing = True # May be uncommened to see antialiasing effect -pcmi = pg.PColorMeshItem(edgecolors=edgecolors, antialiasing=antialiasing) -view.addItem(pcmi) - - -## Set the animation -fps = 25 # Frame per second of the animation - -# Wave parameters -wave_amplitude = 3 -wave_speed = 0.3 -wave_length = 10 -color_speed = 0.3 - -timer = QtCore.QTimer() -timer.setSingleShot(True) -# not using QTimer.singleShot() because of persistence on PyQt. see PR #1605 - -i=0 -def updateData(): - global i - - ## Display the new data set - new_x = x - new_y = y+wave_amplitude*np.cos(x/wave_length+i) - new_z = np.exp(-(x-np.cos(i*color_speed)*xn)**2/1000)[:-1,:-1] - pcmi.setData(new_x, - new_y, - new_z) - - i += wave_speed - timer.start(1000//fps) - -timer.timeout.connect(updateData) -updateData() - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/PanningPlot.py b/pyqtgraph/examples/PanningPlot.py deleted file mode 100644 index 874bf33..0000000 --- a/pyqtgraph/examples/PanningPlot.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Shows use of PlotWidget to display panning data - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: PanningPlot') - -plt = win.addPlot() -#plt.setAutoVisibleOnly(y=True) -curve = plt.plot() - -data = [] -count = 0 -def update(): - global data, curve, count - data.append(np.random.normal(size=10) + np.sin(count * 0.1) * 5) - if len(data) > 100: - data.pop(0) - curve.setData(np.hstack(data)) - count += 1 - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(50) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/PlotAutoRange.py b/pyqtgraph/examples/PlotAutoRange.py deleted file mode 100644 index a15f2e3..0000000 --- a/pyqtgraph/examples/PlotAutoRange.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -This example demonstrates the different auto-ranging capabilities of ViewBoxes -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Plot Auto Range Example") -#mw = QtGui.QMainWindow() -#mw.resize(800,800) - -win = pg.GraphicsLayoutWidget(show=True, title="Plot auto-range examples") -win.resize(800,600) -win.setWindowTitle('pyqtgraph example: PlotAutoRange') - -d = np.random.normal(size=100) -d[50:54] += 10 -p1 = win.addPlot(title="95th percentile range", y=d) -p1.enableAutoRange('y', 0.95) - - -p2 = win.addPlot(title="Auto Pan Only") -p2.setAutoPan(y=True) -curve = p2.plot() -def update(): - t = pg.time() - - data = np.ones(100) * np.sin(t) - data[50:60] += np.sin(t) - global curve - curve.setData(data) - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(50) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - diff --git a/pyqtgraph/examples/PlotSpeedTest.py b/pyqtgraph/examples/PlotSpeedTest.py deleted file mode 100644 index f5d630b..0000000 --- a/pyqtgraph/examples/PlotSpeedTest.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -Update a simple plot as rapidly as possible to measure speed. -""" - -## Add path to library (just for examples; you do not need this) -import initExample - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg -from pyqtgraph.ptime import time -app = pg.mkQApp("Plot Speed Test") - -p = pg.plot() -p.setWindowTitle('pyqtgraph example: PlotSpeedTest') -p.setRange(QtCore.QRectF(0, -10, 5000, 20)) -p.setLabel('bottom', 'Index', units='B') -curve = p.plot() - -#curve.setFillBrush((0, 0, 100, 100)) -#curve.setFillLevel(0) - -#lr = pg.LinearRegionItem([100, 4900]) -#p.addItem(lr) - -data = np.random.normal(size=(50,5000)) -ptr = 0 -lastTime = time() -fps = None -def update(): - global curve, data, ptr, p, lastTime, fps - curve.setData(data[ptr%10]) - ptr += 1 - now = time() - dt = now - lastTime - lastTime = now - if fps is None: - fps = 1.0/dt - else: - s = np.clip(dt*3., 0, 1) - fps = fps * (1-s) + (1.0/dt) * s - p.setTitle('%0.2f fps' % fps) - app.processEvents() ## force complete redraw for every plot -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(0) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/PlotWidget.py b/pyqtgraph/examples/PlotWidget.py deleted file mode 100644 index a3f77b6..0000000 --- a/pyqtgraph/examples/PlotWidget.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates use of PlotWidget class. This is little more than a -GraphicsView with a PlotItem placed in its center. -""" - - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp() -mw = QtGui.QMainWindow() -mw.setWindowTitle('pyqtgraph example: PlotWidget') -mw.resize(800,800) -cw = QtGui.QWidget() -mw.setCentralWidget(cw) -l = QtGui.QVBoxLayout() -cw.setLayout(l) - -pw = pg.PlotWidget(name='Plot1') ## giving the plots names allows us to link their axes together -l.addWidget(pw) -pw2 = pg.PlotWidget(name='Plot2') -l.addWidget(pw2) -pw3 = pg.PlotWidget() -l.addWidget(pw3) - -mw.show() - -## Create an empty plot curve to be filled later, set its pen -p1 = pw.plot() -p1.setPen((200,200,100)) - -## Add in some extra graphics -rect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, 0, 1, 5e-11)) -rect.setPen(pg.mkPen(100, 200, 100)) -pw.addItem(rect) - -pw.setLabel('left', 'Value', units='V') -pw.setLabel('bottom', 'Time', units='s') -pw.setXRange(0, 2) -pw.setYRange(0, 1e-10) - -def rand(n): - data = np.random.random(n) - data[int(n*0.1):int(n*0.13)] += .5 - data[int(n*0.18)] += 2 - data[int(n*0.1):int(n*0.13)] *= 5 - data[int(n*0.18)] *= 20 - data *= 1e-12 - return data, np.arange(n, n+len(data)) / float(n) - - -def updateData(): - yd, xd = rand(10000) - p1.setData(y=yd, x=xd) - -## Start a timer to rapidly update the plot in pw -t = QtCore.QTimer() -t.timeout.connect(updateData) -t.start(50) -#updateData() - -## Multiple parameterized plots--we can autogenerate averages for these. -for i in range(0, 5): - for j in range(0, 3): - yd, xd = rand(10000) - pw2.plot(y=yd*(j+1), x=xd, params={'iter': i, 'val': j}) - -## Test large numbers -curve = pw3.plot(np.random.normal(size=100)*1e0, clickable=True) -curve.curve.setClickable(True) -curve.setPen('w') ## white pen -curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True)) - -def clicked(): - print("curve clicked") -curve.sigClicked.connect(clicked) - -lr = pg.LinearRegionItem([1, 30], bounds=[0,100], movable=True) -pw3.addItem(lr) -line = pg.InfiniteLine(angle=90, movable=True) -pw3.addItem(line) -line.setBounds([0,200]) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/Plotting.py b/pyqtgraph/examples/Plotting.py deleted file mode 100644 index c3831c0..0000000 --- a/pyqtgraph/examples/Plotting.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates many of the 2D plotting capabilities -in pyqtgraph. All of the plots may be panned/scaled by dragging with -the left/right mouse buttons. Right click on any plot to show a context menu. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Plotting Example") -#mw = QtGui.QMainWindow() -#mw.resize(800,800) - -win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples") -win.resize(1000,600) -win.setWindowTitle('pyqtgraph example: Plotting') - -# Enable antialiasing for prettier plots -pg.setConfigOptions(antialias=True) - -p1 = win.addPlot(title="Basic array plotting", y=np.random.normal(size=100)) - -p2 = win.addPlot(title="Multiple curves") -p2.plot(np.random.normal(size=100), pen=(255,0,0), name="Red curve") -p2.plot(np.random.normal(size=110)+5, pen=(0,255,0), name="Green curve") -p2.plot(np.random.normal(size=120)+10, pen=(0,0,255), name="Blue curve") - -p3 = win.addPlot(title="Drawing with points") -p3.plot(np.random.normal(size=100), pen=(200,200,200), symbolBrush=(255,0,0), symbolPen='w') - - -win.nextRow() - -p4 = win.addPlot(title="Parametric, grid enabled") -x = np.cos(np.linspace(0, 2*np.pi, 1000)) -y = np.sin(np.linspace(0, 4*np.pi, 1000)) -p4.plot(x, y) -p4.showGrid(x=True, y=True) - -p5 = win.addPlot(title="Scatter plot, axis labels, log scale") -x = np.random.normal(size=1000) * 1e-5 -y = x*1000 + 0.005 * np.random.normal(size=1000) -y -= y.min()-1.0 -mask = x > 1e-15 -x = x[mask] -y = y[mask] -p5.plot(x, y, pen=None, symbol='t', symbolPen=None, symbolSize=10, symbolBrush=(100, 100, 255, 50)) -p5.setLabel('left', "Y Axis", units='A') -p5.setLabel('bottom', "Y Axis", units='s') -p5.setLogMode(x=True, y=False) - -p6 = win.addPlot(title="Updating plot") -curve = p6.plot(pen='y') -data = np.random.normal(size=(10,1000)) -ptr = 0 -def update(): - global curve, data, ptr, p6 - curve.setData(data[ptr%10]) - if ptr == 0: - p6.enableAutoRange('xy', False) ## stop auto-scaling after the first data set is plotted - ptr += 1 -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(50) - - -win.nextRow() - -p7 = win.addPlot(title="Filled plot, axis disabled") -y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1) -p7.plot(y, fillLevel=-0.3, brush=(50,50,200,100)) -p7.showAxis('bottom', False) - - -x2 = np.linspace(-100, 100, 1000) -data2 = np.sin(x2) / x2 -p8 = win.addPlot(title="Region Selection") -p8.plot(data2, pen=(255,255,255,200)) -lr = pg.LinearRegionItem([400,700]) -lr.setZValue(-10) -p8.addItem(lr) - -p9 = win.addPlot(title="Zoom on selected region") -p9.plot(data2) -def updatePlot(): - p9.setXRange(*lr.getRegion(), padding=0) -def updateRegion(): - lr.setRegion(p9.getViewBox().viewRange()[0]) -lr.sigRegionChanged.connect(updatePlot) -p9.sigXRangeChanged.connect(updateRegion) -updatePlot() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ProgressDialog.py b/pyqtgraph/examples/ProgressDialog.py deleted file mode 100644 index 0fa9aa3..0000000 --- a/pyqtgraph/examples/ProgressDialog.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Using ProgressDialog to show progress updates in a nested process. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import time -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - -app = pg.mkQApp("Progress Dialog Example") - - -def runStage(i): - """Waste time for 2 seconds while incrementing a progress bar. - """ - with pg.ProgressDialog("Running stage %s.." % i, maximum=100, nested=True) as dlg: - for j in range(100): - time.sleep(0.02) - dlg += 1 - if dlg.wasCanceled(): - print("Canceled stage %s" % i) - break - - -def runManyStages(i): - """Iterate over runStage() 3 times while incrementing a progress bar. - """ - with pg.ProgressDialog("Running stage %s.." % i, maximum=3, nested=True, wait=0) as dlg: - for j in range(1,4): - runStage('%d.%d' % (i, j)) - dlg += 1 - if dlg.wasCanceled(): - print("Canceled stage %s" % i) - break - - -with pg.ProgressDialog("Doing a multi-stage process..", maximum=5, nested=True, wait=0) as dlg1: - for i in range(1,6): - if i == 3: - # this stage will have 3 nested progress bars - runManyStages(i) - else: - # this stage will have 2 nested progress bars - runStage(i) - - dlg1 += 1 - if dlg1.wasCanceled(): - print("Canceled process") - break - - diff --git a/pyqtgraph/examples/ROIExamples.py b/pyqtgraph/examples/ROIExamples.py deleted file mode 100644 index abea922..0000000 --- a/pyqtgraph/examples/ROIExamples.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates a variety of uses for ROI. This class provides a user-adjustable -region of interest marker. It is possible to customize the layout and -function of the scale/rotate handles in very flexible ways. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -pg.setConfigOptions(imageAxisOrder='row-major') - -## Create image to display -arr = np.ones((100, 100), dtype=float) -arr[45:55, 45:55] = 0 -arr[25, :] = 5 -arr[:, 25] = 5 -arr[75, :] = 5 -arr[:, 75] = 5 -arr[50, :] = 10 -arr[:, 50] = 10 -arr += np.sin(np.linspace(0, 20, 100)).reshape(1, 100) -arr += np.random.normal(size=(100,100)) - -# add an arrow for asymmetry -arr[10, :50] = 10 -arr[9:12, 44:48] = 10 -arr[8:13, 44:46] = 10 - - -## create GUI -app = pg.mkQApp("ROI Examples") -w = pg.GraphicsLayoutWidget(show=True, size=(1000,800), border=True) -w.setWindowTitle('pyqtgraph example: ROI Examples') - -text = """Data Selection From Image.
\n -Drag an ROI or its handles to update the selected image.
-Hold CTRL while dragging to snap to pixel boundaries
-and 15-degree rotation angles. -""" -w1 = w.addLayout(row=0, col=0) -label1 = w1.addLabel(text, row=0, col=0) -v1a = w1.addViewBox(row=1, col=0, lockAspect=True) -v1b = w1.addViewBox(row=2, col=0, lockAspect=True) -img1a = pg.ImageItem(arr) -v1a.addItem(img1a) -img1b = pg.ImageItem() -v1b.addItem(img1b) -v1a.disableAutoRange('xy') -v1b.disableAutoRange('xy') -v1a.autoRange() -v1b.autoRange() - -rois = [] -rois.append(pg.RectROI([20, 20], [20, 20], pen=(0,9))) -rois[-1].addRotateHandle([1,0], [0.5, 0.5]) -rois.append(pg.LineROI([0, 60], [20, 80], width=5, pen=(1,9))) -rois.append(pg.TriangleROI([80, 75], 20, pen=(5, 9))) -rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9))) -rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9))) -rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9))) -#rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9))) -rois.append(pg.PolyLineROI([[80, 60], [90, 30], [60, 40]], pen=(6,9), closed=True)) - -def update(roi): - img1b.setImage(roi.getArrayRegion(arr, img1a), levels=(0, arr.max())) - v1b.autoRange() - -for roi in rois: - roi.sigRegionChanged.connect(update) - v1a.addItem(roi) - -update(rois[-1]) - - - -text = """User-Modifiable ROIs
-Click on a line segment to add a new handle. -Right click on a handle to remove. -""" -w2 = w.addLayout(row=0, col=1) -label2 = w2.addLabel(text, row=0, col=0) -v2a = w2.addViewBox(row=1, col=0, lockAspect=True) -r2a = pg.PolyLineROI([[0,0], [10,10], [10,30], [30,10]], closed=True) -v2a.addItem(r2a) -r2b = pg.PolyLineROI([[0,-20], [10,-10], [10,-30]], closed=False) -v2a.addItem(r2b) -v2a.disableAutoRange('xy') -#v2b.disableAutoRange('xy') -v2a.autoRange() -#v2b.autoRange() - -text = """Building custom ROI types
-ROIs can be built with a variety of different handle types
-that scale and rotate the roi around an arbitrary center location -""" -w3 = w.addLayout(row=1, col=0) -label3 = w3.addLabel(text, row=0, col=0) -v3 = w3.addViewBox(row=1, col=0, lockAspect=True) - -r3a = pg.ROI([0,0], [10,10]) -v3.addItem(r3a) -## handles scaling horizontally around center -r3a.addScaleHandle([1, 0.5], [0.5, 0.5]) -r3a.addScaleHandle([0, 0.5], [0.5, 0.5]) - -## handles scaling vertically from opposite edge -r3a.addScaleHandle([0.5, 0], [0.5, 1]) -r3a.addScaleHandle([0.5, 1], [0.5, 0]) - -## handles scaling both vertically and horizontally -r3a.addScaleHandle([1, 1], [0, 0]) -r3a.addScaleHandle([0, 0], [1, 1]) - -r3b = pg.ROI([20,0], [10,10]) -v3.addItem(r3b) -## handles rotating around center -r3b.addRotateHandle([1, 1], [0.5, 0.5]) -r3b.addRotateHandle([0, 0], [0.5, 0.5]) - -## handles rotating around opposite corner -r3b.addRotateHandle([1, 0], [0, 1]) -r3b.addRotateHandle([0, 1], [1, 0]) - -## handles rotating/scaling around center -r3b.addScaleRotateHandle([0, 0.5], [0.5, 0.5]) - -# handles rotating/scaling around arbitrary point -r3b.addScaleRotateHandle([0.3, 0], [0.9, 0.7]) - -v3.disableAutoRange('xy') -v3.autoRange() - - -text = """Transforming objects with ROI""" -w4 = w.addLayout(row=1, col=1) -label4 = w4.addLabel(text, row=0, col=0) -v4 = w4.addViewBox(row=1, col=0, lockAspect=True) -g = pg.GridItem() -v4.addItem(g) -r4 = pg.ROI([0,0], [100,100], resizable=False, removable=True) -r4.addRotateHandle([1,0], [0.5, 0.5]) -r4.addRotateHandle([0,1], [0.5, 0.5]) -img4 = pg.ImageItem(arr) -v4.addItem(r4) -img4.setParentItem(r4) - -v4.disableAutoRange('xy') -v4.autoRange() - -# Provide a callback to remove the ROI (and its children) when -# "remove" is selected from the context menu. -def remove(): - v4.removeItem(r4) -r4.sigRemoveRequested.connect(remove) - - - - - - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ROItypes.py b/pyqtgraph/examples/ROItypes.py deleted file mode 100644 index 6d01667..0000000 --- a/pyqtgraph/examples/ROItypes.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/python -i -# -*- coding: utf-8 -*- -## Add path to library (just for examples; you do not need this) -import initExample - - -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import pyqtgraph as pg - -pg.setConfigOptions(imageAxisOrder='row-major') - -## create GUI -app = pg.mkQApp("ROI Types Examples") - -w = pg.GraphicsLayoutWidget(show=True, size=(800,800), border=True) -v = w.addViewBox(colspan=2) -v.invertY(True) ## Images usually have their Y-axis pointing downward -v.setAspectLocked(True) - - -## Create image to display -arr = np.ones((100, 100), dtype=float) -arr[45:55, 45:55] = 0 -arr[25, :] = 5 -arr[:, 25] = 5 -arr[75, :] = 5 -arr[:, 75] = 5 -arr[50, :] = 10 -arr[:, 50] = 10 - -# add an arrow for asymmetry -arr[10, :50] = 10 -arr[9:12, 44:48] = 10 -arr[8:13, 44:46] = 10 - -## Create image items, add to scene and set position -im1 = pg.ImageItem(arr) -im2 = pg.ImageItem(arr) -v.addItem(im1) -v.addItem(im2) -im2.moveBy(110, 20) -v.setRange(QtCore.QRectF(0, 0, 200, 120)) -im1.setTransform(QtGui.QTransform.fromScale(0.8, 0.5)) - -im3 = pg.ImageItem() -v2 = w.addViewBox(1,0) -v2.addItem(im3) -v2.setRange(QtCore.QRectF(0, 0, 60, 60)) -v2.invertY(True) -v2.setAspectLocked(True) -#im3.moveBy(0, 130) -im3.setZValue(10) - -im4 = pg.ImageItem() -v3 = w.addViewBox(1,1) -v3.addItem(im4) -v3.setRange(QtCore.QRectF(0, 0, 60, 60)) -v3.invertY(True) -v3.setAspectLocked(True) -#im4.moveBy(110, 130) -im4.setZValue(10) - -## create the plot -pi1 = w.addPlot(2,0, colspan=2) -#pi1 = pg.PlotItem() -#s.addItem(pi1) -#pi1.scale(0.5, 0.5) -#pi1.setGeometry(0, 170, 300, 100) - -lastRoi = None - -def updateRoi(roi): - global im1, im2, im3, im4, arr, lastRoi - if roi is None: - return - lastRoi = roi - arr1 = roi.getArrayRegion(im1.image, img=im1) - im3.setImage(arr1) - arr2 = roi.getArrayRegion(im2.image, img=im2) - im4.setImage(arr2) - updateRoiPlot(roi, arr1) - -def updateRoiPlot(roi, data=None): - if data is None: - data = roi.getArrayRegion(im1.image, img=im1) - if data is not None: - roi.curve.setData(data.mean(axis=1)) - - -## Create a variety of different ROI types -rois = [] -rois.append(pg.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9))) -rois.append(pg.LineROI([0, 0], [20, 20], width=5, pen=(1,9))) -rois.append(pg.MultiRectROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9))) -rois.append(pg.EllipseROI([110, 10], [30, 20], pen=(3,9))) -rois.append(pg.CircleROI([110, 50], [20, 20], pen=(4,9))) -rois.append(pg.PolyLineROI([[2,0], [2.1,0], [2,.1]], pen=(5,9))) -#rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0))) - -## Add each ROI to the scene and link its data to a plot curve with the same color -for r in rois: - v.addItem(r) - c = pi1.plot(pen=r.pen) - r.curve = c - r.sigRegionChanged.connect(updateRoi) - -def updateImage(): - global im1, arr, lastRoi - r = abs(np.random.normal(loc=0, scale=(arr.max()-arr.min())*0.1, size=arr.shape)) - im1.updateImage(arr + r) - updateRoi(lastRoi) - for r in rois: - updateRoiPlot(r) - -## Rapidly update one of the images with random noise -t = QtCore.QTimer() -t.timeout.connect(updateImage) -t.start(50) - - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/RemoteGraphicsView.py b/pyqtgraph/examples/RemoteGraphicsView.py deleted file mode 100644 index 2b74a8c..0000000 --- a/pyqtgraph/examples/RemoteGraphicsView.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Very simple example demonstrating RemoteGraphicsView. - -This allows graphics to be rendered in a child process and displayed in the -parent, which can improve CPU usage on multi-core processors. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -from pyqtgraph.widgets.RemoteGraphicsView import RemoteGraphicsView -app = pg.mkQApp() - -## Create the widget -v = RemoteGraphicsView(debug=False) # setting debug=True causes both processes to print information - # about interprocess communication -v.show() -v.setWindowTitle('pyqtgraph example: RemoteGraphicsView') - -## v.pg is a proxy to the remote process' pyqtgraph module. All attribute -## requests and function calls made with this object are forwarded to the -## remote process and executed there. See pyqtgraph.multiprocess.remoteproxy -## for more inormation. -plt = v.pg.PlotItem() -v.setCentralItem(plt) -plt.plot([1,4,2,3,6,2,3,4,2,3], pen='g') - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/RemoteSpeedTest.py b/pyqtgraph/examples/RemoteSpeedTest.py deleted file mode 100644 index 8d8dd21..0000000 --- a/pyqtgraph/examples/RemoteSpeedTest.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of RemoteGraphicsView to improve performance in -applications with heavy load. It works by starting a second process to handle -all graphics rendering, thus freeing up the main process to do its work. - -In this example, the update() function is very expensive and is called frequently. -After update() generates a new set of data, it can either plot directly to a local -plot (bottom) or remotely via a RemoteGraphicsView (top), allowing speed comparison -between the two cases. IF you have a multi-core CPU, it should be obvious that the -remote case is much faster. -""" - -import initExample ## Add path to library (just for examples; you do not need this) -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import pyqtgraph.widgets.RemoteGraphicsView -import numpy as np - -app = pg.mkQApp() - -view = pg.widgets.RemoteGraphicsView.RemoteGraphicsView() -pg.setConfigOptions(antialias=True) ## this will be expensive for the local plot -view.pg.setConfigOptions(antialias=True) ## prettier plots at no cost to the main process! -view.setWindowTitle('pyqtgraph example: RemoteSpeedTest') - -app.aboutToQuit.connect(view.close) - -label = QtGui.QLabel() -rcheck = QtGui.QCheckBox('plot remote') -rcheck.setChecked(True) -lcheck = QtGui.QCheckBox('plot local') -lplt = pg.PlotWidget() -layout = pg.LayoutWidget() -layout.addWidget(rcheck) -layout.addWidget(lcheck) -layout.addWidget(label) -layout.addWidget(view, row=1, col=0, colspan=3) -layout.addWidget(lplt, row=2, col=0, colspan=3) -layout.resize(800,800) -layout.show() - -## Create a PlotItem in the remote process that will be displayed locally -rplt = view.pg.PlotItem() -rplt._setProxyOptions(deferGetattr=True) ## speeds up access to rplt.plot -view.setCentralItem(rplt) - -lastUpdate = pg.ptime.time() -avgFps = 0.0 - -def update(): - global check, label, plt, lastUpdate, avgFps, rpltfunc - data = np.random.normal(size=(10000,50)).sum(axis=1) - data += 5 * np.sin(np.linspace(0, 10, data.shape[0])) - - if rcheck.isChecked(): - rplt.plot(data, clear=True, _callSync='off') ## We do not expect a return value. - ## By turning off callSync, we tell - ## the proxy that it does not need to - ## wait for a reply from the remote - ## process. - if lcheck.isChecked(): - lplt.plot(data, clear=True) - - now = pg.ptime.time() - fps = 1.0 / (now - lastUpdate) - lastUpdate = now - avgFps = avgFps * 0.8 + fps * 0.2 - label.setText("Generating %0.2f fps" % avgFps) - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(0) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ScaleBar.py b/pyqtgraph/examples/ScaleBar.py deleted file mode 100644 index 94110a5..0000000 --- a/pyqtgraph/examples/ScaleBar.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates ScaleBar -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -pg.mkQApp() -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: ScaleBar') - -vb = win.addViewBox() -vb.setAspectLocked() - -img = pg.ImageItem() -img.setImage(np.random.normal(size=(100,100))) -img.setScale(0.01) -vb.addItem(img) - -scale = pg.ScaleBar(size=0.1) -scale.setParentItem(vb) -scale.anchor((1, 1), (1, 1), offset=(-20, -20)) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ScatterPlot.py b/pyqtgraph/examples/ScatterPlot.py deleted file mode 100644 index 070f21c..0000000 --- a/pyqtgraph/examples/ScatterPlot.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Example demonstrating a variety of scatter plot features. -""" - - - -## Add path to library (just for examples; you do not need this) -import initExample - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np -from collections import namedtuple -from itertools import chain - -app = pg.mkQApp("Scatter Plot Item Example") -mw = QtGui.QMainWindow() -mw.resize(800,800) -view = pg.GraphicsLayoutWidget() ## GraphicsView with GraphicsLayout inserted by default -mw.setCentralWidget(view) -mw.show() -mw.setWindowTitle('pyqtgraph example: ScatterPlot') - -## create four areas to add plots -w1 = view.addPlot() -w2 = view.addViewBox() -w2.setAspectLocked(True) -view.nextRow() -w3 = view.addPlot() -w4 = view.addPlot() -print("Generating data, this takes a few seconds...") - -## Make all plots clickable -clickedPen = pg.mkPen('b', width=2) -lastClicked = [] -def clicked(plot, points): - global lastClicked - for p in lastClicked: - p.resetPen() - print("clicked points", points) - for p in points: - p.setPen(clickedPen) - lastClicked = points - - -## There are a few different ways we can draw scatter plots; each is optimized for different types of data: - -## 1) All spots identical and transform-invariant (top-left plot). -## In this case we can get a huge performance boost by pre-rendering the spot -## image and just drawing that image repeatedly. - -n = 300 -s1 = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 120)) -pos = np.random.normal(size=(2,n), scale=1e-5) -spots = [{'pos': pos[:,i], 'data': 1} for i in range(n)] + [{'pos': [0,0], 'data': 1}] -s1.addPoints(spots) -w1.addItem(s1) -s1.sigClicked.connect(clicked) - - -## 2) Spots are transform-invariant, but not identical (top-right plot). -## In this case, drawing is almsot as fast as 1), but there is more startup -## overhead and memory usage since each spot generates its own pre-rendered -## image. - -TextSymbol = namedtuple("TextSymbol", "label symbol scale") - -def createLabel(label, angle): - symbol = QtGui.QPainterPath() - #symbol.addText(0, 0, QFont("San Serif", 10), label) - f = QtGui.QFont() - f.setPointSize(10) - symbol.addText(0, 0, f, label) - br = symbol.boundingRect() - scale = min(1. / br.width(), 1. / br.height()) - tr = QtGui.QTransform() - tr.scale(scale, scale) - tr.rotate(angle) - tr.translate(-br.x() - br.width()/2., -br.y() - br.height()/2.) - return TextSymbol(label, tr.map(symbol), 0.1 / scale) - -random_str = lambda : (''.join([chr(np.random.randint(ord('A'),ord('z'))) for i in range(np.random.randint(1,5))]), np.random.randint(0, 360)) - -s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) -pos = np.random.normal(size=(2,n), scale=1e-5) -spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%10, 'size': 5+i/10.} for i in range(n)] -s2.addPoints(spots) -spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': label[1], 'size': label[2]*(5+i/10.)} for (i, label) in [(i, createLabel(*random_str())) for i in range(n)]] -s2.addPoints(spots) -w2.addItem(s2) -s2.sigClicked.connect(clicked) - - -## 3) Spots are not transform-invariant, not identical (bottom-left). -## This is the slowest case, since all spots must be completely re-drawn -## every time because their apparent transformation may have changed. - -s3 = pg.ScatterPlotItem( - pxMode=False, # Set pxMode=False to allow spots to transform with the view - hoverable=True, - hoverPen=pg.mkPen('g'), - hoverSize=1e-6 -) -spots3 = [] -for i in range(10): - for j in range(10): - spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 2}, 'brush':pg.intColor(i*10+j, 100)}) -s3.addPoints(spots3) -w3.addItem(s3) -s3.sigClicked.connect(clicked) - -## Test performance of large scatterplots - -s4 = pg.ScatterPlotItem( - size=10, - pen=pg.mkPen(None), - brush=pg.mkBrush(255, 255, 255, 20), - hoverable=True, - hoverSymbol='s', - hoverSize=15, - hoverPen=pg.mkPen('r', width=2), - hoverBrush=pg.mkBrush('g'), -) -n = 10000 -pos = np.random.normal(size=(2, n), scale=1e-9) -s4.addPoints( - x=pos[0], - y=pos[1], - # size=(np.random.random(n) * 20.).astype(int), - # brush=[pg.mkBrush(x) for x in np.random.randint(0, 256, (n, 3))], - data=np.arange(n) -) -w4.addItem(s4) -s4.sigClicked.connect(clicked) - - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ScatterPlotSpeedTest.py b/pyqtgraph/examples/ScatterPlotSpeedTest.py deleted file mode 100644 index e0edefc..0000000 --- a/pyqtgraph/examples/ScatterPlotSpeedTest.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -For testing rapid updates of ScatterPlotItem under various conditions. - -(Scatter plots are still rather slow to draw; expect about 20fps) -""" - -# Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore, QtWidgets -from pyqtgraph.ptime import time -import pyqtgraph.parametertree as ptree -import pyqtgraph.graphicsItems.ScatterPlotItem - -translate = QtCore.QCoreApplication.translate - -app = pg.mkQApp() -param = ptree.Parameter.create(name=translate('ScatterPlot', 'Parameters'), type='group', children=[ - dict(name='paused', title=translate('ScatterPlot', 'Paused: '), type='bool', value=False), - dict(name='count', title=translate('ScatterPlot', 'Count: '), type='int', limits=[1, None], value=500, step=100), - dict(name='size', title=translate('ScatterPlot', 'Size: '), type='int', limits=[1, None], value=10), - dict(name='randomize', title=translate('ScatterPlot', 'Randomize: '), type='bool', value=False), - dict(name='_USE_QRECT', title='_USE_QRECT: ', type='bool', value=pyqtgraph.graphicsItems.ScatterPlotItem._USE_QRECT), - dict(name='pxMode', title='pxMode: ', type='bool', value=True), - dict(name='useCache', title='useCache: ', type='bool', value=True), - dict(name='mode', title=translate('ScatterPlot', 'Mode: '), type='list', values={translate('ScatterPlot', 'New Item'): 'newItem', translate('ScatterPlot', 'Reuse Item'): 'reuseItem', translate('ScatterPlot', 'Simulate Pan/Zoom'): 'panZoom', translate('ScatterPlot', 'Simulate Hover'): 'hover'}, value='reuseItem'), -]) -for c in param.children(): - c.setDefault(c.value()) - -pt = ptree.ParameterTree(showHeader=False) -pt.setParameters(param) -p = pg.PlotWidget() -splitter = QtWidgets.QSplitter() -splitter.addWidget(pt) -splitter.addWidget(p) -splitter.show() - -data = {} -item = pg.ScatterPlotItem() -hoverBrush = pg.mkBrush('y') -ptr = 0 -lastTime = time() -fps = None -timer = QtCore.QTimer() - - -def mkDataAndItem(): - global data, fps - scale = 100 - data = { - 'pos': np.random.normal(size=(50, param['count']), scale=scale), - 'pen': [pg.mkPen(x) for x in np.random.randint(0, 256, (param['count'], 3))], - 'brush': [pg.mkBrush(x) for x in np.random.randint(0, 256, (param['count'], 3))], - 'size': (np.random.random(param['count']) * param['size']).astype(int) - } - data['pen'][0] = pg.mkPen('w') - data['size'][0] = param['size'] - data['brush'][0] = pg.mkBrush('b') - bound = 5 * scale - p.setRange(xRange=[-bound, bound], yRange=[-bound, bound]) - mkItem() - - -def mkItem(): - global item - pyqtgraph.graphicsItems.ScatterPlotItem._USE_QRECT = param['_USE_QRECT'] - item = pg.ScatterPlotItem(pxMode=param['pxMode'], **getData()) - item.opts['useCache'] = param['useCache'] - p.clear() - p.addItem(item) - - -def getData(): - pos = data['pos'] - pen = data['pen'] - size = data['size'] - brush = data['brush'] - if not param['randomize']: - pen = pen[0] - size = size[0] - brush = brush[0] - return dict(x=pos[ptr % 50], y=pos[(ptr + 1) % 50], pen=pen, brush=brush, size=size) - - -def update(): - global ptr, lastTime, fps - mode = param['mode'] - if mode == 'newItem': - mkItem() - elif mode == 'reuseItem': - item.setData(**getData()) - elif mode == 'panZoom': - item.viewTransformChanged() - item.update() - elif mode == 'hover': - pts = item.points() - old = pts[(ptr - 1) % len(pts)] - new = pts[ptr % len(pts)] - item.pointsAt(new.pos()) - old.resetBrush() # reset old's brush before setting new's to better simulate hovering - new.setBrush(hoverBrush) - - ptr += 1 - now = time() - dt = now - lastTime - lastTime = now - if fps is None: - fps = 1.0 / dt - else: - s = np.clip(dt * 3., 0, 1) - fps = fps * (1 - s) + (1.0 / dt) * s - p.setTitle('%0.2f fps' % fps) - p.repaint() - # app.processEvents() # force complete redraw for every plot - - -mkDataAndItem() -for name in ['count', 'size']: - param.child(name).sigValueChanged.connect(mkDataAndItem) -for name in ['_USE_QRECT', 'useCache', 'pxMode', 'randomize']: - param.child(name).sigValueChanged.connect(mkItem) -param.child('paused').sigValueChanged.connect(lambda _, v: timer.stop() if v else timer.start()) -timer.timeout.connect(update) -timer.start(0) - - -# Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ScatterPlotWidget.py b/pyqtgraph/examples/ScatterPlotWidget.py deleted file mode 100644 index f3766d5..0000000 --- a/pyqtgraph/examples/ScatterPlotWidget.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstration of ScatterPlotWidget for exploring structure in tabular data. - -The widget consists of four components: - -1) A list of column names from which the user may select 1 or 2 columns - to plot. If one column is selected, the data for that column will be - plotted in a histogram-like manner by using pg.pseudoScatter(). - If two columns are selected, then the - scatter plot will be generated with x determined by the first column - that was selected and y by the second. -2) A DataFilter that allows the user to select a subset of the data by - specifying multiple selection criteria. -3) A ColorMap that allows the user to determine how points are colored by - specifying multiple criteria. -4) A PlotWidget for displaying the data. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -pg.mkQApp() - -# Make up some tabular data with structure -data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float), - ('count', int), ('amplitude', float), - ('decay', float), ('type', 'U10')]) -strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E'] -typeInds = np.random.randint(5, size=1000) -data['type'] = np.array(strings)[typeInds] -data['x_pos'] = np.random.normal(size=1000) -data['x_pos'][data['type'] == 'Type-A'] -= 1 -data['x_pos'][data['type'] == 'Type-B'] -= 1 -data['x_pos'][data['type'] == 'Type-C'] += 2 -data['x_pos'][data['type'] == 'Type-D'] += 2 -data['x_pos'][data['type'] == 'Type-E'] += 2 -data['y_pos'] = np.random.normal(size=1000) + data['x_pos']*0.1 -data['y_pos'][data['type'] == 'Type-A'] += 3 -data['y_pos'][data['type'] == 'Type-B'] += 3 -data['amplitude'] = data['x_pos'] * 1.4 + data['y_pos'] + np.random.normal(size=1000, scale=0.4) -data['count'] = (np.random.exponential(size=1000, scale=100) * data['x_pos']).astype(int) -data['decay'] = np.random.normal(size=1000, scale=1e-3) + data['amplitude'] * 1e-4 -data['decay'][data['type'] == 'Type-A'] /= 2 -data['decay'][data['type'] == 'Type-E'] *= 3 - - -# Create ScatterPlotWidget and configure its fields -spw = pg.ScatterPlotWidget() -spw.setFields([ - ('x_pos', {'units': 'm'}), - ('y_pos', {'units': 'm'}), - ('count', {}), - ('amplitude', {'units': 'V'}), - ('decay', {'units': 's'}), - ('type', {'mode': 'enum', 'values': strings}), - ]) - -spw.setData(data) -spw.show() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/SimplePlot.py b/pyqtgraph/examples/SimplePlot.py deleted file mode 100644 index 03ee220..0000000 --- a/pyqtgraph/examples/SimplePlot.py +++ /dev/null @@ -1,12 +0,0 @@ -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -import pyqtgraph.exporters -import numpy as np -plt = pg.plot(np.random.normal(size=100), title="Simplest possible plotting example") - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if sys.flags.interactive != 1 or not hasattr(pg.QtCore, 'PYQT_VERSION'): - pg.QtGui.QApplication.exec_() diff --git a/pyqtgraph/examples/SpinBox.py b/pyqtgraph/examples/SpinBox.py deleted file mode 100644 index 88366cd..0000000 --- a/pyqtgraph/examples/SpinBox.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the SpinBox widget, which is an extension of -QDoubleSpinBox providing some advanced features: - - * SI-prefixed units - * Non-linear stepping modes - * Bounded/unbounded values - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import ast - -app = pg.mkQApp("SpinBox Example") - - -spins = [ - ("Floating-point spin box, min=0, no maximum.
Non-finite values (nan, inf) are permitted.", - pg.SpinBox(value=5.0, bounds=[0, None], finite=False)), - ("Integer spin box, dec stepping
(1-9, 10-90, 100-900, etc), decimals=4", - pg.SpinBox(value=10, int=True, dec=True, minStep=1, step=1, decimals=4)), - ("Float with SI-prefixed units
(n, u, m, k, M, etc)", - pg.SpinBox(value=0.9, suffix='V', siPrefix=True)), - ("Float with SI-prefixed units,
dec step=0.1, minStep=0.1", - pg.SpinBox(value=1.0, suffix='PSI', siPrefix=True, dec=True, step=0.1, minStep=0.1)), - ("Float with SI-prefixed units,
dec step=0.5, minStep=0.01", - pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)), - ("Float with SI-prefixed units,
dec step=1.0, minStep=0.001", - pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)), - ("Float with SI prefix but no suffix", - pg.SpinBox(value=1e9, siPrefix=True)), - ("Float with custom formatting", - pg.SpinBox(value=23.07, format='${value:0.02f}', - regex='\$?(?P(-?\d+(\.\d+)?)|(-?\.\d+))$')), - ("Int with suffix", - pg.SpinBox(value=999, step=1, int=True, suffix="V")), - ("Int with custom formatting", - pg.SpinBox(value=4567, step=1, int=True, bounds=[0,None], format='0x{value:X}', - regex='(0x)?(?P[0-9a-fA-F]+)$', - evalFunc=lambda s: ast.literal_eval('0x'+s))), - ("Integer with bounds=[10, 20] and wrapping", - pg.SpinBox(value=10, bounds=[10, 20], int=True, minStep=1, step=1, wrapping=True)), -] - - -win = QtGui.QMainWindow() -win.setWindowTitle('pyqtgraph example: SpinBox') -cw = QtGui.QWidget() -layout = QtGui.QGridLayout() -cw.setLayout(layout) -win.setCentralWidget(cw) -win.show() -#win.resize(300, 600) -changingLabel = QtGui.QLabel() ## updated immediately -changedLabel = QtGui.QLabel() ## updated only when editing is finished or mouse wheel has stopped for 0.3sec -changingLabel.setMinimumWidth(200) -font = changingLabel.font() -font.setBold(True) -font.setPointSize(14) -changingLabel.setFont(font) -changedLabel.setFont(font) -labels = [] - - -def valueChanged(sb): - changedLabel.setText("Final value: %s" % str(sb.value())) - -def valueChanging(sb, value): - changingLabel.setText("Value changing: %s" % str(sb.value())) - - -for text, spin in spins: - label = QtGui.QLabel(text) - labels.append(label) - layout.addWidget(label) - layout.addWidget(spin) - spin.sigValueChanged.connect(valueChanged) - spin.sigValueChanging.connect(valueChanging) - -layout.addWidget(changingLabel, 0, 1) -layout.addWidget(changedLabel, 2, 1) - - -#def mkWin(): - #win = QtGui.QMainWindow() - #g = QtGui.QFormLayout() - #w = QtGui.QWidget() - #w.setLayout(g) - #win.setCentralWidget(w) - #s1 = SpinBox(value=5, step=0.1, bounds=[-1.5, None], suffix='units') - #t1 = QtGui.QLineEdit() - #g.addRow(s1, t1) - #s2 = SpinBox(value=10e-6, dec=True, step=0.1, minStep=1e-6, suffix='A', siPrefix=True) - #t2 = QtGui.QLineEdit() - #g.addRow(s2, t2) - #s3 = SpinBox(value=1000, dec=True, step=0.5, minStep=1e-6, bounds=[1, 1e9], suffix='Hz', siPrefix=True) - #t3 = QtGui.QLineEdit() - #g.addRow(s3, t3) - #s4 = SpinBox(int=True, dec=True, step=1, minStep=1, bounds=[-10, 1000]) - #t4 = QtGui.QLineEdit() - #g.addRow(s4, t4) - - #win.show() - - #import sys - #for sb in [s1, s2, s3,s4]: - - ##QtCore.QObject.connect(sb, QtCore.SIGNAL('valueChanged(double)'), lambda v: sys.stdout.write(str(sb) + " valueChanged\n")) - ##QtCore.QObject.connect(sb, QtCore.SIGNAL('editingFinished()'), lambda: sys.stdout.write(str(sb) + " editingFinished\n")) - #sb.sigValueChanged.connect(valueChanged) - #sb.sigValueChanging.connect(valueChanging) - #sb.editingFinished.connect(lambda: sys.stdout.write(str(sb) + " editingFinished\n")) - #return win, w, [s1, s2, s3, s4] -#a = mkWin() - - -#def test(n=100): - #for i in range(n): - #win, w, sb = mkWin() - #for s in sb: - #w.setParent(None) - #s.setParent(None) - #s.valueChanged.disconnect() - #s.editingFinished.disconnect() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/Symbols.py b/pyqtgraph/examples/Symbols.py deleted file mode 100644 index 3a683e6..0000000 --- a/pyqtgraph/examples/Symbols.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example shows all the scatter plot symbols available in pyqtgraph. - -These symbols are used to mark point locations for scatter plots and some line -plots, similar to "markers" in matplotlib and vispy. -""" - -import initExample ## Add path to library (just for examples; you do not need this) -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - -app = pg.mkQApp("Symbols Examples") -win = pg.GraphicsLayoutWidget(show=True, title="Scatter Plot Symbols") -win.resize(1000,600) - -pg.setConfigOptions(antialias=True) - -plot = win.addPlot(title="Plotting with symbols") -plot.addLegend() -plot.plot([0, 1, 2, 3, 4], pen=(0,0,200), symbolBrush=(0,0,200), symbolPen='w', symbol='o', symbolSize=14, name="symbol='o'") -plot.plot([1, 2, 3, 4, 5], pen=(0,128,0), symbolBrush=(0,128,0), symbolPen='w', symbol='t', symbolSize=14, name="symbol='t'") -plot.plot([2, 3, 4, 5, 6], pen=(19,234,201), symbolBrush=(19,234,201), symbolPen='w', symbol='t1', symbolSize=14, name="symbol='t1'") -plot.plot([3, 4, 5, 6, 7], pen=(195,46,212), symbolBrush=(195,46,212), symbolPen='w', symbol='t2', symbolSize=14, name="symbol='t2'") -plot.plot([4, 5, 6, 7, 8], pen=(250,194,5), symbolBrush=(250,194,5), symbolPen='w', symbol='t3', symbolSize=14, name="symbol='t3'") -plot.plot([5, 6, 7, 8, 9], pen=(54,55,55), symbolBrush=(55,55,55), symbolPen='w', symbol='s', symbolSize=14, name="symbol='s'") -plot.plot([6, 7, 8, 9, 10], pen=(0,114,189), symbolBrush=(0,114,189), symbolPen='w', symbol='p', symbolSize=14, name="symbol='p'") -plot.plot([7, 8, 9, 10, 11], pen=(217,83,25), symbolBrush=(217,83,25), symbolPen='w', symbol='h', symbolSize=14, name="symbol='h'") -plot.plot([8, 9, 10, 11, 12], pen=(237,177,32), symbolBrush=(237,177,32), symbolPen='w', symbol='star', symbolSize=14, name="symbol='star'") -plot.plot([9, 10, 11, 12, 13], pen=(126,47,142), symbolBrush=(126,47,142), symbolPen='w', symbol='+', symbolSize=14, name="symbol='+'") -plot.plot([10, 11, 12, 13, 14], pen=(119,172,48), symbolBrush=(119,172,48), symbolPen='w', symbol='d', symbolSize=14, name="symbol='d'") -plot.plot([11, 12, 13, 14, 15], pen=(253, 216, 53), symbolBrush=(253, 216, 53), symbolPen='w', symbol='arrow_down', symbolSize=22, name="symbol='arrow_down'") -plot.plot([12, 13, 14, 15, 16], pen=(189, 189, 189), symbolBrush=(189, 189, 189), symbolPen='w', symbol='arrow_left', symbolSize=22, name="symbol='arrow_left'") -plot.plot([13, 14, 15, 16, 17], pen=(187, 26, 95), symbolBrush=(187, 26, 95), symbolPen='w', symbol='arrow_up', symbolSize=22, name="symbol='arrow_up'") -plot.plot([14, 15, 16, 17, 18], pen=(248, 187, 208), symbolBrush=(248, 187, 208), symbolPen='w', symbol='arrow_right', symbolSize=22, name="symbol='arrow_right'") -plot.setXRange(-2, 4) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/TableWidget.py b/pyqtgraph/examples/TableWidget.py deleted file mode 100644 index 0fb1aae..0000000 --- a/pyqtgraph/examples/TableWidget.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple demonstration of TableWidget, which is an extension of QTableWidget -that automatically displays a variety of tabluar data formats. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -app = pg.mkQApp("Table Widget Example") - -w = pg.TableWidget() -w.show() -w.resize(500,500) -w.setWindowTitle('pyqtgraph example: TableWidget') - - -data = np.array([ - (1, 1.6, 'x'), - (3, 5.4, 'y'), - (8, 12.5, 'z'), - (443, 1e-12, 'w'), - ], dtype=[('Column 1', int), ('Column 2', float), ('Column 3', object)]) - -w.setData(data) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/TreeWidget.py b/pyqtgraph/examples/TreeWidget.py deleted file mode 100644 index 5694031..0000000 --- a/pyqtgraph/examples/TreeWidget.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple demonstration of TreeWidget, which is an extension of QTreeWidget -that allows widgets to be added and dragged within the tree more easily. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - -app = pg.mkQApp("TreeWidget Example") - -w = pg.TreeWidget() -w.setColumnCount(2) -w.show() -w.setWindowTitle('pyqtgraph example: TreeWidget') - -i1 = QtGui.QTreeWidgetItem(["Item 1"]) -i11 = QtGui.QTreeWidgetItem(["Item 1.1"]) -i12 = QtGui.QTreeWidgetItem(["Item 1.2"]) -i2 = QtGui.QTreeWidgetItem(["Item 2"]) -i21 = QtGui.QTreeWidgetItem(["Item 2.1"]) -i211 = pg.TreeWidgetItem(["Item 2.1.1"]) -i212 = pg.TreeWidgetItem(["Item 2.1.2"]) -i22 = pg.TreeWidgetItem(["Item 2.2"]) -i3 = pg.TreeWidgetItem(["Item 3"]) -i4 = pg.TreeWidgetItem(["Item 4"]) -i5 = pg.TreeWidgetItem(["Item 5"]) -b5 = QtGui.QPushButton('Button') -i5.setWidget(1, b5) - - - -w.addTopLevelItem(i1) -w.addTopLevelItem(i2) -w.addTopLevelItem(i3) -w.addTopLevelItem(i4) -w.addTopLevelItem(i5) -i1.addChild(i11) -i1.addChild(i12) -i2.addChild(i21) -i21.addChild(i211) -i21.addChild(i212) -i2.addChild(i22) - -b1 = QtGui.QPushButton("Button") -w.setItemWidget(i1, 1, b1) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/VideoSpeedTest.py b/pyqtgraph/examples/VideoSpeedTest.py deleted file mode 100644 index c8c05d8..0000000 --- a/pyqtgraph/examples/VideoSpeedTest.py +++ /dev/null @@ -1,281 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tests the speed of image updates for an ImageItem and RawImageWidget. -The speed will generally depend on the type of data being shown, whether -it is being scaled and/or converted by lookup table, and whether OpenGL -is used by the view widget -""" - -## Add path to library (just for examples; you do not need this) -import initExample - -import argparse -import sys - -import numpy as np - -import pyqtgraph as pg -import pyqtgraph.ptime as ptime -from pyqtgraph.Qt import QtGui, QtCore, QT_LIB - -import importlib -ui_template = importlib.import_module(f'VideoTemplate_{QT_LIB.lower()}') - -try: - import cupy as cp - pg.setConfigOption("useCupy", True) - _has_cupy = True -except ImportError: - cp = None - _has_cupy = False - -try: - from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget -except ImportError: - RawImageGLWidget = None - -parser = argparse.ArgumentParser(description="Benchmark for testing video performance") -parser.add_argument('--cuda', default=False, action='store_true', help="Use CUDA to process on the GPU", dest="cuda") -parser.add_argument('--dtype', default='uint8', choices=['uint8', 'uint16', 'float'], help="Image dtype (uint8, uint16, or float)") -parser.add_argument('--frames', default=3, type=int, help="Number of image frames to generate (default=3)") -parser.add_argument('--image-mode', default='mono', choices=['mono', 'rgb'], help="Image data mode (mono or rgb)", dest='image_mode') -parser.add_argument('--levels', default=None, type=lambda s: tuple([float(x) for x in s.split(',')]), help="min,max levels to scale monochromatic image dynamic range, or rmin,rmax,gmin,gmax,bmin,bmax to scale rgb") -parser.add_argument('--lut', default=False, action='store_true', help="Use color lookup table") -parser.add_argument('--lut-alpha', default=False, action='store_true', help="Use alpha color lookup table", dest='lut_alpha') -parser.add_argument('--size', default='512x512', type=lambda s: tuple([int(x) for x in s.split('x')]), help="WxH image dimensions default='512x512'") -args = parser.parse_args(sys.argv[1:]) - -if RawImageGLWidget is not None: - # don't limit frame rate to vsync - sfmt = QtGui.QSurfaceFormat() - sfmt.setSwapInterval(0) - QtGui.QSurfaceFormat.setDefaultFormat(sfmt) - -app = pg.mkQApp("Video Speed Test Example") - -win = QtGui.QMainWindow() -win.setWindowTitle('pyqtgraph example: VideoSpeedTest') -ui = ui_template.Ui_MainWindow() -ui.setupUi(win) -win.show() - -if RawImageGLWidget is None: - ui.rawGLRadio.setEnabled(False) - ui.rawGLRadio.setText(ui.rawGLRadio.text() + " (OpenGL not available)") -else: - ui.rawGLImg = RawImageGLWidget() - ui.stack.addWidget(ui.rawGLImg) - -# read in CLI args -ui.cudaCheck.setChecked(args.cuda and _has_cupy) -ui.cudaCheck.setEnabled(_has_cupy) -ui.framesSpin.setValue(args.frames) -ui.widthSpin.setValue(args.size[0]) -ui.heightSpin.setValue(args.size[1]) -ui.dtypeCombo.setCurrentText(args.dtype) -ui.rgbCheck.setChecked(args.image_mode=='rgb') -ui.maxSpin1.setOpts(value=255, step=1) -ui.minSpin1.setOpts(value=0, step=1) -levelSpins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3] -if args.cuda and _has_cupy: - xp = cp -else: - xp = np -if args.levels is None: - ui.scaleCheck.setChecked(False) - ui.rgbLevelsCheck.setChecked(False) -else: - ui.scaleCheck.setChecked(True) - if len(args.levels) == 2: - ui.rgbLevelsCheck.setChecked(False) - ui.minSpin1.setValue(args.levels[0]) - ui.maxSpin1.setValue(args.levels[1]) - elif len(args.levels) == 6: - ui.rgbLevelsCheck.setChecked(True) - for spin,val in zip(levelSpins, args.levels): - spin.setValue(val) - else: - raise ValueError("levels argument must be 2 or 6 comma-separated values (got %r)" % (args.levels,)) -ui.lutCheck.setChecked(args.lut) -ui.alphaCheck.setChecked(args.lut_alpha) - - -#ui.graphicsView.useOpenGL() ## buggy, but you can try it if you need extra speed. - -vb = pg.ViewBox() -ui.graphicsView.setCentralItem(vb) -vb.setAspectLocked() -img = pg.ImageItem() -vb.addItem(img) - - - -LUT = None -def updateLUT(): - global LUT, ui - dtype = ui.dtypeCombo.currentText() - if dtype == 'uint8': - n = 256 - else: - n = 4096 - LUT = ui.gradient.getLookupTable(n, alpha=ui.alphaCheck.isChecked()) - if _has_cupy and xp == cp: - LUT = cp.asarray(LUT) -ui.gradient.sigGradientChanged.connect(updateLUT) -updateLUT() - -ui.alphaCheck.toggled.connect(updateLUT) - -def updateScale(): - global ui, levelSpins - if ui.rgbLevelsCheck.isChecked(): - for s in levelSpins[2:]: - s.setEnabled(True) - else: - for s in levelSpins[2:]: - s.setEnabled(False) - -updateScale() - -ui.rgbLevelsCheck.toggled.connect(updateScale) - -cache = {} -def mkData(): - with pg.BusyCursor(): - global data, cache, ui, xp - frames = ui.framesSpin.value() - width = ui.widthSpin.value() - height = ui.heightSpin.value() - cacheKey = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked(), frames, width, height) - if cacheKey not in cache: - if cacheKey[0] == 'uint8': - dt = xp.uint8 - loc = 128 - scale = 64 - mx = 255 - elif cacheKey[0] == 'uint16': - dt = xp.uint16 - loc = 4096 - scale = 1024 - mx = 2**16 - elif cacheKey[0] == 'float': - dt = xp.float32 - loc = 1.0 - scale = 0.1 - mx = 1.0 - else: - raise ValueError(f"unable to handle dtype: {cacheKey[0]}") - - if ui.rgbCheck.isChecked(): - data = xp.random.normal(size=(frames,width,height,3), loc=loc, scale=scale) - data = pg.gaussianFilter(data, (0, 6, 6, 0)) - else: - data = xp.random.normal(size=(frames,width,height), loc=loc, scale=scale) - data = pg.gaussianFilter(data, (0, 6, 6)) - if cacheKey[0] != 'float': - data = xp.clip(data, 0, mx) - data = data.astype(dt) - data[:, 10, 10:50] = mx - data[:, 9:12, 48] = mx - data[:, 8:13, 47] = mx - cache = {cacheKey: data} # clear to save memory (but keep one to prevent unnecessary regeneration) - - data = cache[cacheKey] - updateLUT() - updateSize() - -def updateSize(): - global ui, vb - frames = ui.framesSpin.value() - width = ui.widthSpin.value() - height = ui.heightSpin.value() - dtype = xp.dtype(str(ui.dtypeCombo.currentText())) - rgb = 3 if ui.rgbCheck.isChecked() else 1 - ui.sizeLabel.setText('%d MB' % (frames * width * height * rgb * dtype.itemsize / 1e6)) - vb.setRange(QtCore.QRectF(0, 0, width, height)) - - -def noticeCudaCheck(): - global xp, cache - cache = {} - if ui.cudaCheck.isChecked(): - if _has_cupy: - xp = cp - else: - xp = np - ui.cudaCheck.setChecked(False) - else: - xp = np - mkData() - -mkData() - - -ui.dtypeCombo.currentIndexChanged.connect(mkData) -ui.rgbCheck.toggled.connect(mkData) -ui.widthSpin.editingFinished.connect(mkData) -ui.heightSpin.editingFinished.connect(mkData) -ui.framesSpin.editingFinished.connect(mkData) - -ui.widthSpin.valueChanged.connect(updateSize) -ui.heightSpin.valueChanged.connect(updateSize) -ui.framesSpin.valueChanged.connect(updateSize) -ui.cudaCheck.toggled.connect(noticeCudaCheck) - - -ptr = 0 -lastTime = ptime.time() -fps = None -def update(): - global ui, ptr, lastTime, fps, LUT, img - if ui.lutCheck.isChecked(): - useLut = LUT - else: - useLut = None - - downsample = ui.downsampleCheck.isChecked() - - if ui.scaleCheck.isChecked(): - if ui.rgbLevelsCheck.isChecked(): - useScale = [ - [ui.minSpin1.value(), ui.maxSpin1.value()], - [ui.minSpin2.value(), ui.maxSpin2.value()], - [ui.minSpin3.value(), ui.maxSpin3.value()]] - else: - useScale = [ui.minSpin1.value(), ui.maxSpin1.value()] - else: - useScale = None - - if ui.rawRadio.isChecked(): - ui.rawImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) - ui.stack.setCurrentIndex(1) - elif ui.rawGLRadio.isChecked(): - ui.rawGLImg.setImage(data[ptr%data.shape[0]], lut=useLut, levels=useScale) - ui.stack.setCurrentIndex(2) - else: - img.setImage(data[ptr%data.shape[0]], autoLevels=False, levels=useScale, lut=useLut, autoDownsample=downsample) - ui.stack.setCurrentIndex(0) - #img.setImage(data[ptr%data.shape[0]], autoRange=False) - - ptr += 1 - now = ptime.time() - dt = now - lastTime - lastTime = now - if fps is None: - fps = 1.0/dt - else: - s = np.clip(dt*3., 0, 1) - fps = fps * (1-s) + (1.0/dt) * s - ui.fpsLabel.setText('%0.2f fps' % fps) - app.processEvents() ## force complete redraw for every plot -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(0) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/VideoTemplate_pyqt5.py b/pyqtgraph/examples/VideoTemplate_pyqt5.py deleted file mode 100644 index 2a039ae..0000000 --- a/pyqtgraph/examples/VideoTemplate_pyqt5.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'examples/VideoTemplate.ui' -# -# Created by: PyQt5 UI code generator 5.15.1 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt5 import QtCore, QtGui, QtWidgets - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(695, 798) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) - self.gridLayout_2.setObjectName("gridLayout_2") - self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget) - self.cudaCheck.setObjectName("cudaCheck") - self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) - self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) - self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) - self.scaleCheck.setObjectName("scaleCheck") - self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) - self.gridLayout = QtWidgets.QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) - self.rawRadio.setObjectName("rawRadio") - self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) - self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) - self.gfxRadio.setChecked(True) - self.gfxRadio.setObjectName("gfxRadio") - self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) - self.stack = QtWidgets.QStackedWidget(self.centralwidget) - self.stack.setObjectName("stack") - self.page = QtWidgets.QWidget() - self.page.setObjectName("page") - self.gridLayout_3 = QtWidgets.QGridLayout(self.page) - self.gridLayout_3.setObjectName("gridLayout_3") - self.graphicsView = GraphicsView(self.page) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) - self.stack.addWidget(self.page) - self.page_2 = QtWidgets.QWidget() - self.page_2.setObjectName("page_2") - self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) - self.gridLayout_4.setObjectName("gridLayout_4") - self.rawImg = RawImageWidget(self.page_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) - self.rawImg.setSizePolicy(sizePolicy) - self.rawImg.setObjectName("rawImg") - self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) - self.stack.addWidget(self.page_2) - self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) - self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) - self.rawGLRadio.setObjectName("rawGLRadio") - self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) - self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) - self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) - self.dtypeCombo.setObjectName("dtypeCombo") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) - self.label = QtWidgets.QLabel(self.centralwidget) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) - self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) - self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") - self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.minSpin2 = SpinBox(self.centralwidget) - self.minSpin2.setEnabled(False) - self.minSpin2.setObjectName("minSpin2") - self.horizontalLayout_2.addWidget(self.minSpin2) - self.label_3 = QtWidgets.QLabel(self.centralwidget) - self.label_3.setAlignment(QtCore.Qt.AlignCenter) - self.label_3.setObjectName("label_3") - self.horizontalLayout_2.addWidget(self.label_3) - self.maxSpin2 = SpinBox(self.centralwidget) - self.maxSpin2.setEnabled(False) - self.maxSpin2.setObjectName("maxSpin2") - self.horizontalLayout_2.addWidget(self.maxSpin2) - self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.minSpin1 = SpinBox(self.centralwidget) - self.minSpin1.setObjectName("minSpin1") - self.horizontalLayout.addWidget(self.minSpin1) - self.label_2 = QtWidgets.QLabel(self.centralwidget) - self.label_2.setAlignment(QtCore.Qt.AlignCenter) - self.label_2.setObjectName("label_2") - self.horizontalLayout.addWidget(self.label_2) - self.maxSpin1 = SpinBox(self.centralwidget) - self.maxSpin1.setObjectName("maxSpin1") - self.horizontalLayout.addWidget(self.maxSpin1) - self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) - self.horizontalLayout_3 = QtWidgets.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.minSpin3 = SpinBox(self.centralwidget) - self.minSpin3.setEnabled(False) - self.minSpin3.setObjectName("minSpin3") - self.horizontalLayout_3.addWidget(self.minSpin3) - self.label_4 = QtWidgets.QLabel(self.centralwidget) - self.label_4.setAlignment(QtCore.Qt.AlignCenter) - self.label_4.setObjectName("label_4") - self.horizontalLayout_3.addWidget(self.label_4) - self.maxSpin3 = SpinBox(self.centralwidget) - self.maxSpin3.setEnabled(False) - self.maxSpin3.setObjectName("maxSpin3") - self.horizontalLayout_3.addWidget(self.maxSpin3) - self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) - self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) - self.lutCheck.setObjectName("lutCheck") - self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) - self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) - self.alphaCheck.setObjectName("alphaCheck") - self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) - self.gradient = GradientWidget(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) - self.gradient.setSizePolicy(sizePolicy) - self.gradient.setObjectName("gradient") - self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) - self.fpsLabel = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(12) - self.fpsLabel.setFont(font) - self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter) - self.fpsLabel.setObjectName("fpsLabel") - self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) - self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) - self.rgbCheck.setObjectName("rgbCheck") - self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.centralwidget) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) - self.horizontalLayout_4 = QtWidgets.QHBoxLayout() - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) - self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) - self.framesSpin.setProperty("value", 10) - self.framesSpin.setObjectName("framesSpin") - self.horizontalLayout_4.addWidget(self.framesSpin) - self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) - self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus) - self.widthSpin.setMaximum(10000) - self.widthSpin.setProperty("value", 512) - self.widthSpin.setObjectName("widthSpin") - self.horizontalLayout_4.addWidget(self.widthSpin) - self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) - self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) - self.heightSpin.setMaximum(10000) - self.heightSpin.setProperty("value", 512) - self.heightSpin.setObjectName("heightSpin") - self.horizontalLayout_4.addWidget(self.heightSpin) - self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) - self.sizeLabel = QtWidgets.QLabel(self.centralwidget) - self.sizeLabel.setText("") - self.sizeLabel.setObjectName("sizeLabel") - self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) - MainWindow.setCentralWidget(self.centralwidget) - - self.retranslateUi(MainWindow) - self.stack.setCurrentIndex(1) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available")) - self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) - self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) - self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) - self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) - self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) - self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) - self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) - self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) - self.label.setText(_translate("MainWindow", "Data type")) - self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) - self.label_3.setText(_translate("MainWindow", "<--->")) - self.label_2.setText(_translate("MainWindow", "<--->")) - self.label_4.setText(_translate("MainWindow", "<--->")) - self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) - self.alphaCheck.setText(_translate("MainWindow", "alpha")) - self.fpsLabel.setText(_translate("MainWindow", "FPS")) - self.rgbCheck.setText(_translate("MainWindow", "RGB")) - self.label_5.setText(_translate("MainWindow", "Image size")) -from pyqtgraph import GradientWidget, GraphicsView, SpinBox -from pyqtgraph.widgets.RawImageWidget import RawImageWidget diff --git a/pyqtgraph/examples/VideoTemplate_pyqt6.py b/pyqtgraph/examples/VideoTemplate_pyqt6.py deleted file mode 100644 index f69ebe0..0000000 --- a/pyqtgraph/examples/VideoTemplate_pyqt6.py +++ /dev/null @@ -1,203 +0,0 @@ -# Form implementation generated from reading ui file 'VideoTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(695, 798) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) - self.gridLayout_2.setObjectName("gridLayout_2") - self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget) - self.cudaCheck.setObjectName("cudaCheck") - self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) - self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) - self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) - self.scaleCheck.setObjectName("scaleCheck") - self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) - self.gridLayout = QtWidgets.QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) - self.rawRadio.setObjectName("rawRadio") - self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) - self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) - self.gfxRadio.setChecked(True) - self.gfxRadio.setObjectName("gfxRadio") - self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) - self.stack = QtWidgets.QStackedWidget(self.centralwidget) - self.stack.setObjectName("stack") - self.page = QtWidgets.QWidget() - self.page.setObjectName("page") - self.gridLayout_3 = QtWidgets.QGridLayout(self.page) - self.gridLayout_3.setObjectName("gridLayout_3") - self.graphicsView = GraphicsView(self.page) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) - self.stack.addWidget(self.page) - self.page_2 = QtWidgets.QWidget() - self.page_2.setObjectName("page_2") - self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) - self.gridLayout_4.setObjectName("gridLayout_4") - self.rawImg = RawImageWidget(self.page_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) - self.rawImg.setSizePolicy(sizePolicy) - self.rawImg.setObjectName("rawImg") - self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) - self.stack.addWidget(self.page_2) - self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) - self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) - self.rawGLRadio.setObjectName("rawGLRadio") - self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) - self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) - self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) - self.dtypeCombo.setObjectName("dtypeCombo") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) - self.label = QtWidgets.QLabel(self.centralwidget) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) - self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) - self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") - self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.minSpin2 = SpinBox(self.centralwidget) - self.minSpin2.setEnabled(False) - self.minSpin2.setObjectName("minSpin2") - self.horizontalLayout_2.addWidget(self.minSpin2) - self.label_3 = QtWidgets.QLabel(self.centralwidget) - self.label_3.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.label_3.setObjectName("label_3") - self.horizontalLayout_2.addWidget(self.label_3) - self.maxSpin2 = SpinBox(self.centralwidget) - self.maxSpin2.setEnabled(False) - self.maxSpin2.setObjectName("maxSpin2") - self.horizontalLayout_2.addWidget(self.maxSpin2) - self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.minSpin1 = SpinBox(self.centralwidget) - self.minSpin1.setObjectName("minSpin1") - self.horizontalLayout.addWidget(self.minSpin1) - self.label_2 = QtWidgets.QLabel(self.centralwidget) - self.label_2.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.label_2.setObjectName("label_2") - self.horizontalLayout.addWidget(self.label_2) - self.maxSpin1 = SpinBox(self.centralwidget) - self.maxSpin1.setObjectName("maxSpin1") - self.horizontalLayout.addWidget(self.maxSpin1) - self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) - self.horizontalLayout_3 = QtWidgets.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.minSpin3 = SpinBox(self.centralwidget) - self.minSpin3.setEnabled(False) - self.minSpin3.setObjectName("minSpin3") - self.horizontalLayout_3.addWidget(self.minSpin3) - self.label_4 = QtWidgets.QLabel(self.centralwidget) - self.label_4.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.label_4.setObjectName("label_4") - self.horizontalLayout_3.addWidget(self.label_4) - self.maxSpin3 = SpinBox(self.centralwidget) - self.maxSpin3.setEnabled(False) - self.maxSpin3.setObjectName("maxSpin3") - self.horizontalLayout_3.addWidget(self.maxSpin3) - self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) - self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) - self.lutCheck.setObjectName("lutCheck") - self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) - self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) - self.alphaCheck.setObjectName("alphaCheck") - self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) - self.gradient = GradientWidget(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) - self.gradient.setSizePolicy(sizePolicy) - self.gradient.setObjectName("gradient") - self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) - self.fpsLabel = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(12) - self.fpsLabel.setFont(font) - self.fpsLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.fpsLabel.setObjectName("fpsLabel") - self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) - self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) - self.rgbCheck.setObjectName("rgbCheck") - self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.centralwidget) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) - self.horizontalLayout_4 = QtWidgets.QHBoxLayout() - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) - self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) - self.framesSpin.setProperty("value", 10) - self.framesSpin.setObjectName("framesSpin") - self.horizontalLayout_4.addWidget(self.framesSpin) - self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) - self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.PlusMinus) - self.widthSpin.setMaximum(10000) - self.widthSpin.setProperty("value", 512) - self.widthSpin.setObjectName("widthSpin") - self.horizontalLayout_4.addWidget(self.widthSpin) - self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) - self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) - self.heightSpin.setMaximum(10000) - self.heightSpin.setProperty("value", 512) - self.heightSpin.setObjectName("heightSpin") - self.horizontalLayout_4.addWidget(self.heightSpin) - self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) - self.sizeLabel = QtWidgets.QLabel(self.centralwidget) - self.sizeLabel.setText("") - self.sizeLabel.setObjectName("sizeLabel") - self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) - MainWindow.setCentralWidget(self.centralwidget) - - self.retranslateUi(MainWindow) - self.stack.setCurrentIndex(1) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available")) - self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) - self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) - self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) - self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) - self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) - self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) - self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) - self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) - self.label.setText(_translate("MainWindow", "Data type")) - self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) - self.label_3.setText(_translate("MainWindow", "<--->")) - self.label_2.setText(_translate("MainWindow", "<--->")) - self.label_4.setText(_translate("MainWindow", "<--->")) - self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) - self.alphaCheck.setText(_translate("MainWindow", "alpha")) - self.fpsLabel.setText(_translate("MainWindow", "FPS")) - self.rgbCheck.setText(_translate("MainWindow", "RGB")) - self.label_5.setText(_translate("MainWindow", "Image size")) -from pyqtgraph import GradientWidget, GraphicsView, SpinBox -from pyqtgraph.widgets.RawImageWidget import RawImageWidget diff --git a/pyqtgraph/examples/VideoTemplate_pyside2.py b/pyqtgraph/examples/VideoTemplate_pyside2.py deleted file mode 100644 index 10d3cb6..0000000 --- a/pyqtgraph/examples/VideoTemplate_pyside2.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'VideoTemplate.ui' -## -## Created by: Qt User Interface Compiler version 5.15.2 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * - -from pyqtgraph import GraphicsView -from pyqtgraph.widgets.RawImageWidget import RawImageWidget -from pyqtgraph import GradientWidget -from pyqtgraph import SpinBox - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - if not MainWindow.objectName(): - MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(695, 798) - self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName(u"centralwidget") - self.gridLayout_2 = QGridLayout(self.centralwidget) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.cudaCheck = QCheckBox(self.centralwidget) - self.cudaCheck.setObjectName(u"cudaCheck") - - self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) - - self.downsampleCheck = QCheckBox(self.centralwidget) - self.downsampleCheck.setObjectName(u"downsampleCheck") - - self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) - - self.scaleCheck = QCheckBox(self.centralwidget) - self.scaleCheck.setObjectName(u"scaleCheck") - - self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) - - self.gridLayout = QGridLayout() - self.gridLayout.setObjectName(u"gridLayout") - self.rawRadio = QRadioButton(self.centralwidget) - self.rawRadio.setObjectName(u"rawRadio") - - self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) - - self.gfxRadio = QRadioButton(self.centralwidget) - self.gfxRadio.setObjectName(u"gfxRadio") - self.gfxRadio.setChecked(True) - - self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) - - self.stack = QStackedWidget(self.centralwidget) - self.stack.setObjectName(u"stack") - self.page = QWidget() - self.page.setObjectName(u"page") - self.gridLayout_3 = QGridLayout(self.page) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.graphicsView = GraphicsView(self.page) - self.graphicsView.setObjectName(u"graphicsView") - - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) - - self.stack.addWidget(self.page) - self.page_2 = QWidget() - self.page_2.setObjectName(u"page_2") - self.gridLayout_4 = QGridLayout(self.page_2) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.rawImg = RawImageWidget(self.page_2) - self.rawImg.setObjectName(u"rawImg") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) - self.rawImg.setSizePolicy(sizePolicy) - - self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) - - self.stack.addWidget(self.page_2) - - self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) - - self.rawGLRadio = QRadioButton(self.centralwidget) - self.rawGLRadio.setObjectName(u"rawGLRadio") - - self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) - - - self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) - - self.dtypeCombo = QComboBox(self.centralwidget) - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.setObjectName(u"dtypeCombo") - - self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) - - self.label = QLabel(self.centralwidget) - self.label.setObjectName(u"label") - - self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) - - self.rgbLevelsCheck = QCheckBox(self.centralwidget) - self.rgbLevelsCheck.setObjectName(u"rgbLevelsCheck") - - self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) - - self.horizontalLayout_2 = QHBoxLayout() - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") - self.minSpin2 = SpinBox(self.centralwidget) - self.minSpin2.setObjectName(u"minSpin2") - self.minSpin2.setEnabled(False) - - self.horizontalLayout_2.addWidget(self.minSpin2) - - self.label_3 = QLabel(self.centralwidget) - self.label_3.setObjectName(u"label_3") - self.label_3.setAlignment(Qt.AlignCenter) - - self.horizontalLayout_2.addWidget(self.label_3) - - self.maxSpin2 = SpinBox(self.centralwidget) - self.maxSpin2.setObjectName(u"maxSpin2") - self.maxSpin2.setEnabled(False) - - self.horizontalLayout_2.addWidget(self.maxSpin2) - - - self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.minSpin1 = SpinBox(self.centralwidget) - self.minSpin1.setObjectName(u"minSpin1") - - self.horizontalLayout.addWidget(self.minSpin1) - - self.label_2 = QLabel(self.centralwidget) - self.label_2.setObjectName(u"label_2") - self.label_2.setAlignment(Qt.AlignCenter) - - self.horizontalLayout.addWidget(self.label_2) - - self.maxSpin1 = SpinBox(self.centralwidget) - self.maxSpin1.setObjectName(u"maxSpin1") - - self.horizontalLayout.addWidget(self.maxSpin1) - - - self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) - - self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") - self.minSpin3 = SpinBox(self.centralwidget) - self.minSpin3.setObjectName(u"minSpin3") - self.minSpin3.setEnabled(False) - - self.horizontalLayout_3.addWidget(self.minSpin3) - - self.label_4 = QLabel(self.centralwidget) - self.label_4.setObjectName(u"label_4") - self.label_4.setAlignment(Qt.AlignCenter) - - self.horizontalLayout_3.addWidget(self.label_4) - - self.maxSpin3 = SpinBox(self.centralwidget) - self.maxSpin3.setObjectName(u"maxSpin3") - self.maxSpin3.setEnabled(False) - - self.horizontalLayout_3.addWidget(self.maxSpin3) - - - self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) - - self.lutCheck = QCheckBox(self.centralwidget) - self.lutCheck.setObjectName(u"lutCheck") - - self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) - - self.alphaCheck = QCheckBox(self.centralwidget) - self.alphaCheck.setObjectName(u"alphaCheck") - - self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) - - self.gradient = GradientWidget(self.centralwidget) - self.gradient.setObjectName(u"gradient") - sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) - self.gradient.setSizePolicy(sizePolicy) - - self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) - - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - - self.gridLayout_2.addItem(self.horizontalSpacer, 3, 3, 1, 1) - - self.fpsLabel = QLabel(self.centralwidget) - self.fpsLabel.setObjectName(u"fpsLabel") - font = QFont() - font.setPointSize(12) - self.fpsLabel.setFont(font) - self.fpsLabel.setAlignment(Qt.AlignCenter) - - self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) - - self.rgbCheck = QCheckBox(self.centralwidget) - self.rgbCheck.setObjectName(u"rgbCheck") - - self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) - - self.label_5 = QLabel(self.centralwidget) - self.label_5.setObjectName(u"label_5") - - self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) - - self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") - self.framesSpin = QSpinBox(self.centralwidget) - self.framesSpin.setObjectName(u"framesSpin") - self.framesSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) - self.framesSpin.setValue(10) - - self.horizontalLayout_4.addWidget(self.framesSpin) - - self.widthSpin = QSpinBox(self.centralwidget) - self.widthSpin.setObjectName(u"widthSpin") - self.widthSpin.setButtonSymbols(QAbstractSpinBox.PlusMinus) - self.widthSpin.setMaximum(10000) - self.widthSpin.setValue(512) - - self.horizontalLayout_4.addWidget(self.widthSpin) - - self.heightSpin = QSpinBox(self.centralwidget) - self.heightSpin.setObjectName(u"heightSpin") - self.heightSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) - self.heightSpin.setMaximum(10000) - self.heightSpin.setValue(512) - - self.horizontalLayout_4.addWidget(self.heightSpin) - - - self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) - - self.sizeLabel = QLabel(self.centralwidget) - self.sizeLabel.setObjectName(u"sizeLabel") - - self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) - - MainWindow.setCentralWidget(self.centralwidget) - - self.retranslateUi(MainWindow) - - self.stack.setCurrentIndex(1) - - - QMetaObject.connectSlotsByName(MainWindow) - # setupUi - - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) - self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None)) - self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None)) - self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None)) - self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None)) - self.gfxRadio.setText(QCoreApplication.translate("MainWindow", u"GraphicsView + ImageItem", None)) - self.rawGLRadio.setText(QCoreApplication.translate("MainWindow", u"RawGLImageWidget", None)) - self.dtypeCombo.setItemText(0, QCoreApplication.translate("MainWindow", u"uint8", None)) - self.dtypeCombo.setItemText(1, QCoreApplication.translate("MainWindow", u"uint16", None)) - self.dtypeCombo.setItemText(2, QCoreApplication.translate("MainWindow", u"float", None)) - - self.label.setText(QCoreApplication.translate("MainWindow", u"Data type", None)) - self.rgbLevelsCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) - self.label_3.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.label_2.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.label_4.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.lutCheck.setText(QCoreApplication.translate("MainWindow", u"Use Lookup Table", None)) - self.alphaCheck.setText(QCoreApplication.translate("MainWindow", u"alpha", None)) - self.fpsLabel.setText(QCoreApplication.translate("MainWindow", u"FPS", None)) - self.rgbCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) - self.label_5.setText(QCoreApplication.translate("MainWindow", u"Image size", None)) - self.sizeLabel.setText("") - # retranslateUi - diff --git a/pyqtgraph/examples/VideoTemplate_pyside6.py b/pyqtgraph/examples/VideoTemplate_pyside6.py deleted file mode 100644 index a9d386c..0000000 --- a/pyqtgraph/examples/VideoTemplate_pyside6.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'VideoTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from pyqtgraph import GraphicsView -from pyqtgraph.widgets.RawImageWidget import RawImageWidget -from pyqtgraph import GradientWidget -from pyqtgraph import SpinBox - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - if not MainWindow.objectName(): - MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(695, 798) - self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName(u"centralwidget") - self.gridLayout_2 = QGridLayout(self.centralwidget) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.cudaCheck = QCheckBox(self.centralwidget) - self.cudaCheck.setObjectName(u"cudaCheck") - - self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) - - self.downsampleCheck = QCheckBox(self.centralwidget) - self.downsampleCheck.setObjectName(u"downsampleCheck") - - self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) - - self.scaleCheck = QCheckBox(self.centralwidget) - self.scaleCheck.setObjectName(u"scaleCheck") - - self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) - - self.gridLayout = QGridLayout() - self.gridLayout.setObjectName(u"gridLayout") - self.rawRadio = QRadioButton(self.centralwidget) - self.rawRadio.setObjectName(u"rawRadio") - - self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) - - self.gfxRadio = QRadioButton(self.centralwidget) - self.gfxRadio.setObjectName(u"gfxRadio") - self.gfxRadio.setChecked(True) - - self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) - - self.stack = QStackedWidget(self.centralwidget) - self.stack.setObjectName(u"stack") - self.page = QWidget() - self.page.setObjectName(u"page") - self.gridLayout_3 = QGridLayout(self.page) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.graphicsView = GraphicsView(self.page) - self.graphicsView.setObjectName(u"graphicsView") - - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) - - self.stack.addWidget(self.page) - self.page_2 = QWidget() - self.page_2.setObjectName(u"page_2") - self.gridLayout_4 = QGridLayout(self.page_2) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.rawImg = RawImageWidget(self.page_2) - self.rawImg.setObjectName(u"rawImg") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) - self.rawImg.setSizePolicy(sizePolicy) - - self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) - - self.stack.addWidget(self.page_2) - - self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) - - self.rawGLRadio = QRadioButton(self.centralwidget) - self.rawGLRadio.setObjectName(u"rawGLRadio") - - self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) - - - self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) - - self.dtypeCombo = QComboBox(self.centralwidget) - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.addItem("") - self.dtypeCombo.setObjectName(u"dtypeCombo") - - self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) - - self.label = QLabel(self.centralwidget) - self.label.setObjectName(u"label") - - self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) - - self.rgbLevelsCheck = QCheckBox(self.centralwidget) - self.rgbLevelsCheck.setObjectName(u"rgbLevelsCheck") - - self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) - - self.horizontalLayout_2 = QHBoxLayout() - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") - self.minSpin2 = SpinBox(self.centralwidget) - self.minSpin2.setObjectName(u"minSpin2") - self.minSpin2.setEnabled(False) - - self.horizontalLayout_2.addWidget(self.minSpin2) - - self.label_3 = QLabel(self.centralwidget) - self.label_3.setObjectName(u"label_3") - self.label_3.setAlignment(Qt.AlignCenter) - - self.horizontalLayout_2.addWidget(self.label_3) - - self.maxSpin2 = SpinBox(self.centralwidget) - self.maxSpin2.setObjectName(u"maxSpin2") - self.maxSpin2.setEnabled(False) - - self.horizontalLayout_2.addWidget(self.maxSpin2) - - - self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.minSpin1 = SpinBox(self.centralwidget) - self.minSpin1.setObjectName(u"minSpin1") - - self.horizontalLayout.addWidget(self.minSpin1) - - self.label_2 = QLabel(self.centralwidget) - self.label_2.setObjectName(u"label_2") - self.label_2.setAlignment(Qt.AlignCenter) - - self.horizontalLayout.addWidget(self.label_2) - - self.maxSpin1 = SpinBox(self.centralwidget) - self.maxSpin1.setObjectName(u"maxSpin1") - - self.horizontalLayout.addWidget(self.maxSpin1) - - - self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) - - self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") - self.minSpin3 = SpinBox(self.centralwidget) - self.minSpin3.setObjectName(u"minSpin3") - self.minSpin3.setEnabled(False) - - self.horizontalLayout_3.addWidget(self.minSpin3) - - self.label_4 = QLabel(self.centralwidget) - self.label_4.setObjectName(u"label_4") - self.label_4.setAlignment(Qt.AlignCenter) - - self.horizontalLayout_3.addWidget(self.label_4) - - self.maxSpin3 = SpinBox(self.centralwidget) - self.maxSpin3.setObjectName(u"maxSpin3") - self.maxSpin3.setEnabled(False) - - self.horizontalLayout_3.addWidget(self.maxSpin3) - - - self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) - - self.lutCheck = QCheckBox(self.centralwidget) - self.lutCheck.setObjectName(u"lutCheck") - - self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) - - self.alphaCheck = QCheckBox(self.centralwidget) - self.alphaCheck.setObjectName(u"alphaCheck") - - self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) - - self.gradient = GradientWidget(self.centralwidget) - self.gradient.setObjectName(u"gradient") - sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) - self.gradient.setSizePolicy(sizePolicy) - - self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) - - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - - self.gridLayout_2.addItem(self.horizontalSpacer, 3, 3, 1, 1) - - self.fpsLabel = QLabel(self.centralwidget) - self.fpsLabel.setObjectName(u"fpsLabel") - font = QFont() - font.setPointSize(12) - self.fpsLabel.setFont(font) - self.fpsLabel.setAlignment(Qt.AlignCenter) - - self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) - - self.rgbCheck = QCheckBox(self.centralwidget) - self.rgbCheck.setObjectName(u"rgbCheck") - - self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) - - self.label_5 = QLabel(self.centralwidget) - self.label_5.setObjectName(u"label_5") - - self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) - - self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") - self.framesSpin = QSpinBox(self.centralwidget) - self.framesSpin.setObjectName(u"framesSpin") - self.framesSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) - self.framesSpin.setValue(10) - - self.horizontalLayout_4.addWidget(self.framesSpin) - - self.widthSpin = QSpinBox(self.centralwidget) - self.widthSpin.setObjectName(u"widthSpin") - self.widthSpin.setButtonSymbols(QAbstractSpinBox.PlusMinus) - self.widthSpin.setMaximum(10000) - self.widthSpin.setValue(512) - - self.horizontalLayout_4.addWidget(self.widthSpin) - - self.heightSpin = QSpinBox(self.centralwidget) - self.heightSpin.setObjectName(u"heightSpin") - self.heightSpin.setButtonSymbols(QAbstractSpinBox.NoButtons) - self.heightSpin.setMaximum(10000) - self.heightSpin.setValue(512) - - self.horizontalLayout_4.addWidget(self.heightSpin) - - - self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) - - self.sizeLabel = QLabel(self.centralwidget) - self.sizeLabel.setObjectName(u"sizeLabel") - - self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) - - MainWindow.setCentralWidget(self.centralwidget) - - self.retranslateUi(MainWindow) - - self.stack.setCurrentIndex(1) - - - QMetaObject.connectSlotsByName(MainWindow) - # setupUi - - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) - self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None)) - self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None)) - self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None)) - self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None)) - self.gfxRadio.setText(QCoreApplication.translate("MainWindow", u"GraphicsView + ImageItem", None)) - self.rawGLRadio.setText(QCoreApplication.translate("MainWindow", u"RawGLImageWidget", None)) - self.dtypeCombo.setItemText(0, QCoreApplication.translate("MainWindow", u"uint8", None)) - self.dtypeCombo.setItemText(1, QCoreApplication.translate("MainWindow", u"uint16", None)) - self.dtypeCombo.setItemText(2, QCoreApplication.translate("MainWindow", u"float", None)) - - self.label.setText(QCoreApplication.translate("MainWindow", u"Data type", None)) - self.rgbLevelsCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) - self.label_3.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.label_2.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.label_4.setText(QCoreApplication.translate("MainWindow", u"<--->", None)) - self.lutCheck.setText(QCoreApplication.translate("MainWindow", u"Use Lookup Table", None)) - self.alphaCheck.setText(QCoreApplication.translate("MainWindow", u"alpha", None)) - self.fpsLabel.setText(QCoreApplication.translate("MainWindow", u"FPS", None)) - self.rgbCheck.setText(QCoreApplication.translate("MainWindow", u"RGB", None)) - self.label_5.setText(QCoreApplication.translate("MainWindow", u"Image size", None)) - self.sizeLabel.setText("") - # retranslateUi - diff --git a/pyqtgraph/examples/ViewBox.py b/pyqtgraph/examples/ViewBox.py deleted file mode 100644 index f9dbac4..0000000 --- a/pyqtgraph/examples/ViewBox.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -ViewBox is the general-purpose graphical container that allows the user to -zoom / pan to inspect any area of a 2D coordinate system. - -This unimaginative example demonstrates the constrution of a ViewBox-based -plot area with axes, very similar to the way PlotItem is built. -""" - - -## Add path to library (just for examples; you do not need this) -import initExample - -## This example uses a ViewBox to create a PlotWidget-like interface - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - -app = pg.mkQApp("ViewBox Example") -mw = QtGui.QMainWindow() -mw.setWindowTitle('pyqtgraph example: ViewBox') -mw.show() -mw.resize(800, 600) - -gv = pg.GraphicsView() -mw.setCentralWidget(gv) -l = QtGui.QGraphicsGridLayout() -l.setHorizontalSpacing(0) -l.setVerticalSpacing(0) - -vb = pg.ViewBox() - -p1 = pg.PlotDataItem() -vb.addItem(p1) - -## Just something to play with inside the ViewBox -class movableRect(QtGui.QGraphicsRectItem): - def __init__(self, *args): - QtGui.QGraphicsRectItem.__init__(self, *args) - self.setAcceptHoverEvents(True) - def hoverEnterEvent(self, ev): - self.savedPen = self.pen() - self.setPen(pg.mkPen(255, 255, 255)) - ev.ignore() - def hoverLeaveEvent(self, ev): - self.setPen(self.savedPen) - ev.ignore() - def mousePressEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.pressDelta = self.mapToParent(ev.pos()) - self.pos() - else: - ev.ignore() - def mouseMoveEvent(self, ev): - self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) - -rect = movableRect(QtCore.QRectF(0, 0, 1, 1)) -rect.setPen(pg.mkPen(100, 200, 100)) -vb.addItem(rect) - -l.addItem(vb, 0, 1) -gv.centralWidget.setLayout(l) - - -xScale = pg.AxisItem(orientation='bottom', linkView=vb) -l.addItem(xScale, 1, 1) -yScale = pg.AxisItem(orientation='left', linkView=vb) -l.addItem(yScale, 0, 0) - -xScale.setLabel(text="X Axis", units="s") -yScale.setLabel('Y Axis', units='V') - -def rand(n): - data = np.random.random(n) - data[int(n*0.1):int(n*0.13)] += .5 - data[int(n*0.18)] += 2 - data[int(n*0.1):int(n*0.13)] *= 5 - data[int(n*0.18)] *= 20 - return data, np.arange(n, n+len(data)) / float(n) - - -def updateData(): - yd, xd = rand(10000) - p1.setData(y=yd, x=xd) - -yd, xd = rand(10000) -updateData() -vb.autoRange() - -t = QtCore.QTimer() -t.timeout.connect(updateData) -t.start(50) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ViewBoxFeatures.py b/pyqtgraph/examples/ViewBoxFeatures.py deleted file mode 100644 index 5757924..0000000 --- a/pyqtgraph/examples/ViewBoxFeatures.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ViewBox is the general-purpose graphical container that allows the user to -zoom / pan to inspect any area of a 2D coordinate system. - -This example demonstrates many of the features ViewBox provides. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -x = np.arange(1000, dtype=float) -y = np.random.normal(size=1000) -y += 5 * np.sin(x/100) - -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: ____') -win.resize(1000, 800) -win.ci.setBorder((50, 50, 100)) - -sub1 = win.addLayout() -sub1.addLabel("Standard mouse interaction:
left-drag to pan, right-drag to zoom.") -sub1.nextRow() -v1 = sub1.addViewBox() -l1 = pg.PlotDataItem(y) -v1.addItem(l1) - - -sub2 = win.addLayout() -sub2.addLabel("One-button mouse interaction:
left-drag zoom to box, wheel to zoom out.") -sub2.nextRow() -v2 = sub2.addViewBox() -v2.setMouseMode(v2.RectMode) -l2 = pg.PlotDataItem(y) -v2.addItem(l2) - -win.nextRow() - -sub3 = win.addLayout() -sub3.addLabel("Locked aspect ratio when zooming.") -sub3.nextRow() -v3 = sub3.addViewBox() -v3.setAspectLocked(1.0) -l3 = pg.PlotDataItem(y) -v3.addItem(l3) - -sub4 = win.addLayout() -sub4.addLabel("View limits:
prevent panning or zooming past limits.") -sub4.nextRow() -v4 = sub4.addViewBox() -v4.setLimits(xMin=-100, xMax=1100, - minXRange=20, maxXRange=500, - yMin=-10, yMax=10, - minYRange=1, maxYRange=10) -l4 = pg.PlotDataItem(y) -v4.addItem(l4) - -win.nextRow() - -sub5 = win.addLayout() -sub5.addLabel("Linked axes: Data in this plot is always X-aligned to
the plot above.") -sub5.nextRow() -v5 = sub5.addViewBox() -v5.setXLink(v3) -l5 = pg.PlotDataItem(y) -v5.addItem(l5) - -sub6 = win.addLayout() -sub6.addLabel("Disable mouse: Per-axis control over mouse input.
" - "Auto-scale-visible: Automatically fit *visible* data within view
" - "(try panning left-right).") -sub6.nextRow() -v6 = sub6.addViewBox() -v6.setMouseEnabled(x=True, y=False) -v6.enableAutoRange(x=False, y=True) -v6.setXRange(300, 450) -v6.setAutoVisible(x=False, y=True) -l6 = pg.PlotDataItem(y) -v6.addItem(l6) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/ViewLimits.py b/pyqtgraph/examples/ViewLimits.py deleted file mode 100644 index c8f0dd2..0000000 --- a/pyqtgraph/examples/ViewLimits.py +++ /dev/null @@ -1,15 +0,0 @@ -import initExample ## Add path to library (just for examples; you do not need this) - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -import numpy as np - -plt = pg.plot(np.random.normal(size=100), title="View limit example") -plt.centralWidget.vb.setLimits(xMin=-20, xMax=120, minXRange=5, maxXRange=100) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if sys.flags.interactive != 1 or not hasattr(QtCore, 'PYQT_VERSION'): - pg.QtGui.QApplication.exec_() diff --git a/pyqtgraph/examples/__init__.py b/pyqtgraph/examples/__init__.py deleted file mode 100644 index 82d9715..0000000 --- a/pyqtgraph/examples/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ExampleApp import main as run diff --git a/pyqtgraph/examples/__main__.py b/pyqtgraph/examples/__main__.py deleted file mode 100644 index 41b989d..0000000 --- a/pyqtgraph/examples/__main__.py +++ /dev/null @@ -1,16 +0,0 @@ -import sys, os - -# Set up path to contain pyqtgraph module when run without installation -if __name__ == "__main__" and (__package__ is None or __package__==''): - parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - sys.path.insert(0, parent_dir) - import examples - __package__ = "examples" - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - - -if __name__ == '__main__': - from .ExampleApp import main as run - run() diff --git a/pyqtgraph/examples/__pycache__/Arrow.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Arrow.cpython-36.pyc deleted file mode 100644 index 0685b7b..0000000 Binary files a/pyqtgraph/examples/__pycache__/Arrow.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/BarGraphItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/BarGraphItem.cpython-36.pyc deleted file mode 100644 index c08020b..0000000 Binary files a/pyqtgraph/examples/__pycache__/BarGraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/CLIexample.cpython-36.pyc b/pyqtgraph/examples/__pycache__/CLIexample.cpython-36.pyc deleted file mode 100644 index 12e6dbf..0000000 Binary files a/pyqtgraph/examples/__pycache__/CLIexample.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ColorButton.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ColorButton.cpython-36.pyc deleted file mode 100644 index 9b2f20d..0000000 Binary files a/pyqtgraph/examples/__pycache__/ColorButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ConsoleWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ConsoleWidget.cpython-36.pyc deleted file mode 100644 index 4f4f054..0000000 Binary files a/pyqtgraph/examples/__pycache__/ConsoleWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/CustomGraphItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/CustomGraphItem.cpython-36.pyc deleted file mode 100644 index d43ffbc..0000000 Binary files a/pyqtgraph/examples/__pycache__/CustomGraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/DataSlicing.cpython-36.pyc b/pyqtgraph/examples/__pycache__/DataSlicing.cpython-36.pyc deleted file mode 100644 index 2ca6a3f..0000000 Binary files a/pyqtgraph/examples/__pycache__/DataSlicing.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/DataTreeWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/DataTreeWidget.cpython-36.pyc deleted file mode 100644 index 29621a0..0000000 Binary files a/pyqtgraph/examples/__pycache__/DataTreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/DateAxisItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/DateAxisItem.cpython-36.pyc deleted file mode 100644 index 4adb13c..0000000 Binary files a/pyqtgraph/examples/__pycache__/DateAxisItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/DateAxisItem_QtDesigner.cpython-36.pyc b/pyqtgraph/examples/__pycache__/DateAxisItem_QtDesigner.cpython-36.pyc deleted file mode 100644 index bb7ba8d..0000000 Binary files a/pyqtgraph/examples/__pycache__/DateAxisItem_QtDesigner.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/DiffTreeWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/DiffTreeWidget.cpython-36.pyc deleted file mode 100644 index 4c3c5b8..0000000 Binary files a/pyqtgraph/examples/__pycache__/DiffTreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/Draw.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Draw.cpython-36.pyc deleted file mode 100644 index 60517f7..0000000 Binary files a/pyqtgraph/examples/__pycache__/Draw.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ErrorBarItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ErrorBarItem.cpython-36.pyc deleted file mode 100644 index 8845fdb..0000000 Binary files a/pyqtgraph/examples/__pycache__/ErrorBarItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ExampleApp.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ExampleApp.cpython-36.pyc deleted file mode 100644 index 0a866da..0000000 Binary files a/pyqtgraph/examples/__pycache__/ExampleApp.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/FillBetweenItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/FillBetweenItem.cpython-36.pyc deleted file mode 100644 index aec5618..0000000 Binary files a/pyqtgraph/examples/__pycache__/FillBetweenItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/Flowchart.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Flowchart.cpython-36.pyc deleted file mode 100644 index 38cd910..0000000 Binary files a/pyqtgraph/examples/__pycache__/Flowchart.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/FlowchartCustomNode.cpython-36.pyc b/pyqtgraph/examples/__pycache__/FlowchartCustomNode.cpython-36.pyc deleted file mode 100644 index 7805b82..0000000 Binary files a/pyqtgraph/examples/__pycache__/FlowchartCustomNode.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLBarGraphItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLBarGraphItem.cpython-36.pyc deleted file mode 100644 index c716707..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLBarGraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLImageItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLImageItem.cpython-36.pyc deleted file mode 100644 index 2fb327a..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLImageItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLIsosurface.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLIsosurface.cpython-36.pyc deleted file mode 100644 index bbdba9e..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLIsosurface.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLLinePlotItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLLinePlotItem.cpython-36.pyc deleted file mode 100644 index eca07d1..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLLinePlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLMeshItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLMeshItem.cpython-36.pyc deleted file mode 100644 index 6f12ab8..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLMeshItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLScatterPlotItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLScatterPlotItem.cpython-36.pyc deleted file mode 100644 index 59f2116..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLScatterPlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLSurfacePlot.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLSurfacePlot.cpython-36.pyc deleted file mode 100644 index d4e9ccb..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLSurfacePlot.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLViewWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLViewWidget.cpython-36.pyc deleted file mode 100644 index d76e787..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLViewWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLVolumeItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLVolumeItem.cpython-36.pyc deleted file mode 100644 index ec40247..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLVolumeItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GLshaders.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GLshaders.cpython-36.pyc deleted file mode 100644 index f1b7911..0000000 Binary files a/pyqtgraph/examples/__pycache__/GLshaders.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GradientEditor.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GradientEditor.cpython-36.pyc deleted file mode 100644 index 22c4853..0000000 Binary files a/pyqtgraph/examples/__pycache__/GradientEditor.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GradientWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GradientWidget.cpython-36.pyc deleted file mode 100644 index f8cef6b..0000000 Binary files a/pyqtgraph/examples/__pycache__/GradientWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GraphItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GraphItem.cpython-36.pyc deleted file mode 100644 index beb7d45..0000000 Binary files a/pyqtgraph/examples/__pycache__/GraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GraphicsLayout.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GraphicsLayout.cpython-36.pyc deleted file mode 100644 index df1a552..0000000 Binary files a/pyqtgraph/examples/__pycache__/GraphicsLayout.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/GraphicsScene.cpython-36.pyc b/pyqtgraph/examples/__pycache__/GraphicsScene.cpython-36.pyc deleted file mode 100644 index 183263d..0000000 Binary files a/pyqtgraph/examples/__pycache__/GraphicsScene.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/HistogramLUT.cpython-36.pyc b/pyqtgraph/examples/__pycache__/HistogramLUT.cpython-36.pyc deleted file mode 100644 index df1de45..0000000 Binary files a/pyqtgraph/examples/__pycache__/HistogramLUT.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ImageItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ImageItem.cpython-36.pyc deleted file mode 100644 index dddadbd..0000000 Binary files a/pyqtgraph/examples/__pycache__/ImageItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ImageView.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ImageView.cpython-36.pyc deleted file mode 100644 index 3a769ca..0000000 Binary files a/pyqtgraph/examples/__pycache__/ImageView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/InfiniteLine.cpython-36.pyc b/pyqtgraph/examples/__pycache__/InfiniteLine.cpython-36.pyc deleted file mode 100644 index 46b1d10..0000000 Binary files a/pyqtgraph/examples/__pycache__/InfiniteLine.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/JoystickButton.cpython-36.pyc b/pyqtgraph/examples/__pycache__/JoystickButton.cpython-36.pyc deleted file mode 100644 index 3410d4b..0000000 Binary files a/pyqtgraph/examples/__pycache__/JoystickButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/Legend.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Legend.cpython-36.pyc deleted file mode 100644 index 7e2c98c..0000000 Binary files a/pyqtgraph/examples/__pycache__/Legend.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/LogPlotTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/LogPlotTest.cpython-36.pyc deleted file mode 100644 index 32c95ca..0000000 Binary files a/pyqtgraph/examples/__pycache__/LogPlotTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/MouseSelection.cpython-36.pyc b/pyqtgraph/examples/__pycache__/MouseSelection.cpython-36.pyc deleted file mode 100644 index 0ebde77..0000000 Binary files a/pyqtgraph/examples/__pycache__/MouseSelection.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/MultiPlotSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/MultiPlotSpeedTest.cpython-36.pyc deleted file mode 100644 index 0e5a163..0000000 Binary files a/pyqtgraph/examples/__pycache__/MultiPlotSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/MultiPlotWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/MultiPlotWidget.cpython-36.pyc deleted file mode 100644 index fe520a1..0000000 Binary files a/pyqtgraph/examples/__pycache__/MultiPlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/MultiplePlotAxes.cpython-36.pyc b/pyqtgraph/examples/__pycache__/MultiplePlotAxes.cpython-36.pyc deleted file mode 100644 index 3a357a7..0000000 Binary files a/pyqtgraph/examples/__pycache__/MultiplePlotAxes.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/NonUniformImage.cpython-36.pyc b/pyqtgraph/examples/__pycache__/NonUniformImage.cpython-36.pyc deleted file mode 100644 index 3a401b8..0000000 Binary files a/pyqtgraph/examples/__pycache__/NonUniformImage.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/PColorMeshItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/PColorMeshItem.cpython-36.pyc deleted file mode 100644 index ba40418..0000000 Binary files a/pyqtgraph/examples/__pycache__/PColorMeshItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/PanningPlot.cpython-36.pyc b/pyqtgraph/examples/__pycache__/PanningPlot.cpython-36.pyc deleted file mode 100644 index 8e07c15..0000000 Binary files a/pyqtgraph/examples/__pycache__/PanningPlot.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/PlotAutoRange.cpython-36.pyc b/pyqtgraph/examples/__pycache__/PlotAutoRange.cpython-36.pyc deleted file mode 100644 index 90cdcba..0000000 Binary files a/pyqtgraph/examples/__pycache__/PlotAutoRange.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/PlotSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/PlotSpeedTest.cpython-36.pyc deleted file mode 100644 index 20c148b..0000000 Binary files a/pyqtgraph/examples/__pycache__/PlotSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/PlotWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/PlotWidget.cpython-36.pyc deleted file mode 100644 index c60b3c9..0000000 Binary files a/pyqtgraph/examples/__pycache__/PlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/Plotting.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Plotting.cpython-36.pyc deleted file mode 100644 index adbb41d..0000000 Binary files a/pyqtgraph/examples/__pycache__/Plotting.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ProgressDialog.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ProgressDialog.cpython-36.pyc deleted file mode 100644 index 7feff22..0000000 Binary files a/pyqtgraph/examples/__pycache__/ProgressDialog.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ROIExamples.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ROIExamples.cpython-36.pyc deleted file mode 100644 index 24f5c9b..0000000 Binary files a/pyqtgraph/examples/__pycache__/ROIExamples.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ROItypes.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ROItypes.cpython-36.pyc deleted file mode 100644 index e82c2e3..0000000 Binary files a/pyqtgraph/examples/__pycache__/ROItypes.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/RemoteGraphicsView.cpython-36.pyc b/pyqtgraph/examples/__pycache__/RemoteGraphicsView.cpython-36.pyc deleted file mode 100644 index 8969bdc..0000000 Binary files a/pyqtgraph/examples/__pycache__/RemoteGraphicsView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/RemoteSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/RemoteSpeedTest.cpython-36.pyc deleted file mode 100644 index 22e843c..0000000 Binary files a/pyqtgraph/examples/__pycache__/RemoteSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ScaleBar.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ScaleBar.cpython-36.pyc deleted file mode 100644 index a8fe229..0000000 Binary files a/pyqtgraph/examples/__pycache__/ScaleBar.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ScatterPlot.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ScatterPlot.cpython-36.pyc deleted file mode 100644 index f14233d..0000000 Binary files a/pyqtgraph/examples/__pycache__/ScatterPlot.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ScatterPlotSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ScatterPlotSpeedTest.cpython-36.pyc deleted file mode 100644 index 59dea18..0000000 Binary files a/pyqtgraph/examples/__pycache__/ScatterPlotSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ScatterPlotWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ScatterPlotWidget.cpython-36.pyc deleted file mode 100644 index a4cafc3..0000000 Binary files a/pyqtgraph/examples/__pycache__/ScatterPlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/SimplePlot.cpython-36.pyc b/pyqtgraph/examples/__pycache__/SimplePlot.cpython-36.pyc deleted file mode 100644 index 71da60c..0000000 Binary files a/pyqtgraph/examples/__pycache__/SimplePlot.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/SpinBox.cpython-36.pyc b/pyqtgraph/examples/__pycache__/SpinBox.cpython-36.pyc deleted file mode 100644 index 22903b5..0000000 Binary files a/pyqtgraph/examples/__pycache__/SpinBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/Symbols.cpython-36.pyc b/pyqtgraph/examples/__pycache__/Symbols.cpython-36.pyc deleted file mode 100644 index 176529a..0000000 Binary files a/pyqtgraph/examples/__pycache__/Symbols.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/TableWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/TableWidget.cpython-36.pyc deleted file mode 100644 index c820e3f..0000000 Binary files a/pyqtgraph/examples/__pycache__/TableWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/TreeWidget.cpython-36.pyc b/pyqtgraph/examples/__pycache__/TreeWidget.cpython-36.pyc deleted file mode 100644 index 97c322b..0000000 Binary files a/pyqtgraph/examples/__pycache__/TreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/VideoSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/VideoSpeedTest.cpython-36.pyc deleted file mode 100644 index c21c3db..0000000 Binary files a/pyqtgraph/examples/__pycache__/VideoSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index edaaf43..0000000 Binary files a/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index b0dc65e..0000000 Binary files a/pyqtgraph/examples/__pycache__/VideoTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/VideoTemplate_pyside2.cpython-36.pyc b/pyqtgraph/examples/__pycache__/VideoTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index 8b03842..0000000 Binary files a/pyqtgraph/examples/__pycache__/VideoTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/VideoTemplate_pyside6.cpython-36.pyc b/pyqtgraph/examples/__pycache__/VideoTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index 64daf14..0000000 Binary files a/pyqtgraph/examples/__pycache__/VideoTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ViewBox.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ViewBox.cpython-36.pyc deleted file mode 100644 index bd0acf6..0000000 Binary files a/pyqtgraph/examples/__pycache__/ViewBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ViewBoxFeatures.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ViewBoxFeatures.cpython-36.pyc deleted file mode 100644 index e969ba6..0000000 Binary files a/pyqtgraph/examples/__pycache__/ViewBoxFeatures.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/ViewLimits.cpython-36.pyc b/pyqtgraph/examples/__pycache__/ViewLimits.cpython-36.pyc deleted file mode 100644 index ac36129..0000000 Binary files a/pyqtgraph/examples/__pycache__/ViewLimits.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/examples/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 4a779ba..0000000 Binary files a/pyqtgraph/examples/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/__main__.cpython-36.pyc b/pyqtgraph/examples/__pycache__/__main__.cpython-36.pyc deleted file mode 100644 index b7dbf01..0000000 Binary files a/pyqtgraph/examples/__pycache__/__main__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/beeswarm.cpython-36.pyc b/pyqtgraph/examples/__pycache__/beeswarm.cpython-36.pyc deleted file mode 100644 index ce7b3e7..0000000 Binary files a/pyqtgraph/examples/__pycache__/beeswarm.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/colorMaps.cpython-36.pyc b/pyqtgraph/examples/__pycache__/colorMaps.cpython-36.pyc deleted file mode 100644 index 27ca5e8..0000000 Binary files a/pyqtgraph/examples/__pycache__/colorMaps.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/contextMenu.cpython-36.pyc b/pyqtgraph/examples/__pycache__/contextMenu.cpython-36.pyc deleted file mode 100644 index 7dcb910..0000000 Binary files a/pyqtgraph/examples/__pycache__/contextMenu.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/crosshair.cpython-36.pyc b/pyqtgraph/examples/__pycache__/crosshair.cpython-36.pyc deleted file mode 100644 index 248771f..0000000 Binary files a/pyqtgraph/examples/__pycache__/crosshair.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/customGraphicsItem.cpython-36.pyc b/pyqtgraph/examples/__pycache__/customGraphicsItem.cpython-36.pyc deleted file mode 100644 index d741773..0000000 Binary files a/pyqtgraph/examples/__pycache__/customGraphicsItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/customPlot.cpython-36.pyc b/pyqtgraph/examples/__pycache__/customPlot.cpython-36.pyc deleted file mode 100644 index 119de7e..0000000 Binary files a/pyqtgraph/examples/__pycache__/customPlot.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/designerExample.cpython-36.pyc b/pyqtgraph/examples/__pycache__/designerExample.cpython-36.pyc deleted file mode 100644 index 64b1ae0..0000000 Binary files a/pyqtgraph/examples/__pycache__/designerExample.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/dockarea.cpython-36.pyc b/pyqtgraph/examples/__pycache__/dockarea.cpython-36.pyc deleted file mode 100644 index 6ad6736..0000000 Binary files a/pyqtgraph/examples/__pycache__/dockarea.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index 58b66d5..0000000 Binary files a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index dceab17..0000000 Binary files a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside2.cpython-36.pyc b/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index e354b4f..0000000 Binary files a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside6.cpython-36.pyc b/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index 700d7ec..0000000 Binary files a/pyqtgraph/examples/__pycache__/exampleLoaderTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/fractal.cpython-36.pyc b/pyqtgraph/examples/__pycache__/fractal.cpython-36.pyc deleted file mode 100644 index d183af8..0000000 Binary files a/pyqtgraph/examples/__pycache__/fractal.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/hdf5.cpython-36.pyc b/pyqtgraph/examples/__pycache__/hdf5.cpython-36.pyc deleted file mode 100644 index b4e16e1..0000000 Binary files a/pyqtgraph/examples/__pycache__/hdf5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/histogram.cpython-36.pyc b/pyqtgraph/examples/__pycache__/histogram.cpython-36.pyc deleted file mode 100644 index 2c3f54e..0000000 Binary files a/pyqtgraph/examples/__pycache__/histogram.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/imageAnalysis.cpython-36.pyc b/pyqtgraph/examples/__pycache__/imageAnalysis.cpython-36.pyc deleted file mode 100644 index b2016bf..0000000 Binary files a/pyqtgraph/examples/__pycache__/imageAnalysis.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/infiniteline_performance.cpython-36.pyc b/pyqtgraph/examples/__pycache__/infiniteline_performance.cpython-36.pyc deleted file mode 100644 index b247035..0000000 Binary files a/pyqtgraph/examples/__pycache__/infiniteline_performance.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/initExample.cpython-36.pyc b/pyqtgraph/examples/__pycache__/initExample.cpython-36.pyc deleted file mode 100644 index e0b1057..0000000 Binary files a/pyqtgraph/examples/__pycache__/initExample.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/isocurve.cpython-36.pyc b/pyqtgraph/examples/__pycache__/isocurve.cpython-36.pyc deleted file mode 100644 index 0ca1edb..0000000 Binary files a/pyqtgraph/examples/__pycache__/isocurve.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/linkedViews.cpython-36.pyc b/pyqtgraph/examples/__pycache__/linkedViews.cpython-36.pyc deleted file mode 100644 index 5c08eb0..0000000 Binary files a/pyqtgraph/examples/__pycache__/linkedViews.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/logAxis.cpython-36.pyc b/pyqtgraph/examples/__pycache__/logAxis.cpython-36.pyc deleted file mode 100644 index 2fa15df..0000000 Binary files a/pyqtgraph/examples/__pycache__/logAxis.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/multiplePlotSpeedTest.cpython-36.pyc b/pyqtgraph/examples/__pycache__/multiplePlotSpeedTest.cpython-36.pyc deleted file mode 100644 index 03ae080..0000000 Binary files a/pyqtgraph/examples/__pycache__/multiplePlotSpeedTest.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/multiprocess.cpython-36.pyc b/pyqtgraph/examples/__pycache__/multiprocess.cpython-36.pyc deleted file mode 100644 index 0f65e66..0000000 Binary files a/pyqtgraph/examples/__pycache__/multiprocess.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/optics_demos.cpython-36.pyc b/pyqtgraph/examples/__pycache__/optics_demos.cpython-36.pyc deleted file mode 100644 index 206f51d..0000000 Binary files a/pyqtgraph/examples/__pycache__/optics_demos.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/parallelize.cpython-36.pyc b/pyqtgraph/examples/__pycache__/parallelize.cpython-36.pyc deleted file mode 100644 index c355ae3..0000000 Binary files a/pyqtgraph/examples/__pycache__/parallelize.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/parametertree.cpython-36.pyc b/pyqtgraph/examples/__pycache__/parametertree.cpython-36.pyc deleted file mode 100644 index 85f9ee3..0000000 Binary files a/pyqtgraph/examples/__pycache__/parametertree.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/relativity_demo.cpython-36.pyc b/pyqtgraph/examples/__pycache__/relativity_demo.cpython-36.pyc deleted file mode 100644 index 7ee2627..0000000 Binary files a/pyqtgraph/examples/__pycache__/relativity_demo.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/scrollingPlots.cpython-36.pyc b/pyqtgraph/examples/__pycache__/scrollingPlots.cpython-36.pyc deleted file mode 100644 index 39f2134..0000000 Binary files a/pyqtgraph/examples/__pycache__/scrollingPlots.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/syntax.cpython-36.pyc b/pyqtgraph/examples/__pycache__/syntax.cpython-36.pyc deleted file mode 100644 index 8beda4d..0000000 Binary files a/pyqtgraph/examples/__pycache__/syntax.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/template.cpython-36.pyc b/pyqtgraph/examples/__pycache__/template.cpython-36.pyc deleted file mode 100644 index 6e7ae61..0000000 Binary files a/pyqtgraph/examples/__pycache__/template.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/test_ExampleApp.cpython-36.pyc b/pyqtgraph/examples/__pycache__/test_ExampleApp.cpython-36.pyc deleted file mode 100644 index 5f63998..0000000 Binary files a/pyqtgraph/examples/__pycache__/test_ExampleApp.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/test_examples.cpython-36.pyc b/pyqtgraph/examples/__pycache__/test_examples.cpython-36.pyc deleted file mode 100644 index 9c1d6b8..0000000 Binary files a/pyqtgraph/examples/__pycache__/test_examples.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/text.cpython-36.pyc b/pyqtgraph/examples/__pycache__/text.cpython-36.pyc deleted file mode 100644 index 2f646f9..0000000 Binary files a/pyqtgraph/examples/__pycache__/text.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/utils.cpython-36.pyc b/pyqtgraph/examples/__pycache__/utils.cpython-36.pyc deleted file mode 100644 index 4ee9076..0000000 Binary files a/pyqtgraph/examples/__pycache__/utils.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/__pycache__/verlet_chain_demo.cpython-36.pyc b/pyqtgraph/examples/__pycache__/verlet_chain_demo.cpython-36.pyc deleted file mode 100644 index 507a7bd..0000000 Binary files a/pyqtgraph/examples/__pycache__/verlet_chain_demo.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/beeswarm.py b/pyqtgraph/examples/beeswarm.py deleted file mode 100644 index 48ee423..0000000 --- a/pyqtgraph/examples/beeswarm.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Example beeswarm / bar chart -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.plot() -win.setWindowTitle('pyqtgraph example: beeswarm') - -data = np.random.normal(size=(4,20)) -data[0] += 5 -data[1] += 7 -data[2] += 5 -data[3] = 10 + data[3] * 2 - -## Make bar graph -#bar = pg.BarGraphItem(x=range(4), height=data.mean(axis=1), width=0.5, brush=0.4) -#win.addItem(bar) - -## add scatter plots on top -for i in range(4): - xvals = pg.pseudoScatter(data[i], spacing=0.4, bidir=True) * 0.2 - win.plot(x=xvals+i, y=data[i], pen=None, symbol='o', symbolBrush=pg.intColor(i,6,maxValue=128)) - -## Make error bars -err = pg.ErrorBarItem(x=np.arange(4), y=data.mean(axis=1), height=data.std(axis=1), beam=0.5, pen={'color':'w', 'width':2}) -win.addItem(err) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/colorMaps.py b/pyqtgraph/examples/colorMaps.py deleted file mode 100644 index fb648a6..0000000 --- a/pyqtgraph/examples/colorMaps.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of ImageView, which is a high-level widget for -displaying and analyzing 2D and 3D data. ImageView provides: - - 1. A zoomable region (ViewBox) for displaying the image - 2. A combination histogram and gradient editor (HistogramLUTItem) for - controlling the visual appearance of the image - 3. A timeline for selecting the currently displayed frame (for 3D data only). - 4. Tools for very basic analysis of image data (see ROI and Norm buttons) - -""" -## Add path to library (just for examples; you do not need this) -import initExample - -import numpy as np -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg - -app = pg.mkQApp() - -## Create window with ImageView widget -win = QtGui.QMainWindow() -win.resize(1000,800) - -lw = pg.GraphicsLayoutWidget() -lw.setFixedWidth(1000) -lw.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - -scr = QtGui.QScrollArea() -scr.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) -scr.setWidget(lw) -win.setCentralWidget(scr) -win.show() -win.setWindowTitle('pyqtgraph example: Color maps') - -## Create color map test image -width = 3*256 -height = 32 -img = np.zeros( (width, height) ) -gradient = np.linspace(0.05, 0.95, width) -modulation = np.zeros(width) -for idx in range(width): - modulation[idx] = -0.05 * np.sin( 200 * np.pi * idx/width ) -for idx in range(height): - img[:,idx] = gradient + (idx/(height-1)) * modulation - -num_bars = 0 - -lw.addLabel('=== local color maps ===') -num_bars += 1 -lw.nextRow() -list_of_maps = pg.colormap.listMaps() -for map_name in list_of_maps: - num_bars += 1 - lw.addLabel(map_name) - cmap = pg.colormap.get(map_name) - imi = pg.ImageItem() - imi.setImage(img) - imi.setLookupTable( cmap.getLookupTable(alpha=True) ) - vb = lw.addViewBox(lockAspect=True, enableMouse=False) - vb.addItem(imi) - lw.nextRow() - -lw.addLabel('=== Matplotlib import ===') -num_bars += 1 -lw.nextRow() -list_of_maps = pg.colormap.listMaps('matplotlib') -for map_name in list_of_maps: - num_bars += 1 - lw.addLabel(map_name) - cmap = pg.colormap.get(map_name, source='matplotlib', skipCache=True) - if cmap is not None: - imi = pg.ImageItem() - imi.setImage(img) - imi.setLookupTable( cmap.getLookupTable(alpha=True) ) - vb = lw.addViewBox(lockAspect=True, enableMouse=False) - vb.addItem(imi) - lw.nextRow() - -lw.addLabel('=== ColorCET import ===') -num_bars += 1 -lw.nextRow() -list_of_maps = pg.colormap.listMaps('colorcet') -for map_name in list_of_maps: - num_bars += 1 - lw.addLabel(map_name) - cmap = pg.colormap.get(map_name, source='colorcet', skipCache=True) - if cmap is not None: - imi = pg.ImageItem() - imi.setImage(img) - imi.setLookupTable( cmap.getLookupTable(alpha=True) ) - vb = lw.addViewBox(lockAspect=True, enableMouse=False) - vb.addItem(imi) - lw.nextRow() - -lw.setFixedHeight(num_bars * (height+5) ) - -## Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/contextMenu.py b/pyqtgraph/examples/contextMenu.py deleted file mode 100644 index 904b903..0000000 --- a/pyqtgraph/examples/contextMenu.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates adding a custom context menu to a GraphicsItem -and extending the context menu of a ViewBox. - -PyQtGraph implements a system that allows each item in a scene to implement its -own context menu, and for the menus of its parent items to be automatically -displayed as well. - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: context menu') - - -view = win.addViewBox() - -# add two new actions to the ViewBox context menu: -zoom1 = view.menu.addAction('Zoom to box 1') -zoom2 = view.menu.addAction('Zoom to box 2') - -# define callbacks for these actions -def zoomTo1(): - # note that box1 is defined below - view.autoRange(items=[box1]) -zoom1.triggered.connect(zoomTo1) - -def zoomTo2(): - # note that box1 is defined below - view.autoRange(items=[box2]) -zoom2.triggered.connect(zoomTo2) - - - -class MenuBox(pg.GraphicsObject): - """ - This class draws a rectangular area. Right-clicking inside the area will - raise a custom context menu which also includes the context menus of - its parents. - """ - def __init__(self, name): - self.name = name - self.pen = pg.mkPen('r') - - # menu creation is deferred because it is expensive and often - # the user will never see the menu anyway. - self.menu = None - - # note that the use of super() is often avoided because Qt does not - # allow to inherit from multiple QObject subclasses. - pg.GraphicsObject.__init__(self) - - - # All graphics items must have paint() and boundingRect() defined. - def boundingRect(self): - return QtCore.QRectF(0, 0, 10, 10) - - def paint(self, p, *args): - p.setPen(self.pen) - p.drawRect(self.boundingRect()) - - - # On right-click, raise the context menu - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: - if self.raiseContextMenu(ev): - ev.accept() - - def raiseContextMenu(self, ev): - menu = self.getContextMenus() - - # Let the scene add on to the end of our context menu - # (this is optional) - menu = self.scene().addParentContextMenus(self, menu, ev) - - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - return True - - # This method will be called when this item's _children_ want to raise - # a context menu that includes their parents' menus. - def getContextMenus(self, event=None): - if self.menu is None: - self.menu = QtGui.QMenu() - self.menu.setTitle(self.name+ " options..") - - green = QtGui.QAction("Turn green", self.menu) - green.triggered.connect(self.setGreen) - self.menu.addAction(green) - self.menu.green = green - - blue = QtGui.QAction("Turn blue", self.menu) - blue.triggered.connect(self.setBlue) - self.menu.addAction(blue) - self.menu.green = blue - - alpha = QtGui.QWidgetAction(self.menu) - alphaSlider = QtGui.QSlider() - alphaSlider.setOrientation(QtCore.Qt.Horizontal) - alphaSlider.setMaximum(255) - alphaSlider.setValue(255) - alphaSlider.valueChanged.connect(self.setAlpha) - alpha.setDefaultWidget(alphaSlider) - self.menu.addAction(alpha) - self.menu.alpha = alpha - self.menu.alphaSlider = alphaSlider - return self.menu - - # Define context menu callbacks - def setGreen(self): - self.pen = pg.mkPen('g') - # inform Qt that this item must be redrawn. - self.update() - - def setBlue(self): - self.pen = pg.mkPen('b') - self.update() - - def setAlpha(self, a): - self.setOpacity(a/255.) - - -# This box's context menu will include the ViewBox's menu -box1 = MenuBox("Menu Box #1") -view.addItem(box1) - -# This box's context menu will include both the ViewBox's menu and box1's menu -box2 = MenuBox("Menu Box #2") -box2.setParentItem(box1) -box2.setPos(5, 5) -box2.setScale(0.2) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/crosshair.py b/pyqtgraph/examples/crosshair.py deleted file mode 100644 index 150ed53..0000000 --- a/pyqtgraph/examples/crosshair.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Demonstrates some customized mouse interaction by drawing a crosshair that follows -the mouse. - - -""" - -import initExample ## Add path to library (just for examples; you do not need this) -import numpy as np -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.Point import Point - -#generate layout -app = pg.mkQApp("Crosshair Example") -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: crosshair') -label = pg.LabelItem(justify='right') -win.addItem(label) -p1 = win.addPlot(row=1, col=0) -p2 = win.addPlot(row=2, col=0) - -region = pg.LinearRegionItem() -region.setZValue(10) -# Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this -# item when doing auto-range calculations. -p2.addItem(region, ignoreBounds=True) - -#pg.dbg() -p1.setAutoVisible(y=True) - - -#create numpy arrays -#make the numbers large to show that the xrange shows data from 10000 to all the way 0 -data1 = 10000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000) -data2 = 15000 + 15000 * pg.gaussianFilter(np.random.random(size=10000), 10) + 3000 * np.random.random(size=10000) - -p1.plot(data1, pen="r") -p1.plot(data2, pen="g") - -p2.plot(data1, pen="w") - -def update(): - region.setZValue(10) - minX, maxX = region.getRegion() - p1.setXRange(minX, maxX, padding=0) - -region.sigRegionChanged.connect(update) - -def updateRegion(window, viewRange): - rgn = viewRange[0] - region.setRegion(rgn) - -p1.sigRangeChanged.connect(updateRegion) - -region.setRegion([1000, 2000]) - -#cross hair -vLine = pg.InfiniteLine(angle=90, movable=False) -hLine = pg.InfiniteLine(angle=0, movable=False) -p1.addItem(vLine, ignoreBounds=True) -p1.addItem(hLine, ignoreBounds=True) - - -vb = p1.vb - -def mouseMoved(evt): - pos = evt[0] ## using signal proxy turns original arguments into a tuple - if p1.sceneBoundingRect().contains(pos): - mousePoint = vb.mapSceneToView(pos) - index = int(mousePoint.x()) - if index > 0 and index < len(data1): - label.setText("x=%0.1f, y1=%0.1f, y2=%0.1f" % (mousePoint.x(), data1[index], data2[index])) - vLine.setPos(mousePoint.x()) - hLine.setPos(mousePoint.y()) - - - -proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved) -#p1.scene().sigMouseMoved.connect(mouseMoved) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/customGraphicsItem.py b/pyqtgraph/examples/customGraphicsItem.py deleted file mode 100644 index 9723b83..0000000 --- a/pyqtgraph/examples/customGraphicsItem.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Demonstrate creation of a custom graphic (a candlestick plot) - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph import QtCore, QtGui - -## Create a subclass of GraphicsObject. -## The only required methods are paint() and boundingRect() -## (see QGraphicsItem documentation) -class CandlestickItem(pg.GraphicsObject): - def __init__(self, data): - pg.GraphicsObject.__init__(self) - self.data = data ## data must have fields: time, open, close, min, max - self.generatePicture() - - def generatePicture(self): - ## pre-computing a QPicture object allows paint() to run much more quickly, - ## rather than re-drawing the shapes every time. - self.picture = QtGui.QPicture() - p = QtGui.QPainter(self.picture) - p.setPen(pg.mkPen('w')) - w = (self.data[1][0] - self.data[0][0]) / 3. - for (t, open, close, min, max) in self.data: - p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max)) - if open > close: - p.setBrush(pg.mkBrush('r')) - else: - p.setBrush(pg.mkBrush('g')) - p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open)) - p.end() - - def paint(self, p, *args): - p.drawPicture(0, 0, self.picture) - - def boundingRect(self): - ## boundingRect _must_ indicate the entire area that will be drawn on - ## or else we will get artifacts and possibly crashing. - ## (in this case, QPicture does all the work of computing the bouning rect for us) - return QtCore.QRectF(self.picture.boundingRect()) - -data = [ ## fields are (time, open, close, min, max). - (1., 10, 13, 5, 15), - (2., 13, 17, 9, 20), - (3., 17, 14, 11, 23), - (4., 14, 15, 5, 19), - (5., 15, 9, 8, 22), - (6., 9, 15, 8, 16), -] -item = CandlestickItem(data) -plt = pg.plot() -plt.addItem(item) -plt.setWindowTitle('pyqtgraph example: customGraphicsItem') - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/customPlot.py b/pyqtgraph/examples/customPlot.py deleted file mode 100644 index 007ec69..0000000 --- a/pyqtgraph/examples/customPlot.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the creation of a plot with -DateAxisItem and a customized ViewBox. -""" - - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import time - -class CustomViewBox(pg.ViewBox): - def __init__(self, *args, **kwds): - kwds['enableMenu'] = False - pg.ViewBox.__init__(self, *args, **kwds) - self.setMouseMode(self.RectMode) - - ## reimplement right-click to zoom out - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: - self.autoRange() - - ## reimplement mouseDragEvent to disable continuous axis zoom - def mouseDragEvent(self, ev, axis=None): - if axis is not None and ev.button() == QtCore.Qt.RightButton: - ev.ignore() - else: - pg.ViewBox.mouseDragEvent(self, ev, axis=axis) - -class CustomTickSliderItem(pg.TickSliderItem): - def __init__(self, *args, **kwds): - pg.TickSliderItem.__init__(self, *args, **kwds) - - self.all_ticks = {} - self._range = [0,1] - - def setTicks(self, ticks): - for tick, pos in self.listTicks(): - self.removeTick(tick) - - for pos in ticks: - tickItem = self.addTick(pos, movable=False, color="333333") - self.all_ticks[pos] = tickItem - - self.updateRange(None, self._range) - - def updateRange(self, vb, viewRange): - origin = self.tickSize/2. - length = self.length - - lengthIncludingPadding = length + self.tickSize + 2 - - self._range = viewRange - - for pos in self.all_ticks: - tickValueIncludingPadding = (pos - viewRange[0]) / (viewRange[1] - viewRange[0]) - tickValue = (tickValueIncludingPadding*lengthIncludingPadding - origin) / length - - # Convert from np.bool_ to bool for setVisible - visible = bool(tickValue >= 0 and tickValue <= 1) - - tick = self.all_ticks[pos] - tick.setVisible(visible) - - if visible: - self.setTickValue(tick, tickValue) - -app = pg.mkQApp() - -axis = pg.DateAxisItem(orientation='bottom') -vb = CustomViewBox() - -pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with DateAxisItem, custom ViewBox and markers on x axis
Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom") - -dates = np.arange(8) * (3600*24*356) -pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o') - -# Using allowAdd and allowRemove to limit user interaction -tickViewer = CustomTickSliderItem(allowAdd=False, allowRemove=False) -vb.sigXRangeChanged.connect(tickViewer.updateRange) -pw.plotItem.layout.addItem(tickViewer, 4, 1) - -tickViewer.setTicks( [dates[0], dates[2], dates[-1]] ) - -pw.show() -pw.setWindowTitle('pyqtgraph example: customPlot') - -r = pg.PolyLineROI([(0,0), (10, 10)]) -pw.addItem(r) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/designerExample.py b/pyqtgraph/examples/designerExample.py deleted file mode 100644 index 812eff6..0000000 --- a/pyqtgraph/examples/designerExample.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Simple example of loading UI template created with Qt Designer. - -This example uses uic.loadUiType to parse and load the ui at runtime. It is also -possible to pre-compile the .ui file using pyuic (see VideoSpeedTest and -ScatterPlotSpeedTest examples; these .ui files have been compiled with the -tools/rebuildUi.py script). -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -import os - -pg.mkQApp() - -## Define main window class from template -path = os.path.dirname(os.path.abspath(__file__)) -uiFile = os.path.join(path, 'designerExample.ui') -WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) - -class MainWindow(TemplateBaseClass): - def __init__(self): - TemplateBaseClass.__init__(self) - self.setWindowTitle('pyqtgraph example: Qt Designer') - - # Create the main window - self.ui = WindowTemplate() - self.ui.setupUi(self) - self.ui.plotBtn.clicked.connect(self.plot) - - self.show() - - def plot(self): - self.ui.plot.plot(np.random.normal(size=100), clear=True) - -win = MainWindow() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/dockarea.py b/pyqtgraph/examples/dockarea.py deleted file mode 100644 index f87a8da..0000000 --- a/pyqtgraph/examples/dockarea.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of pyqtgraph's dock widget system. - -The dockarea system allows the design of user interfaces which can be rearranged by -the user at runtime. Docks can be moved, resized, stacked, and torn out of the main -window. This is similar in principle to the docking system built into Qt, but -offers a more deterministic dock placement API (in Qt it is very difficult to -programatically generate complex dock arrangements). Additionally, Qt's docks are -designed to be used as small panels around the outer edge of a window. Pyqtgraph's -docks were created with the notion that the entire window (or any portion of it) -would consist of dockable components. - -""" - - - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph.console -import numpy as np - -from pyqtgraph.dockarea import * - -app = pg.mkQApp("DockArea Example") -win = QtGui.QMainWindow() -area = DockArea() -win.setCentralWidget(area) -win.resize(1000,500) -win.setWindowTitle('pyqtgraph example: dockarea') - -## Create docks, place them into the window one at a time. -## Note that size arguments are only a suggestion; docks will still have to -## fill the entire dock area and obey the limits of their internal widgets. -d1 = Dock("Dock1", size=(1, 1)) ## give this dock the minimum possible size -d2 = Dock("Dock2 - Console", size=(500,300), closable=True) -d3 = Dock("Dock3", size=(500,400)) -d4 = Dock("Dock4 (tabbed) - Plot", size=(500,200)) -d5 = Dock("Dock5 - Image", size=(500,200)) -d6 = Dock("Dock6 (tabbed) - Plot", size=(500,200)) -area.addDock(d1, 'left') ## place d1 at left edge of dock area (it will fill the whole space since there are no other docks yet) -area.addDock(d2, 'right') ## place d2 at right edge of dock area -area.addDock(d3, 'bottom', d1)## place d3 at bottom edge of d1 -area.addDock(d4, 'right') ## place d4 at right edge of dock area -area.addDock(d5, 'left', d1) ## place d5 at left edge of d1 -area.addDock(d6, 'top', d4) ## place d5 at top edge of d4 - -## Test ability to move docks programatically after they have been placed -area.moveDock(d4, 'top', d2) ## move d4 to top edge of d2 -area.moveDock(d6, 'above', d4) ## move d6 to stack on top of d4 -area.moveDock(d5, 'top', d2) ## move d5 to top edge of d2 - - -## Add widgets into each dock - -## first dock gets save/restore buttons -w1 = pg.LayoutWidget() -label = QtGui.QLabel(""" -- DockArea Example -- -This window has 6 Dock widgets in it. Each dock can be dragged -by its title bar to occupy a different space within the window -but note that one dock has its title bar hidden). Additionally, -the borders between docks may be dragged to resize. Docks that are dragged on top -of one another are stacked in a tabbed layout. Double-click a dock title -bar to place it in its own window. -""") -saveBtn = QtGui.QPushButton('Save dock state') -restoreBtn = QtGui.QPushButton('Restore dock state') -restoreBtn.setEnabled(False) -w1.addWidget(label, row=0, col=0) -w1.addWidget(saveBtn, row=1, col=0) -w1.addWidget(restoreBtn, row=2, col=0) -d1.addWidget(w1) -state = None -def save(): - global state - state = area.saveState() - restoreBtn.setEnabled(True) -def load(): - global state - area.restoreState(state) -saveBtn.clicked.connect(save) -restoreBtn.clicked.connect(load) - - -w2 = pg.console.ConsoleWidget() -d2.addWidget(w2) - -## Hide title bar on dock 3 -d3.hideTitleBar() -w3 = pg.PlotWidget(title="Plot inside dock with no title bar") -w3.plot(np.random.normal(size=100)) -d3.addWidget(w3) - -w4 = pg.PlotWidget(title="Dock 4 plot") -w4.plot(np.random.normal(size=100)) -d4.addWidget(w4) - -w5 = pg.ImageView() -w5.setImage(np.random.normal(size=(100,100))) -d5.addWidget(w5) - -w6 = pg.PlotWidget(title="Dock 6 plot") -w6.plot(np.random.normal(size=100)) -d6.addWidget(w6) - - - -win.show() - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/exampleLoaderTemplate_pyqt5.py b/pyqtgraph/examples/exampleLoaderTemplate_pyqt5.py deleted file mode 100644 index c39b55a..0000000 --- a/pyqtgraph/examples/exampleLoaderTemplate_pyqt5.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui' -# -# Created by: PyQt5 UI code generator 5.12.3 -# -# WARNING! All changes made in this file will be lost! - - -from PyQt5 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(846, 552) - self.gridLayout_2 = QtWidgets.QGridLayout(Form) - self.gridLayout_2.setObjectName("gridLayout_2") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.widget = QtWidgets.QWidget(self.splitter) - self.widget.setObjectName("widget") - self.gridLayout = QtWidgets.QGridLayout(self.widget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.exampleTree = QtWidgets.QTreeWidget(self.widget) - self.exampleTree.setObjectName("exampleTree") - self.exampleTree.headerItem().setText(0, "1") - self.exampleTree.header().setVisible(False) - self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2) - self.qtLibCombo = QtWidgets.QComboBox(self.widget) - self.qtLibCombo.setObjectName("qtLibCombo") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1) - self.label = QtWidgets.QLabel(self.widget) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 1, 0, 1, 1) - self.loadBtn = QtWidgets.QPushButton(self.widget) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1) - self.widget1 = QtWidgets.QWidget(self.splitter) - self.widget1.setObjectName("widget1") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.loadedFileLabel = QtWidgets.QLabel(self.widget1) - font = QtGui.QFont() - font.setBold(True) - self.loadedFileLabel.setFont(font) - self.loadedFileLabel.setText("") - self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter) - self.loadedFileLabel.setObjectName("loadedFileLabel") - self.verticalLayout.addWidget(self.loadedFileLabel) - self.codeView = QtWidgets.QPlainTextEdit(self.widget1) - font = QtGui.QFont() - font.setFamily("Courier New") - self.codeView.setFont(font) - self.codeView.setObjectName("codeView") - self.verticalLayout.addWidget(self.codeView) - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.qtLibCombo.setItemText(0, _translate("Form", "default")) - self.qtLibCombo.setItemText(1, _translate("Form", "PyQt5")) - self.qtLibCombo.setItemText(2, _translate("Form", "PySide2")) - self.qtLibCombo.setItemText(3, _translate("Form", "PySide6")) - self.qtLibCombo.setItemText(4, _translate("Form", "PyQt6")) - self.label.setText(_translate("Form", "Qt Library:")) - self.loadBtn.setText(_translate("Form", "Run Example")) diff --git a/pyqtgraph/examples/exampleLoaderTemplate_pyqt6.py b/pyqtgraph/examples/exampleLoaderTemplate_pyqt6.py deleted file mode 100644 index 46ea294..0000000 --- a/pyqtgraph/examples/exampleLoaderTemplate_pyqt6.py +++ /dev/null @@ -1,78 +0,0 @@ -# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.1 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(846, 552) - self.gridLayout_2 = QtWidgets.QGridLayout(Form) - self.gridLayout_2.setObjectName("gridLayout_2") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Orientations.Horizontal) - self.splitter.setObjectName("splitter") - self.widget = QtWidgets.QWidget(self.splitter) - self.widget.setObjectName("widget") - self.gridLayout = QtWidgets.QGridLayout(self.widget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.exampleTree = QtWidgets.QTreeWidget(self.widget) - self.exampleTree.setObjectName("exampleTree") - self.exampleTree.headerItem().setText(0, "1") - self.exampleTree.header().setVisible(False) - self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2) - self.qtLibCombo = QtWidgets.QComboBox(self.widget) - self.qtLibCombo.setObjectName("qtLibCombo") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1) - self.label = QtWidgets.QLabel(self.widget) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 1, 0, 1, 1) - self.loadBtn = QtWidgets.QPushButton(self.widget) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1) - self.widget1 = QtWidgets.QWidget(self.splitter) - self.widget1.setObjectName("widget1") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.loadedFileLabel = QtWidgets.QLabel(self.widget1) - font = QtGui.QFont() - font.setBold(True) - self.loadedFileLabel.setFont(font) - self.loadedFileLabel.setText("") - self.loadedFileLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.loadedFileLabel.setObjectName("loadedFileLabel") - self.verticalLayout.addWidget(self.loadedFileLabel) - self.codeView = QtWidgets.QPlainTextEdit(self.widget1) - font = QtGui.QFont() - font.setFamily("Courier New") - self.codeView.setFont(font) - self.codeView.setObjectName("codeView") - self.verticalLayout.addWidget(self.codeView) - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.qtLibCombo.setItemText(0, _translate("Form", "default")) - self.qtLibCombo.setItemText(1, _translate("Form", "PyQt5")) - self.qtLibCombo.setItemText(2, _translate("Form", "PySide2")) - self.qtLibCombo.setItemText(3, _translate("Form", "PySide6")) - self.qtLibCombo.setItemText(4, _translate("Form", "PyQt6")) - self.label.setText(_translate("Form", "Qt Library:")) - self.loadBtn.setText(_translate("Form", "Run Example")) diff --git a/pyqtgraph/examples/exampleLoaderTemplate_pyside2.py b/pyqtgraph/examples/exampleLoaderTemplate_pyside2.py deleted file mode 100644 index ac80386..0000000 --- a/pyqtgraph/examples/exampleLoaderTemplate_pyside2.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui', -# licensing of 'exampleLoaderTemplate.ui' applies. -# -# Created: Mon Feb 22 18:33:36 2021 -# by: pyside2-uic running on PySide2 5.12.6 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(846, 552) - self.gridLayout_2 = QtWidgets.QGridLayout(Form) - self.gridLayout_2.setObjectName("gridLayout_2") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.widget = QtWidgets.QWidget(self.splitter) - self.widget.setObjectName("widget") - self.gridLayout = QtWidgets.QGridLayout(self.widget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.exampleTree = QtWidgets.QTreeWidget(self.widget) - self.exampleTree.setObjectName("exampleTree") - self.exampleTree.headerItem().setText(0, "1") - self.exampleTree.header().setVisible(False) - self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2) - self.qtLibCombo = QtWidgets.QComboBox(self.widget) - self.qtLibCombo.setObjectName("qtLibCombo") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1) - self.label = QtWidgets.QLabel(self.widget) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 1, 0, 1, 1) - self.loadBtn = QtWidgets.QPushButton(self.widget) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1) - self.widget1 = QtWidgets.QWidget(self.splitter) - self.widget1.setObjectName("widget1") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.loadedFileLabel = QtWidgets.QLabel(self.widget1) - font = QtGui.QFont() - font.setBold(True) - self.loadedFileLabel.setFont(font) - self.loadedFileLabel.setText("") - self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter) - self.loadedFileLabel.setObjectName("loadedFileLabel") - self.verticalLayout.addWidget(self.loadedFileLabel) - self.codeView = QtWidgets.QPlainTextEdit(self.widget1) - font = QtGui.QFont() - font.setFamily("Courier New") - self.codeView.setFont(font) - self.codeView.setObjectName("codeView") - self.verticalLayout.addWidget(self.codeView) - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "PyQtGraph", None, -1)) - self.qtLibCombo.setItemText(0, QtWidgets.QApplication.translate("Form", "default", None, -1)) - self.qtLibCombo.setItemText(1, QtWidgets.QApplication.translate("Form", "PyQt5", None, -1)) - self.qtLibCombo.setItemText(2, QtWidgets.QApplication.translate("Form", "PySide2", None, -1)) - self.qtLibCombo.setItemText(3, QtWidgets.QApplication.translate("Form", "PySide6", None, -1)) - self.qtLibCombo.setItemText(4, QtWidgets.QApplication.translate("Form", "PyQt6", None, -1)) - self.label.setText(QtWidgets.QApplication.translate("Form", "Qt Library:", None, -1)) - self.loadBtn.setText(QtWidgets.QApplication.translate("Form", "Run Example", None, -1)) - diff --git a/pyqtgraph/examples/exampleLoaderTemplate_pyside6.py b/pyqtgraph/examples/exampleLoaderTemplate_pyside6.py deleted file mode 100644 index 2087abb..0000000 --- a/pyqtgraph/examples/exampleLoaderTemplate_pyside6.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'exampleLoaderTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(846, 552) - self.gridLayout_2 = QGridLayout(Form) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.splitter = QSplitter(Form) - self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Horizontal) - self.widget = QWidget(self.splitter) - self.widget.setObjectName(u"widget") - self.gridLayout = QGridLayout(self.widget) - self.gridLayout.setObjectName(u"gridLayout") - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.exampleTree = QTreeWidget(self.widget) - __qtreewidgetitem = QTreeWidgetItem() - __qtreewidgetitem.setText(0, u"1"); - self.exampleTree.setHeaderItem(__qtreewidgetitem) - self.exampleTree.setObjectName(u"exampleTree") - self.exampleTree.header().setVisible(False) - - self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2) - - self.qtLibCombo = QComboBox(self.widget) - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.addItem("") - self.qtLibCombo.setObjectName(u"qtLibCombo") - - self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1) - - self.label = QLabel(self.widget) - self.label.setObjectName(u"label") - - self.gridLayout.addWidget(self.label, 1, 0, 1, 1) - - self.loadBtn = QPushButton(self.widget) - self.loadBtn.setObjectName(u"loadBtn") - - self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1) - - self.splitter.addWidget(self.widget) - self.widget1 = QWidget(self.splitter) - self.widget1.setObjectName(u"widget1") - self.verticalLayout = QVBoxLayout(self.widget1) - self.verticalLayout.setObjectName(u"verticalLayout") - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.loadedFileLabel = QLabel(self.widget1) - self.loadedFileLabel.setObjectName(u"loadedFileLabel") - font = QFont() - font.setBold(True) - self.loadedFileLabel.setFont(font) - self.loadedFileLabel.setAlignment(Qt.AlignCenter) - - self.verticalLayout.addWidget(self.loadedFileLabel) - - self.codeView = QPlainTextEdit(self.widget1) - self.codeView.setObjectName(u"codeView") - font1 = QFont() - font1.setFamily(u"Courier New") - self.codeView.setFont(font1) - - self.verticalLayout.addWidget(self.codeView) - - self.splitter.addWidget(self.widget1) - - self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.qtLibCombo.setItemText(0, QCoreApplication.translate("Form", u"default", None)) - self.qtLibCombo.setItemText(1, QCoreApplication.translate("Form", u"PyQt5", None)) - self.qtLibCombo.setItemText(2, QCoreApplication.translate("Form", u"PySide2", None)) - self.qtLibCombo.setItemText(3, QCoreApplication.translate("Form", u"PySide6", None)) - self.qtLibCombo.setItemText(4, QCoreApplication.translate("Form", u"PyQt6", None)) - - self.label.setText(QCoreApplication.translate("Form", u"Qt Library:", None)) - self.loadBtn.setText(QCoreApplication.translate("Form", u"Run Example", None)) - self.loadedFileLabel.setText("") - # retranslateUi - diff --git a/pyqtgraph/examples/fractal.py b/pyqtgraph/examples/fractal.py deleted file mode 100644 index 3f00814..0000000 --- a/pyqtgraph/examples/fractal.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Displays an interactive Koch fractal -""" -import initExample ## Add path to library (just for examples; you do not need this) - -from functools import reduce -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -app = pg.mkQApp("Fractal Example") - -# Set up UI widgets -win = pg.QtGui.QWidget() -win.setWindowTitle('pyqtgraph example: fractal demo') -layout = pg.QtGui.QGridLayout() -win.setLayout(layout) -layout.setContentsMargins(0, 0, 0, 0) -depthLabel = pg.QtGui.QLabel('fractal depth:') -layout.addWidget(depthLabel, 0, 0) -depthSpin = pg.SpinBox(value=5, step=1, bounds=[1, 10], delay=0, int=True) -depthSpin.resize(100, 20) -layout.addWidget(depthSpin, 0, 1) -w = pg.GraphicsLayoutWidget() -layout.addWidget(w, 1, 0, 1, 2) -win.show() - -# Set up graphics -v = w.addViewBox() -v.setAspectLocked() -baseLine = pg.PolyLineROI([[0, 0], [1, 0], [1.5, 1], [2, 0], [3, 0]], pen=(0, 255, 0, 100), movable=False) -v.addItem(baseLine) -fc = pg.PlotCurveItem(pen=(255, 255, 255, 200), antialias=True) -v.addItem(fc) -v.autoRange() - - -transformMap = [0, 0, None] - - -def update(): - # recalculate and redraw the fractal curve - - depth = depthSpin.value() - pts = baseLine.getState()['points'] - nbseg = len(pts) - 1 - nseg = nbseg**depth - - # Get a transformation matrix for each base segment - trs = [] - v1 = pts[-1] - pts[0] - l1 = v1.length() - for i in range(len(pts)-1): - p1 = pts[i] - p2 = pts[i+1] - v2 = p2 - p1 - t = p1 - pts[0] - r = v2.angle(v1) - s = v2.length() / l1 - trs.append(pg.SRTTransform({'pos': t, 'scale': (s, s), 'angle': r})) - - basePts = [np.array(list(pt) + [1]) for pt in baseLine.getState()['points']] - baseMats = np.dstack([tr.matrix().T for tr in trs]).transpose(2, 0, 1) - - # Generate an array of matrices to transform base points - global transformMap - if transformMap[:2] != [depth, nbseg]: - # we can cache the transform index to save a little time.. - nseg = nbseg**depth - matInds = np.empty((depth, nseg), dtype=int) - for i in range(depth): - matInds[i] = np.tile(np.repeat(np.arange(nbseg), nbseg**(depth-1-i)), nbseg**i) - transformMap = [depth, nbseg, matInds] - - # Each column in matInds contains the indices referring to the base transform - # matrices that must be multiplied together to generate the final transform - # for each segment of the fractal - matInds = transformMap[2] - - # Collect all matrices needed for generating fractal curve - mats = baseMats[matInds] - - # Magic-multiply stacks of matrices together - def matmul(a, b): - return np.sum(np.transpose(a,(0,2,1))[..., None] * b[..., None, :], axis=-3) - mats = reduce(matmul, mats) - - # Transform base points through matrix array - pts = np.empty((nseg * nbseg + 1, 2)) - for l in range(len(trs)): - bp = basePts[l] - pts[l:-1:len(trs)] = np.dot(mats, bp)[:, :2] - - # Finish the curve with the last base point - pts[-1] = basePts[-1][:2] - - # update fractal curve with new points - fc.setData(pts[:,0], pts[:,1]) - - -# Update the fractal whenever the base shape or depth has changed -baseLine.sigRegionChanged.connect(update) -depthSpin.valueChanged.connect(update) - -# Initialize -update() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - \ No newline at end of file diff --git a/pyqtgraph/examples/hdf5.py b/pyqtgraph/examples/hdf5.py deleted file mode 100644 index 3cd5de2..0000000 --- a/pyqtgraph/examples/hdf5.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -""" -In this example we create a subclass of PlotCurveItem for displaying a very large -data set from an HDF5 file that does not fit in memory. - -The basic approach is to override PlotCurveItem.viewRangeChanged such that it -reads only the portion of the HDF5 data that is necessary to display the visible -portion of the data. This is further downsampled to reduce the number of samples -being displayed. - -A more clever implementation of this class would employ some kind of caching -to avoid re-reading the entire visible waveform at every update. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import sys, os -import numpy as np -import h5py -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - -pg.mkQApp() - - -plt = pg.plot() -plt.setWindowTitle('pyqtgraph example: HDF5 big data') -plt.enableAutoRange(False, False) -plt.setXRange(0, 500) - -class HDF5Plot(pg.PlotCurveItem): - def __init__(self, *args, **kwds): - self.hdf5 = None - self.limit = 10000 # maximum number of samples to be plotted - pg.PlotCurveItem.__init__(self, *args, **kwds) - - def setHDF5(self, data): - self.hdf5 = data - self.updateHDF5Plot() - - def viewRangeChanged(self): - self.updateHDF5Plot() - - def updateHDF5Plot(self): - if self.hdf5 is None: - self.setData([]) - return - - vb = self.getViewBox() - if vb is None: - return # no ViewBox yet - - # Determine what data range must be read from HDF5 - xrange = vb.viewRange()[0] - start = max(0,int(xrange[0])-1) - stop = min(len(self.hdf5), int(xrange[1]+2)) - - # Decide by how much we should downsample - ds = int((stop-start) / self.limit) + 1 - - if ds == 1: - # Small enough to display with no intervention. - visible = self.hdf5[start:stop] - scale = 1 - else: - # Here convert data into a down-sampled array suitable for visualizing. - # Must do this piecewise to limit memory usage. - samples = 1 + ((stop-start) // ds) - visible = np.zeros(samples*2, dtype=self.hdf5.dtype) - sourcePtr = start - targetPtr = 0 - - # read data in chunks of ~1M samples - chunkSize = (1000000//ds) * ds - while sourcePtr < stop-1: - chunk = self.hdf5[sourcePtr:min(stop,sourcePtr+chunkSize)] - sourcePtr += len(chunk) - - # reshape chunk to be integral multiple of ds - chunk = chunk[:(len(chunk)//ds) * ds].reshape(len(chunk)//ds, ds) - - # compute max and min - chunkMax = chunk.max(axis=1) - chunkMin = chunk.min(axis=1) - - # interleave min and max into plot data to preserve envelope shape - visible[targetPtr:targetPtr+chunk.shape[0]*2:2] = chunkMin - visible[1+targetPtr:1+targetPtr+chunk.shape[0]*2:2] = chunkMax - targetPtr += chunk.shape[0]*2 - - visible = visible[:targetPtr] - scale = ds * 0.5 - - self.setData(visible) # update the plot - self.setPos(start, 0) # shift to match starting index - self.resetTransform() - self.scale(scale, 1) # scale to match downsampling - - - - -def createFile(finalSize=2000000000): - """Create a large HDF5 data file for testing. - Data consists of 1M random samples tiled through the end of the array. - """ - - chunk = np.random.normal(size=1000000).astype(np.float32) - - f = h5py.File('test.hdf5', 'w') - f.create_dataset('data', data=chunk, chunks=True, maxshape=(None,)) - data = f['data'] - - nChunks = finalSize // (chunk.size * chunk.itemsize) - with pg.ProgressDialog("Generating test.hdf5...", 0, nChunks) as dlg: - for i in range(nChunks): - newshape = [data.shape[0] + chunk.shape[0]] - data.resize(newshape) - data[-chunk.shape[0]:] = chunk - dlg += 1 - if dlg.wasCanceled(): - f.close() - os.remove('test.hdf5') - sys.exit() - dlg += 1 - f.close() - -if len(sys.argv) > 1: - fileName = sys.argv[1] -else: - fileName = 'test.hdf5' - if not os.path.isfile(fileName): - size, ok = QtGui.QInputDialog.getDouble(None, "Create HDF5 Dataset?", "This demo requires a large HDF5 array. To generate a file, enter the array size (in GB) and press OK.", 2.0) - if not ok: - sys.exit(0) - else: - createFile(int(size*1e9)) - #raise Exception("No suitable HDF5 file found. Use createFile() to generate an example file.") - -f = h5py.File(fileName, 'r') -curve = HDF5Plot() -curve.setHDF5(f['data']) -plt.addItem(curve) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - - - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - - - - diff --git a/pyqtgraph/examples/histogram.py b/pyqtgraph/examples/histogram.py deleted file mode 100644 index 53d4fda..0000000 --- a/pyqtgraph/examples/histogram.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -In this example we draw two different kinds of histogram. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.GraphicsLayoutWidget(show=True) -win.resize(800,350) -win.setWindowTitle('pyqtgraph example: Histogram') -plt1 = win.addPlot() -plt2 = win.addPlot() - -## make interesting distribution of values -vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)]) - -## compute standard histogram -y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40)) - -## Using stepMode="center" causes the plot to draw two lines for each sample. -## notice that len(x) == len(y)+1 -plt1.plot(x, y, stepMode="center", fillLevel=0, fillOutline=True, brush=(0,0,255,150)) - -## Now draw all points as a nicely-spaced scatter plot -y = pg.pseudoScatter(vals, spacing=0.15) -#plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5) -plt2.plot(vals, y, pen=None, symbol='o', symbolSize=5, symbolPen=(255,255,255,200), symbolBrush=(0,0,255,150)) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/imageAnalysis.py b/pyqtgraph/examples/imageAnalysis.py deleted file mode 100644 index d6febb1..0000000 --- a/pyqtgraph/examples/imageAnalysis.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Demonstrates common image analysis tools. - -Many of the features demonstrated here are already provided by the ImageView -widget, but here we present a lower-level approach that provides finer control -over the user interface. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - -# Interpret image data as row-major instead of col-major -pg.setConfigOptions(imageAxisOrder='row-major') - -pg.mkQApp() -win = pg.GraphicsLayoutWidget() -win.setWindowTitle('pyqtgraph example: Image Analysis') - -# A plot area (ViewBox + axes) for displaying the image -p1 = win.addPlot(title="") - -# Item for displaying image data -img = pg.ImageItem() -p1.addItem(img) - -# Custom ROI for selecting an image region -roi = pg.ROI([-8, 14], [6, 5]) -roi.addScaleHandle([0.5, 1], [0.5, 0.5]) -roi.addScaleHandle([0, 0.5], [0.5, 0.5]) -p1.addItem(roi) -roi.setZValue(10) # make sure ROI is drawn above image - -# Isocurve drawing -iso = pg.IsocurveItem(level=0.8, pen='g') -iso.setParentItem(img) -iso.setZValue(5) - -# Contrast/color control -hist = pg.HistogramLUTItem() -hist.setImageItem(img) -win.addItem(hist) - -# Draggable line for setting isocurve level -isoLine = pg.InfiniteLine(angle=0, movable=True, pen='g') -hist.vb.addItem(isoLine) -hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier -isoLine.setValue(0.8) -isoLine.setZValue(1000) # bring iso line above contrast controls - -# Another plot area for displaying ROI data -win.nextRow() -p2 = win.addPlot(colspan=2) -p2.setMaximumHeight(250) -win.resize(800, 800) -win.show() - - -# Generate image data -data = np.random.normal(size=(200, 100)) -data[20:80, 20:80] += 2. -data = pg.gaussianFilter(data, (3, 3)) -data += np.random.normal(size=(200, 100)) * 0.1 -img.setImage(data) -hist.setLevels(data.min(), data.max()) - -# build isocurves from smoothed data -iso.setData(pg.gaussianFilter(data, (2, 2))) - -# set position and scale of image -tr = QtGui.QTransform() -img.setTransform(tr.scale(0.2, 0.2).translate(-50, 0)) - -# zoom to fit imageo -p1.autoRange() - - -# Callbacks for handling user interaction -def updatePlot(): - global img, roi, data, p2 - selected = roi.getArrayRegion(data, img) - p2.plot(selected.mean(axis=0), clear=True) - -roi.sigRegionChanged.connect(updatePlot) -updatePlot() - -def updateIsocurve(): - global isoLine, iso - iso.setLevel(isoLine.value()) - -isoLine.sigDragged.connect(updateIsocurve) - -def imageHoverEvent(event): - """Show the position, pixel, and value under the mouse cursor. - """ - if event.isExit(): - p1.setTitle("") - return - pos = event.pos() - i, j = pos.y(), pos.x() - i = int(np.clip(i, 0, data.shape[0] - 1)) - j = int(np.clip(j, 0, data.shape[1] - 1)) - val = data[i, j] - ppos = img.mapToParent(pos) - x, y = ppos.x(), ppos.y() - p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %g" % (x, y, i, j, val)) - -# Monkey-patch the image to use our custom hover function. -# This is generally discouraged (you should subclass ImageItem instead), -# but it works for a very simple use like this. -img.hoverEvent = imageHoverEvent - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/infiniteline_performance.py b/pyqtgraph/examples/infiniteline_performance.py deleted file mode 100644 index 9682080..0000000 --- a/pyqtgraph/examples/infiniteline_performance.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/python - -import initExample ## Add path to library (just for examples; you do not need this) -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg -from pyqtgraph.ptime import time -app = pg.mkQApp("Infinite Line Performance") - -p = pg.plot() -p.setWindowTitle('pyqtgraph performance: InfiniteLine') -p.setRange(QtCore.QRectF(0, -10, 5000, 20)) -p.setLabel('bottom', 'Index', units='B') -curve = p.plot() - -# Add a large number of horizontal InfiniteLine to plot -for i in range(100): - line = pg.InfiniteLine(pos=np.random.randint(5000), movable=True) - p.addItem(line) - -data = np.random.normal(size=(50, 5000)) -ptr = 0 -lastTime = time() -fps = None - - -def update(): - global curve, data, ptr, p, lastTime, fps - curve.setData(data[ptr % 10]) - ptr += 1 - now = time() - dt = now - lastTime - lastTime = now - if fps is None: - fps = 1.0/dt - else: - s = np.clip(dt*3., 0, 1) - fps = fps * (1-s) + (1.0/dt) * s - p.setTitle('%0.2f fps' % fps) - app.processEvents() # force complete redraw for every plot - - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(0) - - -# Start Qt event loop unless running in interactive mode. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file diff --git a/pyqtgraph/examples/initExample.py b/pyqtgraph/examples/initExample.py deleted file mode 100644 index 9b86880..0000000 --- a/pyqtgraph/examples/initExample.py +++ /dev/null @@ -1,32 +0,0 @@ -## make this version of pyqtgraph importable before any others -## we do this to make sure that, when running examples, the correct library -## version is imported (if there are multiple versions present). -import sys, os -import importlib - -if not hasattr(sys, 'frozen'): - if __file__ == '': - path = os.getcwd() - else: - path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) - path.rstrip(os.path.sep) - if 'pyqtgraph' in os.listdir(path): - sys.path.insert(0, path) ## examples adjacent to pyqtgraph (as in source tree) - else: - for p in sys.path: - if len(p) < 3: - continue - if path.startswith(p): ## If the example is already in an importable location, promote that location - sys.path.remove(p) - sys.path.insert(0, p) - -import pyqtgraph as pg -print("Using", pg.Qt.QT_LIB) - -## Enable fault handling to give more helpful error messages on crash. -## Only available in python 3.3+ -try: - import faulthandler - faulthandler.enable() -except ImportError: - pass \ No newline at end of file diff --git a/pyqtgraph/examples/isocurve.py b/pyqtgraph/examples/isocurve.py deleted file mode 100644 index 1ed3059..0000000 --- a/pyqtgraph/examples/isocurve.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tests use of IsoCurve item displayed with image -""" - - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Isocurve Example") - -## make pretty looping data -frames = 200 -data = np.random.normal(size=(frames,30,30), loc=0, scale=100) -data = np.concatenate([data, data], axis=0) -data = pg.gaussianFilter(data, (10, 10, 10))[frames//2:frames + frames//2] -data[:, 15:16, 15:17] += 1 - -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: Isocurve') -vb = win.addViewBox() -img = pg.ImageItem(data[0]) -vb.addItem(img) -vb.setAspectLocked() - -## generate empty curves -curves = [] -levels = np.linspace(data.min(), data.max(), 10) -for i in range(len(levels)): - v = levels[i] - ## generate isocurve with automatic color selection - c = pg.IsocurveItem(level=v, pen=(i, len(levels)*1.5)) - c.setParentItem(img) ## make sure isocurve is always correctly displayed over image - c.setZValue(10) - curves.append(c) - -## animate! -ptr = 0 -imgLevels = (data.min(), data.max() * 2) -def update(): - global data, curves, img, ptr, imgLevels - ptr = (ptr + 1) % data.shape[0] - data[ptr] - img.setImage(data[ptr], levels=imgLevels) - for c in curves: - c.setData(data[ptr]) - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(50) - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/linkedViews.py b/pyqtgraph/examples/linkedViews.py deleted file mode 100644 index 0a387dd..0000000 --- a/pyqtgraph/examples/linkedViews.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the ability to link the axes of views together -Views can be linked manually using the context menu, but only if they are given -names. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - - -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import pyqtgraph as pg - -app = pg.mkQApp("Linked Views Example") -#mw = QtGui.QMainWindow() -#mw.resize(800,800) - -x = np.linspace(-50, 50, 1000) -y = np.sin(x) / x - -win = pg.GraphicsLayoutWidget(show=True, title="pyqtgraph example: Linked Views") -win.resize(800,600) - -win.addLabel("Linked Views", colspan=2) -win.nextRow() - -p1 = win.addPlot(x=x, y=y, name="Plot1", title="Plot1") -p2 = win.addPlot(x=x, y=y, name="Plot2", title="Plot2: Y linked with Plot1") -p2.setLabel('bottom', "Label to test offset") -p2.setYLink('Plot1') ## test linking by name - - -## create plots 3 and 4 out of order -p4 = win.addPlot(x=x, y=y, name="Plot4", title="Plot4: X -> Plot3 (deferred), Y -> Plot1", row=2, col=1) -p4.setXLink('Plot3') ## Plot3 has not been created yet, but this should still work anyway. -p4.setYLink(p1) -p3 = win.addPlot(x=x, y=y, name="Plot3", title="Plot3: X linked with Plot1", row=2, col=0) -p3.setXLink(p1) -p3.setLabel('left', "Label to test offset") -#QtGui.QApplication.processEvents() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - diff --git a/pyqtgraph/examples/logAxis.py b/pyqtgraph/examples/logAxis.py deleted file mode 100644 index 9a66f11..0000000 --- a/pyqtgraph/examples/logAxis.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Test programmatically setting log transformation modes. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import numpy as np -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg - - -app = pg.mkQApp("Log Axis Example") - -w = pg.GraphicsLayoutWidget(show=True) -w.setWindowTitle('pyqtgraph example: logAxis') -p1 = w.addPlot(0,0, title="X Semilog") -p2 = w.addPlot(1,0, title="Y Semilog") -p3 = w.addPlot(2,0, title="XY Log") -p1.showGrid(True, True) -p2.showGrid(True, True) -p3.showGrid(True, True) -p1.setLogMode(True, False) -p2.setLogMode(False, True) -p3.setLogMode(True, True) -w.show() - -y = np.random.normal(size=1000) -x = np.linspace(0, 1, 1000) -p1.plot(x, y) -p2.plot(x, y) -p3.plot(x, y) - - - -#p.getAxis('bottom').setLogMode(True) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/multiplePlotSpeedTest.py b/pyqtgraph/examples/multiplePlotSpeedTest.py deleted file mode 100644 index 07df752..0000000 --- a/pyqtgraph/examples/multiplePlotSpeedTest.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -app = pg.mkQApp() -plt = pg.PlotWidget() - -app.processEvents() - -## Putting this at the beginning or end does not have much effect -plt.show() - -## The auto-range is recomputed after each item is added, -## so disabling it before plotting helps -plt.enableAutoRange(False, False) - -def plot(): - start = pg.ptime.time() - n = 15 - pts = 100 - x = np.linspace(0, 0.8, pts) - y = np.random.random(size=pts)*0.8 - for i in range(n): - for j in range(n): - ## calling PlotWidget.plot() generates a PlotDataItem, which - ## has a bit more overhead than PlotCurveItem, which is all - ## we need here. This overhead adds up quickly and makes a big - ## difference in speed. - - #plt.plot(x=x+i, y=y+j) - plt.addItem(pg.PlotCurveItem(x=x+i, y=y+j)) - - #path = pg.arrayToQPath(x+i, y+j) - #item = QtGui.QGraphicsPathItem(path) - #item.setPen(pg.mkPen('w')) - #plt.addItem(item) - - dt = pg.ptime.time() - start - print("Create plots took: %0.3fms" % (dt*1000)) - -## Plot and clear 5 times, printing the time it took -for i in range(5): - plt.clear() - plot() - app.processEvents() - plt.autoRange() - - - - - -def fastPlot(): - ## Different approach: generate a single item with all data points. - ## This runs about 20x faster. - start = pg.ptime.time() - n = 15 - pts = 100 - x = np.linspace(0, 0.8, pts) - y = np.random.random(size=pts)*0.8 - xdata = np.empty((n, n, pts)) - xdata[:] = x.reshape(1,1,pts) + np.arange(n).reshape(n,1,1) - ydata = np.empty((n, n, pts)) - ydata[:] = y.reshape(1,1,pts) + np.arange(n).reshape(1,n,1) - conn = np.ones((n*n,pts)) - conn[:,-1] = False # make sure plots are disconnected - path = pg.arrayToQPath(xdata.flatten(), ydata.flatten(), conn.flatten()) - item = QtGui.QGraphicsPathItem(path) - item.setPen(pg.mkPen('w')) - plt.addItem(item) - - dt = pg.ptime.time() - start - print("Create plots took: %0.3fms" % (dt*1000)) - - -## Plot and clear 5 times, printing the time it took -if hasattr(pg, 'arrayToQPath'): - for i in range(5): - plt.clear() - fastPlot() - app.processEvents() -else: - print("Skipping fast tests--arrayToQPath function is missing.") - -plt.autoRange() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/multiprocess.py b/pyqtgraph/examples/multiprocess.py deleted file mode 100644 index 2e32b04..0000000 --- a/pyqtgraph/examples/multiprocess.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -import initExample ## Add path to library (just for examples; you do not need this) -import numpy as np -import pyqtgraph.multiprocess as mp -import pyqtgraph as pg -import time - - - - -print("\n=================\nStart Process") -proc = mp.Process() -import os -print("parent:", os.getpid(), "child:", proc.proc.pid) -print("started") -rnp = proc._import('numpy') -arr = rnp.array([1,2,3,4]) -print(repr(arr)) -print(str(arr)) -print("return value:", repr(arr.mean(_returnType='value'))) -print( "return proxy:", repr(arr.mean(_returnType='proxy'))) -print( "return auto: ", repr(arr.mean(_returnType='auto'))) -proc.join() -print( "process finished") - - - -print( "\n=================\nStart ForkedProcess") -proc = mp.ForkedProcess() -rnp = proc._import('numpy') -arr = rnp.array([1,2,3,4]) -print( repr(arr)) -print( str(arr)) -print( repr(arr.mean())) -proc.join() -print( "process finished") - - - - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -app = pg.mkQApp("Multiprocess Example") - -print( "\n=================\nStart QtProcess") -import sys -if (sys.flags.interactive != 1): - print( " (not interactive; remote process will exit immediately.)") -proc = mp.QtProcess() -d1 = proc.transfer(np.random.normal(size=1000)) -d2 = proc.transfer(np.random.normal(size=1000)) -rpg = proc._import('pyqtgraph') -plt = rpg.plot(d1+d2) - - -## Start Qt event loop unless running in interactive mode or using pyside. -#import sys -#if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - #QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/optics/__init__.py b/pyqtgraph/examples/optics/__init__.py deleted file mode 100644 index b3d31cd..0000000 --- a/pyqtgraph/examples/optics/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .pyoptic import * \ No newline at end of file diff --git a/pyqtgraph/examples/optics/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/examples/optics/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index de98329..0000000 Binary files a/pyqtgraph/examples/optics/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/optics/__pycache__/pyoptic.cpython-36.pyc b/pyqtgraph/examples/optics/__pycache__/pyoptic.cpython-36.pyc deleted file mode 100644 index add014c..0000000 Binary files a/pyqtgraph/examples/optics/__pycache__/pyoptic.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/optics/pyoptic.py b/pyqtgraph/examples/optics/pyoptic.py deleted file mode 100644 index f70edd2..0000000 --- a/pyqtgraph/examples/optics/pyoptic.py +++ /dev/null @@ -1,561 +0,0 @@ -# -*- coding: utf-8 -*- -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import csv, gzip, os -from pyqtgraph import Point - -class GlassDB: - """ - Database of dispersion coefficients for Schott glasses - + Corning 7980 - """ - def __init__(self, fileName='schott_glasses.csv'): - path = os.path.dirname(__file__) - fh = gzip.open(os.path.join(path, 'schott_glasses.csv.gz'), 'rb') - r = csv.reader(map(str, fh.readlines())) - lines = [x for x in r] - self.data = {} - header = lines[0] - for l in lines[1:]: - info = {} - for i in range(1, len(l)): - info[header[i]] = l[i] - self.data[l[0]] = info - self.data['Corning7980'] = { ## Thorlabs UV fused silica--not in schott catalog. - 'B1': 0.68374049400, - 'B2': 0.42032361300, - 'B3': 0.58502748000, - 'C1': 0.00460352869, - 'C2': 0.01339688560, - 'C3': 64.49327320000, - 'TAUI25/250': 0.95, ## transmission data is fabricated, but close. - 'TAUI25/1400': 0.98, - } - - for k in self.data: - self.data[k]['ior_cache'] = {} - - - def ior(self, glass, wl): - """ - Return the index of refraction for *glass* at wavelength *wl*. - - The *glass* argument must be a key in self.data. - """ - info = self.data[glass] - cache = info['ior_cache'] - if wl not in cache: - B = list(map(float, [info['B1'], info['B2'], info['B3']])) - C = list(map(float, [info['C1'], info['C2'], info['C3']])) - w2 = (wl/1000.)**2 - n = np.sqrt(1.0 + (B[0]*w2 / (w2-C[0])) + (B[1]*w2 / (w2-C[1])) + (B[2]*w2 / (w2-C[2]))) - cache[wl] = n - return cache[wl] - - def transmissionCurve(self, glass): - data = self.data[glass] - keys = [int(x[7:]) for x in data.keys() if 'TAUI25' in x] - keys.sort() - curve = np.empty((2,len(keys))) - for i in range(len(keys)): - curve[0][i] = keys[i] - key = 'TAUI25/%d' % keys[i] - val = data[key] - if val == '': - val = 0 - else: - val = float(val) - curve[1][i] = val - return curve - - -GLASSDB = GlassDB() - - -def wlPen(wl): - """Return a pen representing the given wavelength""" - l1 = 400 - l2 = 700 - hue = np.clip(((l2-l1) - (wl-l1)) * 0.8 / (l2-l1), 0, 0.8) - val = 1.0 - if wl > 700: - val = 1.0 * (((700-wl)/700.) + 1) - elif wl < 400: - val = wl * 1.0/400. - #print hue, val - color = pg.hsvColor(hue, 1.0, val) - pen = pg.mkPen(color) - return pen - - -class ParamObj(object): - # Just a helper for tracking parameters and responding to changes - def __init__(self): - self.__params = {} - - def __setitem__(self, item, val): - self.setParam(item, val) - - def setParam(self, param, val): - self.setParams(**{param:val}) - - def setParams(self, **params): - """Set parameters for this optic. This is a good function to override for subclasses.""" - self.__params.update(params) - self.paramStateChanged() - - def paramStateChanged(self): - pass - - def __getitem__(self, item): - # bug in pyside 1.2.2 causes getitem to be called inside QGraphicsObject.parentItem: - return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-671 - - def __len__(self): - # Workaround for PySide bug: https://bugreports.qt.io/browse/PYSIDE-671 - return 0 - - def getParam(self, param): - return self.__params[param] - - -class Optic(pg.GraphicsObject, ParamObj): - - sigStateChanged = QtCore.Signal() - - - def __init__(self, gitem, **params): - ParamObj.__init__(self) - pg.GraphicsObject.__init__(self) #, [0,0], [1,1]) - - self.gitem = gitem - self.surfaces = gitem.surfaces - gitem.setParentItem(self) - - self.roi = pg.ROI([0,0], [1,1]) - self.roi.addRotateHandle([1, 1], [0.5, 0.5]) - self.roi.setParentItem(self) - - defaults = { - 'pos': Point(0,0), - 'angle': 0, - } - defaults.update(params) - self._ior_cache = {} - self.roi.sigRegionChanged.connect(self.roiChanged) - self.setParams(**defaults) - - def updateTransform(self): - self.setPos(0, 0) - tr = QtGui.QTransform() - self.setTransform(tr.translate(Point(self['pos'])).rotate(self['angle'])) - - def setParam(self, param, val): - ParamObj.setParam(self, param, val) - - def paramStateChanged(self): - """Some parameters of the optic have changed.""" - # Move graphics item - self.gitem.setPos(Point(self['pos'])) - self.gitem.resetTransform() - self.gitem.setRotation(self['angle']) - - # Move ROI to match - try: - self.roi.sigRegionChanged.disconnect(self.roiChanged) - br = self.gitem.boundingRect() - o = self.gitem.mapToParent(br.topLeft()) - self.roi.setAngle(self['angle']) - self.roi.setPos(o) - self.roi.setSize([br.width(), br.height()]) - finally: - self.roi.sigRegionChanged.connect(self.roiChanged) - - self.sigStateChanged.emit() - - def roiChanged(self, *args): - pos = self.roi.pos() - # rotate gitem temporarily so we can decide where it will need to move - self.gitem.resetTransform() - self.gitem.setRotation(self.roi.angle()) - br = self.gitem.boundingRect() - o1 = self.gitem.mapToParent(br.topLeft()) - self.setParams(angle=self.roi.angle(), pos=pos + (self.gitem.pos() - o1)) - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, p, *args): - pass - - def ior(self, wavelength): - return GLASSDB.ior(self['glass'], wavelength) - - - -class Lens(Optic): - def __init__(self, **params): - defaults = { - 'dia': 25.4, ## diameter of lens - 'r1': 50., ## positive means convex, use 0 for planar - 'r2': 0, ## negative means convex - 'd': 4.0, - 'glass': 'N-BK7', - 'reflect': False, - } - defaults.update(params) - d = defaults.pop('d') - defaults['x1'] = -d/2. - defaults['x2'] = d/2. - - gitem = CircularSolid(brush=(100, 100, 130, 100), **defaults) - Optic.__init__(self, gitem, **defaults) - - def propagateRay(self, ray): - """Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays""" - - """ - NOTE:: We can probably use this to compute refractions faster: (from GLSL 120 docs) - - For the incident vector I and surface normal N, and the - ratio of indices of refraction eta, return the refraction - vector. The result is computed by - k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I)) - if (k < 0.0) - return genType(0.0) - else - return eta * I - (eta * dot(N, I) + sqrt(k)) * N - The input parameters for the incident vector I and the - surface normal N must already be normalized to get the - desired results. eta == ratio of IORs - - - For reflection: - For the incident vector I and surface orientation N, - returns the reflection direction: - I – 2 ∗ dot(N, I) ∗ N - N must already be normalized in order to achieve the - desired result. - """ - iors = [self.ior(ray['wl']), 1.0] - for i in [0,1]: - surface = self.surfaces[i] - ior = iors[i] - p1, ai = surface.intersectRay(ray) - if p1 is None: - ray.setEnd(None) - break - p1 = surface.mapToItem(ray, p1) - - rd = ray['dir'] - a1 = np.arctan2(rd[1], rd[0]) - ar = a1 - ai + np.arcsin((np.sin(ai) * ray['ior'] / ior)) - ray.setEnd(p1) - dp = Point(np.cos(ar), np.sin(ar)) - ray = Ray(parent=ray, ior=ior, dir=dp) - return [ray] - - -class Mirror(Optic): - def __init__(self, **params): - defaults = { - 'r1': 0, - 'r2': 0, - 'd': 0.01, - } - defaults.update(params) - d = defaults.pop('d') - defaults['x1'] = -d/2. - defaults['x2'] = d/2. - gitem = CircularSolid(brush=(100,100,100,255), **defaults) - Optic.__init__(self, gitem, **defaults) - - def propagateRay(self, ray): - """Refract, reflect, absorb, and/or scatter ray. This function may create and return new rays""" - - surface = self.surfaces[0] - p1, ai = surface.intersectRay(ray) - if p1 is not None: - p1 = surface.mapToItem(ray, p1) - rd = ray['dir'] - a1 = np.arctan2(rd[1], rd[0]) - ar = a1 + np.pi - 2*ai - ray.setEnd(p1) - dp = Point(np.cos(ar), np.sin(ar)) - ray = Ray(parent=ray, dir=dp) - else: - ray.setEnd(None) - return [ray] - - -class CircularSolid(pg.GraphicsObject, ParamObj): - """GraphicsObject with two circular or flat surfaces.""" - def __init__(self, pen=None, brush=None, **opts): - """ - Arguments for each surface are: - x1,x2 - position of center of _physical surface_ - r1,r2 - radius of curvature - d1,d2 - diameter of optic - """ - defaults = dict(x1=-2, r1=100, d1=25.4, x2=2, r2=100, d2=25.4) - defaults.update(opts) - ParamObj.__init__(self) - self.surfaces = [CircleSurface(defaults['r1'], defaults['d1']), CircleSurface(-defaults['r2'], defaults['d2'])] - pg.GraphicsObject.__init__(self) - for s in self.surfaces: - s.setParentItem(self) - - if pen is None: - self.pen = pg.mkPen((220,220,255,200), width=1, cosmetic=True) - else: - self.pen = pg.mkPen(pen) - - if brush is None: - self.brush = pg.mkBrush((230, 230, 255, 30)) - else: - self.brush = pg.mkBrush(brush) - - self.setParams(**defaults) - - def paramStateChanged(self): - self.updateSurfaces() - - def updateSurfaces(self): - self.surfaces[0].setParams(self['r1'], self['d1']) - self.surfaces[1].setParams(-self['r2'], self['d2']) - self.surfaces[0].setPos(self['x1'], 0) - self.surfaces[1].setPos(self['x2'], 0) - - self.path = QtGui.QPainterPath() - self.path.connectPath(self.surfaces[0].path.translated(self.surfaces[0].pos())) - self.path.connectPath(self.surfaces[1].path.translated(self.surfaces[1].pos()).toReversed()) - self.path.closeSubpath() - - def boundingRect(self): - return self.path.boundingRect() - - def shape(self): - return self.path - - def paint(self, p, *args): - p.setRenderHints(p.renderHints() | p.Antialiasing) - p.setPen(self.pen) - p.fillPath(self.path, self.brush) - p.drawPath(self.path) - - -class CircleSurface(pg.GraphicsObject): - def __init__(self, radius=None, diameter=None): - """center of physical surface is at 0,0 - radius is the radius of the surface. If radius is None, the surface is flat. - diameter is of the optic's edge.""" - pg.GraphicsObject.__init__(self) - - self.r = radius - self.d = diameter - self.mkPath() - - def setParams(self, r, d): - self.r = r - self.d = d - self.mkPath() - - def mkPath(self): - self.prepareGeometryChange() - r = self.r - d = self.d - h2 = d/2. - self.path = QtGui.QPainterPath() - if r == 0: ## flat surface - self.path.moveTo(0, h2) - self.path.lineTo(0, -h2) - else: - ## half-height of surface can't be larger than radius - h2 = min(h2, abs(r)) - arc = QtCore.QRectF(0, -r, r*2, r*2) - a1 = np.arcsin(h2/r) * 180. / np.pi - a2 = -2*a1 - a1 += 180. - self.path.arcMoveTo(arc, a1) - self.path.arcTo(arc, a1, a2) - self.h2 = h2 - - def boundingRect(self): - return self.path.boundingRect() - - def paint(self, p, *args): - return ## usually we let the optic draw. - - def intersectRay(self, ray): - ## return the point of intersection and the angle of incidence - #print "intersect ray" - h = self.h2 - r = self.r - p, dir = ray.currentState(relativeTo=self) # position and angle of ray in local coords. - #print " ray: ", p, dir - p = p - Point(r, 0) ## move position so center of circle is at 0,0 - #print " adj: ", p, r - - if r == 0: - #print " flat" - if dir[0] == 0: - y = 0 - else: - y = p[1] - p[0] * dir[1]/dir[0] - if abs(y) > h: - return None, None - else: - return (Point(0, y), np.arctan2(dir[1], dir[0])) - else: - #print " curve" - ## find intersection of circle and line (quadratic formula) - dx = dir[0] - dy = dir[1] - dr = (dx**2 + dy**2) ** 0.5 - D = p[0] * (p[1]+dy) - (p[0]+dx) * p[1] - idr2 = 1.0 / dr**2 - disc = r**2 * dr**2 - D**2 - if disc < 0: - return None, None - disc2 = disc**0.5 - if dy < 0: - sgn = -1 - else: - sgn = 1 - - - br = self.path.boundingRect() - x1 = (D*dy + sgn*dx*disc2) * idr2 - y1 = (-D*dx + abs(dy)*disc2) * idr2 - if br.contains(x1+r, y1): - pt = Point(x1, y1) - else: - x2 = (D*dy - sgn*dx*disc2) * idr2 - y2 = (-D*dx - abs(dy)*disc2) * idr2 - pt = Point(x2, y2) - if not br.contains(x2+r, y2): - return None, None - raise Exception("No intersection!") - - norm = np.arctan2(pt[1], pt[0]) - if r < 0: - norm += np.pi - #print " norm:", norm*180/3.1415 - dp = p - pt - #print " dp:", dp - ang = np.arctan2(dp[1], dp[0]) - #print " ang:", ang*180/3.1415 - #print " ai:", (ang-norm)*180/3.1415 - - #print " intersection:", pt - return pt + Point(r, 0), ang-norm - - -class Ray(pg.GraphicsObject, ParamObj): - """Represents a single straight segment of a ray""" - - sigStateChanged = QtCore.Signal() - - def __init__(self, **params): - ParamObj.__init__(self) - defaults = { - 'ior': 1.0, - 'wl': 500, - 'end': None, - 'dir': Point(1,0), - } - self.params = {} - pg.GraphicsObject.__init__(self) - self.children = [] - parent = params.get('parent', None) - if parent is not None: - defaults['start'] = parent['end'] - defaults['wl'] = parent['wl'] - self['ior'] = parent['ior'] - self['dir'] = parent['dir'] - parent.addChild(self) - - defaults.update(params) - defaults['dir'] = Point(defaults['dir']) - self.setParams(**defaults) - self.mkPath() - - def clearChildren(self): - for c in self.children: - c.clearChildren() - c.setParentItem(None) - self.scene().removeItem(c) - self.children = [] - - def paramStateChanged(self): - pass - - def addChild(self, ch): - self.children.append(ch) - ch.setParentItem(self) - - def currentState(self, relativeTo=None): - pos = self['start'] - dir = self['dir'] - if relativeTo is None: - return pos, dir - else: - trans = self.itemTransform(relativeTo)[0] - p1 = trans.map(pos) - p2 = trans.map(pos + dir) - return Point(p1), Point(p2-p1) - - def setEnd(self, end): - self['end'] = end - self.mkPath() - - def boundingRect(self): - return self.path.boundingRect() - - def paint(self, p, *args): - #p.setPen(pg.mkPen((255,0,0, 150))) - p.setRenderHints(p.renderHints() | p.Antialiasing) - p.setCompositionMode(p.CompositionMode_Plus) - p.setPen(wlPen(self['wl'])) - p.drawPath(self.path) - - def mkPath(self): - self.prepareGeometryChange() - self.path = QtGui.QPainterPath() - self.path.moveTo(self['start']) - if self['end'] is not None: - self.path.lineTo(self['end']) - else: - self.path.lineTo(self['start']+500*self['dir']) - - -def trace(rays, optics): - if len(optics) < 1 or len(rays) < 1: - return - for r in rays: - r.clearChildren() - o = optics[0] - r2 = o.propagateRay(r) - trace(r2, optics[1:]) - - -class Tracer(QtCore.QObject): - """ - Simple ray tracer. - - Initialize with a list of rays and optics; - calling trace() will cause rays to be extended by propagating them through - each optic in sequence. - """ - def __init__(self, rays, optics): - QtCore.QObject.__init__(self) - self.optics = optics - self.rays = rays - for o in self.optics: - o.sigStateChanged.connect(self.trace) - self.trace() - - def trace(self): - trace(self.rays, self.optics) - diff --git a/pyqtgraph/examples/optics/schott_glasses.csv.gz b/pyqtgraph/examples/optics/schott_glasses.csv.gz deleted file mode 100644 index 8df4ae1..0000000 Binary files a/pyqtgraph/examples/optics/schott_glasses.csv.gz and /dev/null differ diff --git a/pyqtgraph/examples/optics_demos.py b/pyqtgraph/examples/optics_demos.py deleted file mode 100644 index 1f17362..0000000 --- a/pyqtgraph/examples/optics_demos.py +++ /dev/null @@ -1,170 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Optical system design demo - - - -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -from optics import * - -import pyqtgraph as pg - -import numpy as np -from pyqtgraph import Point - -app = pg.mkQApp("Optics Demo") - -w = pg.GraphicsLayoutWidget(show=True, border=0.5) -w.resize(1000, 900) -w.show() - - - -### Curved mirror demo - -view = w.addViewBox() -view.setAspectLocked() -#grid = pg.GridItem() -#view.addItem(grid) -view.setRange(pg.QtCore.QRectF(-50, -30, 100, 100)) - -optics = [] -rays = [] -m1 = Mirror(r1=-100, pos=(5,0), d=5, angle=-15) -optics.append(m1) -m2 = Mirror(r1=-70, pos=(-40, 30), d=6, angle=180-15) -optics.append(m2) - -allRays = [] -for y in np.linspace(-10, 10, 21): - r = Ray(start=Point(-100, y)) - view.addItem(r) - allRays.append(r) - -for o in optics: - view.addItem(o) - -t1 = Tracer(allRays, optics) - - - -### Dispersion demo - -optics = [] - -view = w.addViewBox() - -view.setAspectLocked() -#grid = pg.GridItem() -#view.addItem(grid) -view.setRange(pg.QtCore.QRectF(-10, -50, 90, 60)) - -optics = [] -rays = [] -l1 = Lens(r1=20, r2=20, d=10, angle=8, glass='Corning7980') -optics.append(l1) - -allRays = [] -for wl in np.linspace(355,1040, 25): - for y in [10]: - r = Ray(start=Point(-100, y), wl=wl) - view.addItem(r) - allRays.append(r) - -for o in optics: - view.addItem(o) - -t2 = Tracer(allRays, optics) - - - -### Scanning laser microscopy demo - -w.nextRow() -view = w.addViewBox(colspan=2) - -optics = [] - - -#view.setAspectLocked() -view.setRange(QtCore.QRectF(200, -50, 500, 200)) - - - -## Scan mirrors -scanx = 250 -scany = 20 -m1 = Mirror(dia=4.2, d=0.001, pos=(scanx, 0), angle=315) -m2 = Mirror(dia=8.4, d=0.001, pos=(scanx, scany), angle=135) - -## Scan lenses -l3 = Lens(r1=23.0, r2=0, d=5.8, pos=(scanx+50, scany), glass='Corning7980') ## 50mm UVFS (LA4148) -l4 = Lens(r1=0, r2=69.0, d=3.2, pos=(scanx+250, scany), glass='Corning7980') ## 150mm UVFS (LA4874) - -## Objective -obj = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, scany), glass='Corning7980') - -IROptics = [m1, m2, l3, l4, obj] - - - -## Scan mirrors -scanx = 250 -scany = 30 -m1a = Mirror(dia=4.2, d=0.001, pos=(scanx, 2*scany), angle=315) -m2a = Mirror(dia=8.4, d=0.001, pos=(scanx, 3*scany), angle=135) - -## Scan lenses -l3a = Lens(r1=46, r2=0, d=3.8, pos=(scanx+50, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380) -l4a = Lens(r1=0, r2=46, d=3.8, pos=(scanx+250, 3*scany), glass='Corning7980') ## 100mm UVFS (LA4380) - -## Objective -obja = Lens(r1=15, r2=15, d=10, dia=8, pos=(scanx+400, 3*scany), glass='Corning7980') - -IROptics2 = [m1a, m2a, l3a, l4a, obja] - - - -for o in set(IROptics+IROptics2): - view.addItem(o) - -IRRays = [] -IRRays2 = [] - -for dy in [-0.4, -0.15, 0, 0.15, 0.4]: - IRRays.append(Ray(start=Point(-50, dy), dir=(1, 0), wl=780)) - IRRays2.append(Ray(start=Point(-50, dy+2*scany), dir=(1, 0), wl=780)) - -for r in set(IRRays+IRRays2): - view.addItem(r) - -IRTracer = Tracer(IRRays, IROptics) -IRTracer2 = Tracer(IRRays2, IROptics2) - -phase = 0.0 -def update(): - global phase - if phase % (8*np.pi) > 4*np.pi: - m1['angle'] = 315 + 1.5*np.sin(phase) - m1a['angle'] = 315 + 1.5*np.sin(phase) - else: - m2['angle'] = 135 + 1.5*np.sin(phase) - m2a['angle'] = 135 + 1.5*np.sin(phase) - phase += 0.2 - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(40) - - - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/parallelize.py b/pyqtgraph/examples/parallelize.py deleted file mode 100644 index b309aa3..0000000 --- a/pyqtgraph/examples/parallelize.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -import initExample ## Add path to library (just for examples; you do not need this) - -import time -import numpy as np -import pyqtgraph.multiprocess as mp -import pyqtgraph as pg -from pyqtgraph.python2_3 import xrange - -print( "\n=================\nParallelize") - -## Do a simple task: -## for x in range(N): -## sum([x*i for i in range(M)]) -## -## We'll do this three times -## - once without Parallelize -## - once with Parallelize, but forced to use a single worker -## - once with Parallelize automatically determining how many workers to use -## - -tasks = range(10) -results = [None] * len(tasks) -results2 = results[:] -results3 = results[:] -size = 2000000 - -pg.mkQApp() - -### Purely serial processing -start = time.time() -with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg: - for i, x in enumerate(tasks): - tot = 0 - for j in xrange(size): - tot += j * x - results[i] = tot - dlg += 1 - if dlg.wasCanceled(): - raise Exception('processing canceled') -print( "Serial time: %0.2f" % (time.time() - start)) - -### Use parallelize, but force a single worker -### (this simulates the behavior seen on windows, which lacks os.fork) -start = time.time() -with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialog='processing serially (using Parallelizer)..') as tasker: - for i, x in tasker: - tot = 0 - for j in xrange(size): - tot += j * x - tasker.results[i] = tot -print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start)) -print( "Results match serial: %s" % str(results2 == results)) - -### Use parallelize with multiple workers -start = time.time() -with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processing in parallel..') as tasker: - for i, x in tasker: - tot = 0 - for j in xrange(size): - tot += j * x - tasker.results[i] = tot -print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start)) -print( "Results match serial: %s" % str(results3 == results)) - diff --git a/pyqtgraph/examples/parametertree.py b/pyqtgraph/examples/parametertree.py deleted file mode 100644 index fdd1bda..0000000 --- a/pyqtgraph/examples/parametertree.py +++ /dev/null @@ -1,186 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example demonstrates the use of pyqtgraph's parametertree system. This provides -a simple way to generate user interfaces that control sets of parameters. The example -demonstrates a variety of different parameter types (int, float, list, etc.) -as well as some customized parameter types - -""" - - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - - -app = pg.mkQApp("Parameter Tree Example") -import pyqtgraph.parametertree.parameterTypes as pTypes -from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType - - -## test subclassing parameters -## This parameter automatically generates two child parameters which are always reciprocals of each other -class ComplexParameter(pTypes.GroupParameter): - def __init__(self, **opts): - opts['type'] = 'bool' - opts['value'] = True - pTypes.GroupParameter.__init__(self, **opts) - - self.addChild({'name': 'A = 1/B', 'type': 'float', 'value': 7, 'suffix': 'Hz', 'siPrefix': True}) - self.addChild({'name': 'B = 1/A', 'type': 'float', 'value': 1/7., 'suffix': 's', 'siPrefix': True}) - self.a = self.param('A = 1/B') - self.b = self.param('B = 1/A') - self.a.sigValueChanged.connect(self.aChanged) - self.b.sigValueChanged.connect(self.bChanged) - - def aChanged(self): - self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged) - - def bChanged(self): - self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged) - - -## test add/remove -## this group includes a menu allowing the user to add new parameters into its child list -class ScalableGroup(pTypes.GroupParameter): - def __init__(self, **opts): - opts['type'] = 'group' - opts['addText'] = "Add" - opts['addList'] = ['str', 'float', 'int'] - pTypes.GroupParameter.__init__(self, **opts) - - def addNew(self, typ): - val = { - 'str': '', - 'float': 0.0, - 'int': 0 - }[typ] - self.addChild(dict(name="ScalableParam %d" % (len(self.childs)+1), type=typ, value=val, removable=True, renamable=True)) - - - - -params = [ - {'name': 'Basic parameter data types', 'type': 'group', 'children': [ - {'name': 'Integer', 'type': 'int', 'value': 10}, - {'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False}, - {'name': 'String', 'type': 'str', 'value': "hi", 'tip': 'Well hello'}, - {'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2}, - {'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2}, - {'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"}, - {'name': 'Color', 'type': 'color', 'value': "FF0", 'tip': "This is a color button"}, - {'name': 'Gradient', 'type': 'colormap'}, - {'name': 'Subgroup', 'type': 'group', 'children': [ - {'name': 'Sub-param 1', 'type': 'int', 'value': 10}, - {'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6}, - ]}, - {'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'}, - {'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'}, - ]}, - {'name': 'Numerical Parameter Options', 'type': 'group', 'children': [ - {'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'}, - {'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6}, - {'name': 'Int suffix', 'type': 'int', 'value': 9, 'suffix': 'V'}, - {'name': 'DEC stepping', 'type': 'float', 'value': 1.2e6, 'dec': True, 'step': 1, 'siPrefix': True, 'suffix': 'Hz'}, - - ]}, - {'name': 'Save/Restore functionality', 'type': 'group', 'children': [ - {'name': 'Save State', 'type': 'action'}, - {'name': 'Restore State', 'type': 'action', 'children': [ - {'name': 'Add missing items', 'type': 'bool', 'value': True}, - {'name': 'Remove extra items', 'type': 'bool', 'value': True}, - ]}, - ]}, - {'name': 'Extra Parameter Options', 'type': 'group', 'children': [ - {'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True}, - {'name': 'Disabled', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'enabled': False}, - {'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True}, - {'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True}, - ]}, - {'name': 'Custom context menu', 'type': 'group', 'children': [ - {'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [ - 'menu1', - 'menu2' - ]}, - {'name': 'Dict contextMenu', 'type': 'float', 'value': 0, 'context': { - 'changeName': 'Title', - 'internal': 'What the user sees', - }}, - ]}, - ComplexParameter(name='Custom parameter group (reciprocal values)'), - ScalableGroup(name="Expandable Parameter Group", tip='Click to add children', children=[ - {'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"}, - {'name': 'ScalableParam 2', 'type': 'str', 'value': "default param 2"}, - ]), -] - -## Create tree of Parameter objects -p = Parameter.create(name='params', type='group', children=params) - -## If anything changes in the tree, print a message -def change(param, changes): - print("tree changes:") - for param, change, data in changes: - path = p.childPath(param) - if path is not None: - childName = '.'.join(path) - else: - childName = param.name() - print(' parameter: %s'% childName) - print(' change: %s'% change) - print(' data: %s'% str(data)) - print(' ----------') - -p.sigTreeStateChanged.connect(change) - - -def valueChanging(param, value): - print("Value changing (not finalized): %s %s" % (param, value)) - -# Too lazy for recursion: -for child in p.children(): - child.sigValueChanging.connect(valueChanging) - for ch2 in child.children(): - ch2.sigValueChanging.connect(valueChanging) - - - -def save(): - global state - state = p.saveState() - -def restore(): - global state - add = p['Save/Restore functionality', 'Restore State', 'Add missing items'] - rem = p['Save/Restore functionality', 'Restore State', 'Remove extra items'] - p.restoreState(state, addChildren=add, removeChildren=rem) -p.param('Save/Restore functionality', 'Save State').sigActivated.connect(save) -p.param('Save/Restore functionality', 'Restore State').sigActivated.connect(restore) - - -## Create two ParameterTree widgets, both accessing the same data -t = ParameterTree() -t.setParameters(p, showTop=False) -t.setWindowTitle('pyqtgraph example: Parameter Tree') -t2 = ParameterTree() -t2.setParameters(p, showTop=False) - -win = QtGui.QWidget() -layout = QtGui.QGridLayout() -win.setLayout(layout) -layout.addWidget(QtGui.QLabel("These are two views of the same data. They should always display the same values."), 0, 0, 1, 2) -layout.addWidget(t, 1, 0, 1, 1) -layout.addWidget(t2, 1, 1, 1, 1) -win.show() - -## test save/restore -s = p.saveState() -p.restoreState(s) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/relativity/__init__.py b/pyqtgraph/examples/relativity/__init__.py deleted file mode 100644 index f1acf52..0000000 --- a/pyqtgraph/examples/relativity/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .relativity import * diff --git a/pyqtgraph/examples/relativity/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/examples/relativity/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index b59a30d..0000000 Binary files a/pyqtgraph/examples/relativity/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/relativity/__pycache__/relativity.cpython-36.pyc b/pyqtgraph/examples/relativity/__pycache__/relativity.cpython-36.pyc deleted file mode 100644 index d5788c3..0000000 Binary files a/pyqtgraph/examples/relativity/__pycache__/relativity.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/relativity/presets/Grid Expansion.cfg b/pyqtgraph/examples/relativity/presets/Grid Expansion.cfg deleted file mode 100644 index 0ab7779..0000000 --- a/pyqtgraph/examples/relativity/presets/Grid Expansion.cfg +++ /dev/null @@ -1,411 +0,0 @@ -name: 'params' -strictNaming: False -default: None -renamable: False -enabled: True -value: None -visible: True -readonly: False -removable: False -type: 'group' -children: - Load Preset..: - name: 'Load Preset..' - limits: ['', 'Twin Paradox (grid)', 'Twin Paradox'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Twin Paradox (grid)' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Duration: - name: 'Duration' - limits: [0.1, None] - strictNaming: False - default: 10.0 - renamable: False - enabled: True - readonly: False - value: 20.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Reference Frame: - name: 'Reference Frame' - limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Grid02' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Animate: - name: 'Animate' - strictNaming: False - default: True - renamable: False - enabled: True - value: True - visible: True - readonly: False - removable: False - type: 'bool' - children: - Animation Speed: - name: 'Animation Speed' - limits: [0.0001, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - dec: True - type: 'float' - children: - Recalculate Worldlines: - name: 'Recalculate Worldlines' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Save: - name: 'Save' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Load: - name: 'Load' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Objects: - name: 'Objects' - strictNaming: False - default: None - renamable: False - addText: 'Add New..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: None - children: - Grid: - name: 'Grid' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Grid' - autoIncrementName: True - children: - Number of Clocks: - name: 'Number of Clocks' - limits: [1, None] - strictNaming: False - default: 5 - renamable: False - enabled: True - value: 5 - visible: True - readonly: False - removable: False - type: 'int' - children: - Spacing: - name: 'Spacing' - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - ClockTemplate: - name: 'ClockTemplate' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -2.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Command: - name: 'Command' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - value: 1.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command2: - name: 'Command2' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 2.0 - renamable: False - enabled: True - value: 3.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command3: - name: 'Command3' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 4.0 - renamable: False - enabled: True - value: 11.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command4: - name: 'Command4' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 8.0 - renamable: False - enabled: True - value: 13.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (100, 100, 150, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 0.5 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - addList: ['Clock', 'Grid'] diff --git a/pyqtgraph/examples/relativity/presets/Twin Paradox (grid).cfg b/pyqtgraph/examples/relativity/presets/Twin Paradox (grid).cfg deleted file mode 100644 index ebe366b..0000000 --- a/pyqtgraph/examples/relativity/presets/Twin Paradox (grid).cfg +++ /dev/null @@ -1,667 +0,0 @@ -name: 'params' -strictNaming: False -default: None -renamable: False -enabled: True -value: None -visible: True -readonly: False -removable: False -type: 'group' -children: - Load Preset..: - name: 'Load Preset..' - limits: ['', 'Twin Paradox (grid)', 'Twin Paradox'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Twin Paradox (grid)' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Duration: - name: 'Duration' - limits: [0.1, None] - strictNaming: False - default: 10.0 - renamable: False - enabled: True - readonly: False - value: 27.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Reference Frame: - name: 'Reference Frame' - limits: ['Grid00', 'Grid01', 'Grid02', 'Grid03', 'Grid04', 'Grid05', 'Grid06', 'Grid07', 'Grid08', 'Grid09', 'Grid10', 'Alice', 'Bob'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Alice' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Animate: - name: 'Animate' - strictNaming: False - default: True - renamable: False - enabled: True - value: True - visible: True - readonly: False - removable: False - type: 'bool' - children: - Animation Speed: - name: 'Animation Speed' - limits: [0.0001, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - dec: True - type: 'float' - children: - Recalculate Worldlines: - name: 'Recalculate Worldlines' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Save: - name: 'Save' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Load: - name: 'Load' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Objects: - name: 'Objects' - strictNaming: False - default: None - renamable: False - addText: 'Add New..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: None - children: - Grid: - name: 'Grid' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Grid' - autoIncrementName: True - children: - Number of Clocks: - name: 'Number of Clocks' - limits: [1, None] - strictNaming: False - default: 5 - renamable: False - enabled: True - value: 11 - visible: True - readonly: False - removable: False - type: 'int' - children: - Spacing: - name: 'Spacing' - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 2.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - ClockTemplate: - name: 'ClockTemplate' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -10.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (77, 77, 77, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 1.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -2.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Alice: - name: 'Alice' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Command: - name: 'Command' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - value: 1.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command2: - name: 'Command2' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 2.0 - renamable: False - enabled: True - value: 3.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command3: - name: 'Command3' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 3.0 - renamable: False - enabled: True - value: 8.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command4: - name: 'Command4' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 4.0 - renamable: False - enabled: True - value: 12.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command5: - name: 'Command5' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 6.0 - renamable: False - enabled: True - value: 17.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command6: - name: 'Command6' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 7.0 - renamable: False - enabled: True - value: 19.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (82, 123, 44, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 1.5 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 3.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Bob: - name: 'Bob' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (69, 69, 126, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 1.5 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - addList: ['Clock', 'Grid'] diff --git a/pyqtgraph/examples/relativity/presets/Twin Paradox.cfg b/pyqtgraph/examples/relativity/presets/Twin Paradox.cfg deleted file mode 100644 index 569c3a0..0000000 --- a/pyqtgraph/examples/relativity/presets/Twin Paradox.cfg +++ /dev/null @@ -1,538 +0,0 @@ -name: 'params' -strictNaming: False -default: None -renamable: False -enabled: True -value: None -visible: True -readonly: False -removable: False -type: 'group' -children: - Load Preset..: - name: 'Load Preset..' - limits: ['', 'Twin Paradox', 'test'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Twin Paradox' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Duration: - name: 'Duration' - limits: [0.1, None] - strictNaming: False - default: 10.0 - renamable: False - enabled: True - readonly: False - value: 27.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Reference Frame: - name: 'Reference Frame' - limits: ['Alice', 'Bob'] - strictNaming: False - default: None - renamable: False - enabled: True - value: 'Alice' - visible: True - readonly: False - values: [] - removable: False - type: 'list' - children: - Animate: - name: 'Animate' - strictNaming: False - default: True - renamable: False - enabled: True - value: True - visible: True - readonly: False - removable: False - type: 'bool' - children: - Animation Speed: - name: 'Animation Speed' - limits: [0.0001, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - dec: True - type: 'float' - children: - Recalculate Worldlines: - name: 'Recalculate Worldlines' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Save: - name: 'Save' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Load: - name: 'Load' - strictNaming: False - default: None - renamable: False - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'action' - children: - Objects: - name: 'Objects' - strictNaming: False - default: None - renamable: False - addText: 'Add New..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: None - children: - Alice: - name: 'Alice' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Command: - name: 'Command' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - value: 1.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command2: - name: 'Command2' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 2.0 - renamable: False - enabled: True - value: 3.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command3: - name: 'Command3' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 3.0 - renamable: False - enabled: True - value: 8.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: -0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command4: - name: 'Command4' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 4.0 - renamable: False - enabled: True - value: 12.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command5: - name: 'Command5' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 6.0 - renamable: False - enabled: True - value: 17.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Command6: - name: 'Command6' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: None - autoIncrementName: True - children: - Proper Time: - name: 'Proper Time' - strictNaming: False - default: 7.0 - renamable: False - enabled: True - value: 19.0 - visible: True - readonly: False - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (82, 123, 44, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 0.5 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.5 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Bob: - name: 'Bob' - strictNaming: False - default: None - renamable: True - enabled: True - value: None - visible: True - readonly: False - removable: True - type: 'Clock' - autoIncrementName: True - children: - Initial Position: - name: 'Initial Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Acceleration: - name: 'Acceleration' - strictNaming: False - default: None - renamable: False - addText: 'Add Command..' - enabled: True - value: None - visible: True - readonly: False - removable: False - type: 'AccelerationGroup' - children: - Rest Mass: - name: 'Rest Mass' - limits: [1e-09, None] - strictNaming: False - default: 1.0 - renamable: False - enabled: True - readonly: False - value: 1.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - Color: - name: 'Color' - strictNaming: False - default: (100, 100, 150) - renamable: False - enabled: True - value: (69, 69, 126, 255) - visible: True - readonly: False - removable: False - type: 'color' - children: - Size: - name: 'Size' - strictNaming: False - default: 0.5 - renamable: False - enabled: True - value: 0.5 - visible: True - readonly: False - removable: False - type: 'float' - children: - Vertical Position: - name: 'Vertical Position' - strictNaming: False - default: 0.0 - renamable: False - enabled: True - readonly: False - value: 0.0 - visible: True - step: 0.1 - removable: False - type: 'float' - children: - addList: ['Clock', 'Grid'] diff --git a/pyqtgraph/examples/relativity/relativity.py b/pyqtgraph/examples/relativity/relativity.py deleted file mode 100644 index 64a6711..0000000 --- a/pyqtgraph/examples/relativity/relativity.py +++ /dev/null @@ -1,768 +0,0 @@ -import numpy as np -import collections -import sys, os -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.parametertree import Parameter, ParameterTree -from pyqtgraph.parametertree import types as pTypes -import pyqtgraph.configfile -from pyqtgraph.python2_3 import xrange - - -class RelativityGUI(QtGui.QWidget): - def __init__(self): - QtGui.QWidget.__init__(self) - - self.animations = [] - self.animTimer = QtCore.QTimer() - self.animTimer.timeout.connect(self.stepAnimation) - self.animTime = 0 - self.animDt = .016 - self.lastAnimTime = 0 - - self.setupGUI() - - self.objectGroup = ObjectGroupParam() - - self.params = Parameter.create(name='params', type='group', children=[ - dict(name='Load Preset..', type='list', values=[]), - #dict(name='Unit System', type='list', values=['', 'MKS']), - dict(name='Duration', type='float', value=10.0, step=0.1, limits=[0.1, None]), - dict(name='Reference Frame', type='list', values=[]), - dict(name='Animate', type='bool', value=True), - dict(name='Animation Speed', type='float', value=1.0, dec=True, step=0.1, limits=[0.0001, None]), - dict(name='Recalculate Worldlines', type='action'), - dict(name='Save', type='action'), - dict(name='Load', type='action'), - self.objectGroup, - ]) - self.tree.setParameters(self.params, showTop=False) - self.params.param('Recalculate Worldlines').sigActivated.connect(self.recalculate) - self.params.param('Save').sigActivated.connect(self.save) - self.params.param('Load').sigActivated.connect(self.load) - self.params.param('Load Preset..').sigValueChanged.connect(self.loadPreset) - self.params.sigTreeStateChanged.connect(self.treeChanged) - - ## read list of preset configs - presetDir = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'presets') - if os.path.exists(presetDir): - presets = [os.path.splitext(p)[0] for p in os.listdir(presetDir)] - self.params.param('Load Preset..').setLimits(['']+presets) - - - - - def setupGUI(self): - self.layout = QtGui.QVBoxLayout() - self.layout.setContentsMargins(0,0,0,0) - self.setLayout(self.layout) - self.splitter = QtGui.QSplitter() - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.layout.addWidget(self.splitter) - - self.tree = ParameterTree(showHeader=False) - self.splitter.addWidget(self.tree) - - self.splitter2 = QtGui.QSplitter() - self.splitter2.setOrientation(QtCore.Qt.Vertical) - self.splitter.addWidget(self.splitter2) - - self.worldlinePlots = pg.GraphicsLayoutWidget() - self.splitter2.addWidget(self.worldlinePlots) - - self.animationPlots = pg.GraphicsLayoutWidget() - self.splitter2.addWidget(self.animationPlots) - - self.splitter2.setSizes([int(self.height()*0.8), int(self.height()*0.2)]) - - self.inertWorldlinePlot = self.worldlinePlots.addPlot() - self.refWorldlinePlot = self.worldlinePlots.addPlot() - - self.inertAnimationPlot = self.animationPlots.addPlot() - self.inertAnimationPlot.setAspectLocked(1) - self.refAnimationPlot = self.animationPlots.addPlot() - self.refAnimationPlot.setAspectLocked(1) - - self.inertAnimationPlot.setXLink(self.inertWorldlinePlot) - self.refAnimationPlot.setXLink(self.refWorldlinePlot) - - def recalculate(self): - ## build 2 sets of clocks - clocks1 = collections.OrderedDict() - clocks2 = collections.OrderedDict() - for cl in self.params.param('Objects'): - clocks1.update(cl.buildClocks()) - clocks2.update(cl.buildClocks()) - - ## Inertial simulation - dt = self.animDt * self.params['Animation Speed'] - sim1 = Simulation(clocks1, ref=None, duration=self.params['Duration'], dt=dt) - sim1.run() - sim1.plot(self.inertWorldlinePlot) - self.inertWorldlinePlot.autoRange(padding=0.1) - - ## reference simulation - ref = self.params['Reference Frame'] - dur = clocks1[ref].refData['pt'][-1] ## decide how long to run the reference simulation - sim2 = Simulation(clocks2, ref=clocks2[ref], duration=dur, dt=dt) - sim2.run() - sim2.plot(self.refWorldlinePlot) - self.refWorldlinePlot.autoRange(padding=0.1) - - - ## create animations - self.refAnimationPlot.clear() - self.inertAnimationPlot.clear() - self.animTime = 0 - - self.animations = [Animation(sim1), Animation(sim2)] - self.inertAnimationPlot.addItem(self.animations[0]) - self.refAnimationPlot.addItem(self.animations[1]) - - ## create lines representing all that is visible to a particular reference - #self.inertSpaceline = Spaceline(sim1, ref) - #self.refSpaceline = Spaceline(sim2) - self.inertWorldlinePlot.addItem(self.animations[0].items[ref].spaceline()) - self.refWorldlinePlot.addItem(self.animations[1].items[ref].spaceline()) - - - - - def setAnimation(self, a): - if a: - self.lastAnimTime = pg.ptime.time() - self.animTimer.start(int(self.animDt*1000)) - else: - self.animTimer.stop() - - def stepAnimation(self): - now = pg.ptime.time() - dt = (now-self.lastAnimTime) * self.params['Animation Speed'] - self.lastAnimTime = now - self.animTime += dt - if self.animTime > self.params['Duration']: - self.animTime = 0 - for a in self.animations: - a.restart() - - for a in self.animations: - a.stepTo(self.animTime) - - - def treeChanged(self, *args): - clocks = [] - for c in self.params.param('Objects'): - clocks.extend(c.clockNames()) - #for param, change, data in args[1]: - #if change == 'childAdded': - self.params.param('Reference Frame').setLimits(clocks) - self.setAnimation(self.params['Animate']) - - def save(self): - filename = pg.QtGui.QFileDialog.getSaveFileName(self, "Save State..", "untitled.cfg", "Config Files (*.cfg)") - if isinstance(filename, tuple): - filename = filename[0] # Qt4/5 API difference - if filename == '': - return - state = self.params.saveState() - pg.configfile.writeConfigFile(state, str(filename)) - - def load(self): - filename = pg.QtGui.QFileDialog.getOpenFileName(self, "Save State..", "", "Config Files (*.cfg)") - if isinstance(filename, tuple): - filename = filename[0] # Qt4/5 API difference - if filename == '': - return - state = pg.configfile.readConfigFile(str(filename)) - self.loadState(state) - - def loadPreset(self, param, preset): - if preset == '': - return - path = os.path.abspath(os.path.dirname(__file__)) - fn = os.path.join(path, 'presets', preset+".cfg") - state = pg.configfile.readConfigFile(fn) - self.loadState(state) - - def loadState(self, state): - if 'Load Preset..' in state['children']: - del state['children']['Load Preset..']['limits'] - del state['children']['Load Preset..']['value'] - self.params.param('Objects').clearChildren() - self.params.restoreState(state, removeChildren=False) - self.recalculate() - - -class ObjectGroupParam(pTypes.GroupParameter): - def __init__(self): - pTypes.GroupParameter.__init__(self, name="Objects", addText="Add New..", addList=['Clock', 'Grid']) - - def addNew(self, typ): - if typ == 'Clock': - self.addChild(ClockParam()) - elif typ == 'Grid': - self.addChild(GridParam()) - -class ClockParam(pTypes.GroupParameter): - def __init__(self, **kwds): - defs = dict(name="Clock", autoIncrementName=True, renamable=True, removable=True, children=[ - dict(name='Initial Position', type='float', value=0.0, step=0.1), - #dict(name='V0', type='float', value=0.0, step=0.1), - AccelerationGroup(), - - dict(name='Rest Mass', type='float', value=1.0, step=0.1, limits=[1e-9, None]), - dict(name='Color', type='color', value=(100,100,150)), - dict(name='Size', type='float', value=0.5), - dict(name='Vertical Position', type='float', value=0.0, step=0.1), - ]) - #defs.update(kwds) - pTypes.GroupParameter.__init__(self, **defs) - self.restoreState(kwds, removeChildren=False) - - def buildClocks(self): - x0 = self['Initial Position'] - y0 = self['Vertical Position'] - color = self['Color'] - m = self['Rest Mass'] - size = self['Size'] - prog = self.param('Acceleration').generate() - c = Clock(x0=x0, m0=m, y0=y0, color=color, prog=prog, size=size) - return {self.name(): c} - - def clockNames(self): - return [self.name()] - -pTypes.registerParameterType('Clock', ClockParam) - -class GridParam(pTypes.GroupParameter): - def __init__(self, **kwds): - defs = dict(name="Grid", autoIncrementName=True, renamable=True, removable=True, children=[ - dict(name='Number of Clocks', type='int', value=5, limits=[1, None]), - dict(name='Spacing', type='float', value=1.0, step=0.1), - ClockParam(name='ClockTemplate'), - ]) - #defs.update(kwds) - pTypes.GroupParameter.__init__(self, **defs) - self.restoreState(kwds, removeChildren=False) - - def buildClocks(self): - clocks = {} - template = self.param('ClockTemplate') - spacing = self['Spacing'] - for i in range(self['Number of Clocks']): - c = list(template.buildClocks().values())[0] - c.x0 += i * spacing - clocks[self.name() + '%02d' % i] = c - return clocks - - def clockNames(self): - return [self.name() + '%02d' % i for i in range(self['Number of Clocks'])] - -pTypes.registerParameterType('Grid', GridParam) - -class AccelerationGroup(pTypes.GroupParameter): - def __init__(self, **kwds): - defs = dict(name="Acceleration", addText="Add Command..") - pTypes.GroupParameter.__init__(self, **defs) - self.restoreState(kwds, removeChildren=False) - - def addNew(self): - nextTime = 0.0 - if self.hasChildren(): - nextTime = self.children()[-1]['Proper Time'] + 1 - self.addChild(Parameter.create(name='Command', autoIncrementName=True, type=None, renamable=True, removable=True, children=[ - dict(name='Proper Time', type='float', value=nextTime), - dict(name='Acceleration', type='float', value=0.0, step=0.1), - ])) - - def generate(self): - prog = [] - for cmd in self: - prog.append((cmd['Proper Time'], cmd['Acceleration'])) - return prog - -pTypes.registerParameterType('AccelerationGroup', AccelerationGroup) - - -class Clock(object): - nClocks = 0 - - def __init__(self, x0=0.0, y0=0.0, m0=1.0, v0=0.0, t0=0.0, color=None, prog=None, size=0.5): - Clock.nClocks += 1 - self.pen = pg.mkPen(color) - self.brush = pg.mkBrush(color) - self.y0 = y0 - self.x0 = x0 - self.v0 = v0 - self.m0 = m0 - self.t0 = t0 - self.prog = prog - self.size = size - - def init(self, nPts): - ## Keep records of object from inertial frame as well as reference frame - self.inertData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)]) - self.refData = np.empty(nPts, dtype=[('x', float), ('t', float), ('v', float), ('pt', float), ('m', float), ('f', float)]) - - ## Inertial frame variables - self.x = self.x0 - self.v = self.v0 - self.m = self.m0 - self.t = 0.0 ## reference clock always starts at 0 - self.pt = self.t0 ## proper time starts at t0 - - ## reference frame variables - self.refx = None - self.refv = None - self.refm = None - self.reft = None - - self.recordFrame(0) - - def recordFrame(self, i): - f = self.force() - self.inertData[i] = (self.x, self.t, self.v, self.pt, self.m, f) - self.refData[i] = (self.refx, self.reft, self.refv, self.pt, self.refm, f) - - def force(self, t=None): - if len(self.prog) == 0: - return 0.0 - if t is None: - t = self.pt - - ret = 0.0 - for t1,f in self.prog: - if t >= t1: - ret = f - return ret - - def acceleration(self, t=None): - return self.force(t) / self.m0 - - def accelLimits(self): - ## return the proper time values which bound the current acceleration command - if len(self.prog) == 0: - return -np.inf, np.inf - t = self.pt - ind = -1 - for i, v in enumerate(self.prog): - t1,f = v - if t >= t1: - ind = i - - if ind == -1: - return -np.inf, self.prog[0][0] - elif ind == len(self.prog)-1: - return self.prog[-1][0], np.inf - else: - return self.prog[ind][0], self.prog[ind+1][0] - - - def getCurve(self, ref=True): - - if ref is False: - data = self.inertData - else: - data = self.refData[1:] - - x = data['x'] - y = data['t'] - - curve = pg.PlotCurveItem(x=x, y=y, pen=self.pen) - #x = self.data['x'] - ref.data['x'] - #y = self.data['t'] - - step = 1.0 - #mod = self.data['pt'] % step - #inds = np.argwhere(abs(mod[1:] - mod[:-1]) > step*0.9) - inds = [0] - pt = data['pt'] - for i in range(1,len(pt)): - diff = pt[i] - pt[inds[-1]] - if abs(diff) >= step: - inds.append(i) - inds = np.array(inds) - - #t = self.data['t'][inds] - #x = self.data['x'][inds] - pts = [] - for i in inds: - x = data['x'][i] - y = data['t'][i] - if i+1 < len(data): - dpt = data['pt'][i+1]-data['pt'][i] - dt = data['t'][i+1]-data['t'][i] - else: - dpt = 1 - - if dpt > 0: - c = pg.mkBrush((0,0,0)) - else: - c = pg.mkBrush((200,200,200)) - pts.append({'pos': (x, y), 'brush': c}) - - points = pg.ScatterPlotItem(pts, pen=self.pen, size=7) - - return curve, points - - -class Simulation: - def __init__(self, clocks, ref, duration, dt): - self.clocks = clocks - self.ref = ref - self.duration = duration - self.dt = dt - - @staticmethod - def hypTStep(dt, v0, x0, tau0, g): - ## Hyperbolic step. - ## If an object has proper acceleration g and starts at position x0 with speed v0 and proper time tau0 - ## as seen from an inertial frame, then return the new v, x, tau after time dt has elapsed. - if g == 0: - return v0, x0 + v0*dt, tau0 + dt * (1. - v0**2)**0.5 - v02 = v0**2 - g2 = g**2 - - tinit = v0 / (g * (1 - v02)**0.5) - - B = (1 + (g2 * (dt+tinit)**2))**0.5 - - v1 = g * (dt+tinit) / B - - dtau = (np.arcsinh(g * (dt+tinit)) - np.arcsinh(g * tinit)) / g - - tau1 = tau0 + dtau - - x1 = x0 + (1.0 / g) * ( B - 1. / (1.-v02)**0.5 ) - - return v1, x1, tau1 - - - @staticmethod - def tStep(dt, v0, x0, tau0, g): - ## Linear step. - ## Probably not as accurate as hyperbolic step, but certainly much faster. - gamma = (1. - v0**2)**-0.5 - dtau = dt / gamma - return v0 + dtau * g, x0 + v0*dt, tau0 + dtau - - @staticmethod - def tauStep(dtau, v0, x0, t0, g): - ## linear step in proper time of clock. - ## If an object has proper acceleration g and starts at position x0 with speed v0 at time t0 - ## as seen from an inertial frame, then return the new v, x, t after proper time dtau has elapsed. - - - ## Compute how much t will change given a proper-time step of dtau - gamma = (1. - v0**2)**-0.5 - if g == 0: - dt = dtau * gamma - else: - v0g = v0 * gamma - dt = (np.sinh(dtau * g + np.arcsinh(v0g)) - v0g) / g - - #return v0 + dtau * g, x0 + v0*dt, t0 + dt - v1, x1, t1 = Simulation.hypTStep(dt, v0, x0, t0, g) - return v1, x1, t0+dt - - @staticmethod - def hypIntersect(x0r, t0r, vr, x0, t0, v0, g): - ## given a reference clock (seen from inertial frame) has rx, rt, and rv, - ## and another clock starts at x0, t0, and v0, with acceleration g, - ## compute the intersection time of the object clock's hyperbolic path with - ## the reference plane. - - ## I'm sure we can simplify this... - - if g == 0: ## no acceleration, path is linear (and hyperbola is undefined) - #(-t0r + t0 v0 vr - vr x0 + vr x0r)/(-1 + v0 vr) - - t = (-t0r + t0 *v0 *vr - vr *x0 + vr *x0r)/(-1 + v0 *vr) - return t - - gamma = (1.0-v0**2)**-0.5 - sel = (1 if g>0 else 0) + (1 if vr<0 else 0) - sel = sel%2 - if sel == 0: - #(1/(g^2 (-1 + vr^2)))(-g^2 t0r + g gamma vr + g^2 t0 vr^2 - - #g gamma v0 vr^2 - g^2 vr x0 + - #g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 + - #2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) + - #g^2 (t0 - t0r + vr (-x0 + x0r))^2))) - - t = (1./(g**2 *(-1. + vr**2)))*(-g**2 *t0r + g *gamma *vr + g**2 *t0 *vr**2 - g *gamma *v0 *vr**2 - g**2 *vr *x0 + g**2 *vr *x0r + np.sqrt(g**2 *vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr)* (-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr* (-x0 + x0r))**2))) - - else: - - #-(1/(g^2 (-1 + vr^2)))(g^2 t0r - g gamma vr - g^2 t0 vr^2 + - #g gamma v0 vr^2 + g^2 vr x0 - - #g^2 vr x0r + \[Sqrt](g^2 vr^2 (1 + gamma^2 (v0 - vr)^2 - vr^2 + - #2 g gamma (v0 - vr) (-t0 + t0r + vr (x0 - x0r)) + - #g^2 (t0 - t0r + vr (-x0 + x0r))^2))) - - t = -(1./(g**2 *(-1. + vr**2)))*(g**2 *t0r - g *gamma* vr - g**2 *t0 *vr**2 + g *gamma *v0 *vr**2 + g**2* vr* x0 - g**2 *vr *x0r + np.sqrt(g**2* vr**2 *(1. + gamma**2 *(v0 - vr)**2 - vr**2 + 2 *g *gamma *(v0 - vr) *(-t0 + t0r + vr *(x0 - x0r)) + g**2 *(t0 - t0r + vr *(-x0 + x0r))**2))) - return t - - def run(self): - nPts = int(self.duration/self.dt)+1 - for cl in self.clocks.values(): - cl.init(nPts) - - if self.ref is None: - self.runInertial(nPts) - else: - self.runReference(nPts) - - def runInertial(self, nPts): - clocks = self.clocks - dt = self.dt - tVals = np.linspace(0, dt*(nPts-1), nPts) - for cl in self.clocks.values(): - for i in xrange(1,nPts): - nextT = tVals[i] - while True: - tau1, tau2 = cl.accelLimits() - x = cl.x - v = cl.v - tau = cl.pt - g = cl.acceleration() - - v1, x1, tau1 = self.hypTStep(dt, v, x, tau, g) - if tau1 > tau2: - dtau = tau2-tau - cl.v, cl.x, cl.t = self.tauStep(dtau, v, x, cl.t, g) - cl.pt = tau2 - else: - cl.v, cl.x, cl.pt = v1, x1, tau1 - cl.t += dt - - if cl.t >= nextT: - cl.refx = cl.x - cl.refv = cl.v - cl.reft = cl.t - cl.recordFrame(i) - break - - - def runReference(self, nPts): - clocks = self.clocks - ref = self.ref - dt = self.dt - dur = self.duration - - ## make sure reference clock is not present in the list of clocks--this will be handled separately. - clocks = clocks.copy() - for k,v in clocks.items(): - if v is ref: - del clocks[k] - break - - ref.refx = 0 - ref.refv = 0 - ref.refm = ref.m0 - - ## These are the set of proper times (in the reference frame) that will be simulated - ptVals = np.linspace(ref.pt, ref.pt + dt*(nPts-1), nPts) - - for i in xrange(1,nPts): - - ## step reference clock ahead one time step in its proper time - nextPt = ptVals[i] ## this is where (when) we want to end up - while True: - tau1, tau2 = ref.accelLimits() - dtau = min(nextPt-ref.pt, tau2-ref.pt) ## do not step past the next command boundary - g = ref.acceleration() - v, x, t = Simulation.tauStep(dtau, ref.v, ref.x, ref.t, g) - ref.pt += dtau - ref.v = v - ref.x = x - ref.t = t - ref.reft = ref.pt - if ref.pt >= nextPt: - break - #else: - #print "Stepped to", tau2, "instead of", nextPt - ref.recordFrame(i) - - ## determine plane visible to reference clock - ## this plane goes through the point ref.x, ref.t and has slope = ref.v - - - ## update all other clocks - for cl in clocks.values(): - while True: - g = cl.acceleration() - tau1, tau2 = cl.accelLimits() - ##Given current position / speed of clock, determine where it will intersect reference plane - #t1 = (ref.v * (cl.x - cl.v * cl.t) + (ref.t - ref.v * ref.x)) / (1. - cl.v) - t1 = Simulation.hypIntersect(ref.x, ref.t, ref.v, cl.x, cl.t, cl.v, g) - dt1 = t1 - cl.t - - ## advance clock by correct time step - v, x, tau = Simulation.hypTStep(dt1, cl.v, cl.x, cl.pt, g) - - ## check to see whether we have gone past an acceleration command boundary. - ## if so, we must instead advance the clock to the boundary and start again - if tau < tau1: - dtau = tau1 - cl.pt - cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g) - cl.pt = tau1-0.000001 - continue - if tau > tau2: - dtau = tau2 - cl.pt - cl.v, cl.x, cl.t = Simulation.tauStep(dtau, cl.v, cl.x, cl.t, g) - cl.pt = tau2 - continue - - ## Otherwise, record the new values and exit the loop - cl.v = v - cl.x = x - cl.pt = tau - cl.t = t1 - cl.m = None - break - - ## transform position into reference frame - x = cl.x - ref.x - t = cl.t - ref.t - gamma = (1.0 - ref.v**2) ** -0.5 - vg = -ref.v * gamma - - cl.refx = gamma * (x - ref.v * t) - cl.reft = ref.pt # + gamma * (t - ref.v * x) # this term belongs here, but it should always be equal to 0. - cl.refv = (cl.v - ref.v) / (1.0 - cl.v * ref.v) - cl.refm = None - cl.recordFrame(i) - - t += dt - - def plot(self, plot): - plot.clear() - for cl in self.clocks.values(): - c, p = cl.getCurve() - plot.addItem(c) - plot.addItem(p) - -class Animation(pg.ItemGroup): - def __init__(self, sim): - pg.ItemGroup.__init__(self) - self.sim = sim - self.clocks = sim.clocks - - self.items = {} - for name, cl in self.clocks.items(): - item = ClockItem(cl) - self.addItem(item) - self.items[name] = item - - def restart(self): - for cl in self.items.values(): - cl.reset() - - def stepTo(self, t): - for i in self.items.values(): - i.stepTo(t) - - -class ClockItem(pg.ItemGroup): - def __init__(self, clock): - pg.ItemGroup.__init__(self) - self.size = clock.size - self.item = QtGui.QGraphicsEllipseItem(QtCore.QRectF(0, 0, self.size, self.size)) - tr = QtGui.QTransform.fromTranslate(-self.size*0.5, -self.size*0.5) - self.item.setTransform(tr) - self.item.setPen(pg.mkPen(100,100,100)) - self.item.setBrush(clock.brush) - self.hand = QtGui.QGraphicsLineItem(0, 0, 0, self.size*0.5) - self.hand.setPen(pg.mkPen('w')) - self.hand.setZValue(10) - self.flare = QtGui.QGraphicsPolygonItem(QtGui.QPolygonF([ - QtCore.QPointF(0, -self.size*0.25), - QtCore.QPointF(0, self.size*0.25), - QtCore.QPointF(self.size*1.5, 0), - QtCore.QPointF(0, -self.size*0.25), - ])) - self.flare.setPen(pg.mkPen('y')) - self.flare.setBrush(pg.mkBrush(255,150,0)) - self.flare.setZValue(-10) - self.addItem(self.hand) - self.addItem(self.item) - self.addItem(self.flare) - - self.clock = clock - self.i = 1 - - self._spaceline = None - - - def spaceline(self): - if self._spaceline is None: - self._spaceline = pg.InfiniteLine() - self._spaceline.setPen(self.clock.pen) - return self._spaceline - - def stepTo(self, t): - data = self.clock.refData - - while self.i < len(data)-1 and data['t'][self.i] < t: - self.i += 1 - while self.i > 1 and data['t'][self.i-1] >= t: - self.i -= 1 - - self.setPos(data['x'][self.i], self.clock.y0) - - t = data['pt'][self.i] - self.hand.setRotation(-0.25 * t * 360.) - - v = data['v'][self.i] - gam = (1.0 - v**2)**0.5 - self.setTransform(QtGui.QTransform.fromScale(gam, 1.0)) - - f = data['f'][self.i] - tr = QtGui.QTransform() - if f < 0: - tr.translate(self.size*0.4, 0) - else: - tr.translate(-self.size*0.4, 0) - - tr.scale(-f * (0.5+np.random.random()*0.1), 1.0) - self.flare.setTransform(tr) - - if self._spaceline is not None: - self._spaceline.setPos(pg.Point(data['x'][self.i], data['t'][self.i])) - self._spaceline.setAngle(data['v'][self.i] * 45.) - - - def reset(self): - self.i = 1 - - -#class Spaceline(pg.InfiniteLine): - #def __init__(self, sim, frame): - #self.sim = sim - #self.frame = frame - #pg.InfiniteLine.__init__(self) - #self.setPen(sim.clocks[frame].pen) - - #def stepTo(self, t): - #self.setAngle(0) - - #pass - -if __name__ == '__main__': - pg.mkQApp() - #import pyqtgraph.console - #cw = pyqtgraph.console.ConsoleWidget() - #cw.show() - #cw.catchNextException() - win = RelativityGUI() - win.setWindowTitle("Relativity!") - win.show() - win.resize(1100,700) - - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() - - - #win.params.param('Objects').restoreState(state, removeChildren=False) - diff --git a/pyqtgraph/examples/relativity_demo.py b/pyqtgraph/examples/relativity_demo.py deleted file mode 100644 index 24a1f47..0000000 --- a/pyqtgraph/examples/relativity_demo.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Special relativity simulation - - - -""" -import initExample ## Add path to library (just for examples; you do not need this) -import pyqtgraph as pg -from relativity import RelativityGUI - -pg.mkQApp() -win = RelativityGUI() -win.setWindowTitle("Relativity!") -win.resize(1100,700) -win.show() -win.loadPreset(None, 'Twin Paradox (grid)') - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(pg.QtCore, 'PYQT_VERSION'): - pg.QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/scrollingPlots.py b/pyqtgraph/examples/scrollingPlots.py deleted file mode 100644 index d370aa4..0000000 --- a/pyqtgraph/examples/scrollingPlots.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Various methods of drawing scrolling plots. -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -win = pg.GraphicsLayoutWidget(show=True) -win.setWindowTitle('pyqtgraph example: Scrolling Plots') - - -# 1) Simplest approach -- update data in the array such that plot appears to scroll -# In these examples, the array size is fixed. -p1 = win.addPlot() -p2 = win.addPlot() -data1 = np.random.normal(size=300) -curve1 = p1.plot(data1) -curve2 = p2.plot(data1) -ptr1 = 0 -def update1(): - global data1, ptr1 - data1[:-1] = data1[1:] # shift data in the array one sample left - # (see also: np.roll) - data1[-1] = np.random.normal() - curve1.setData(data1) - - ptr1 += 1 - curve2.setData(data1) - curve2.setPos(ptr1, 0) - - -# 2) Allow data to accumulate. In these examples, the array doubles in length -# whenever it is full. -win.nextRow() -p3 = win.addPlot() -p4 = win.addPlot() -# Use automatic downsampling and clipping to reduce the drawing load -p3.setDownsampling(mode='peak') -p4.setDownsampling(mode='peak') -p3.setClipToView(True) -p4.setClipToView(True) -p3.setRange(xRange=[-100, 0]) -p3.setLimits(xMax=0) -curve3 = p3.plot() -curve4 = p4.plot() - -data3 = np.empty(100) -ptr3 = 0 - -def update2(): - global data3, ptr3 - data3[ptr3] = np.random.normal() - ptr3 += 1 - if ptr3 >= data3.shape[0]: - tmp = data3 - data3 = np.empty(data3.shape[0] * 2) - data3[:tmp.shape[0]] = tmp - curve3.setData(data3[:ptr3]) - curve3.setPos(-ptr3, 0) - curve4.setData(data3[:ptr3]) - - -# 3) Plot in chunks, adding one new plot curve for every 100 samples -chunkSize = 100 -# Remove chunks after we have 10 -maxChunks = 10 -startTime = pg.ptime.time() -win.nextRow() -p5 = win.addPlot(colspan=2) -p5.setLabel('bottom', 'Time', 's') -p5.setXRange(-10, 0) -curves = [] -data5 = np.empty((chunkSize+1,2)) -ptr5 = 0 - -def update3(): - global p5, data5, ptr5, curves - now = pg.ptime.time() - for c in curves: - c.setPos(-(now-startTime), 0) - - i = ptr5 % chunkSize - if i == 0: - curve = p5.plot() - curves.append(curve) - last = data5[-1] - data5 = np.empty((chunkSize+1,2)) - data5[0] = last - while len(curves) > maxChunks: - c = curves.pop(0) - p5.removeItem(c) - else: - curve = curves[-1] - data5[i+1,0] = now - startTime - data5[i+1,1] = np.random.normal() - curve.setData(x=data5[:i+2, 0], y=data5[:i+2, 1]) - ptr5 += 1 - - -# update all plots -def update(): - update1() - update2() - update3() -timer = pg.QtCore.QTimer() -timer.timeout.connect(update) -timer.start(50) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/syntax.py b/pyqtgraph/examples/syntax.py deleted file mode 100644 index 2c54786..0000000 --- a/pyqtgraph/examples/syntax.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -# based on https://github.com/art1415926535/PyQt5-syntax-highlighting - -from pyqtgraph.Qt import QtCore, QtGui - -QRegExp = QtCore.QRegExp - -QFont = QtGui.QFont -QColor = QtGui.QColor -QTextCharFormat = QtGui.QTextCharFormat -QSyntaxHighlighter = QtGui.QSyntaxHighlighter - - -def format(color, style=''): - """ - Return a QTextCharFormat with the given attributes. - """ - _color = QColor() - if type(color) is not str: - _color.setRgb(color[0], color[1], color[2]) - else: - _color.setNamedColor(color) - - _format = QTextCharFormat() - _format.setForeground(_color) - if 'bold' in style: - _format.setFontWeight(QFont.Bold) - if 'italic' in style: - _format.setFontItalic(True) - - return _format - - -class LightThemeColors: - - Red = "#B71C1C" - Pink = "#FCE4EC" - Purple = "#4A148C" - DeepPurple = "#311B92" - Indigo = "#1A237E" - Blue = "#0D47A1" - LightBlue = "#01579B" - Cyan = "#006064" - Teal = "#004D40" - Green = "#1B5E20" - LightGreen = "#33691E" - Lime = "#827717" - Yellow = "#F57F17" - Amber = "#FF6F00" - Orange = "#E65100" - DeepOrange = "#BF360C" - Brown = "#3E2723" - Grey = "#212121" - BlueGrey = "#263238" - - -class DarkThemeColors: - - Red = "#F44336" - Pink = "#F48FB1" - Purple = "#CE93D8" - DeepPurple = "#B39DDB" - Indigo = "#9FA8DA" - Blue = "#90CAF9" - LightBlue = "#81D4FA" - Cyan = "#80DEEA" - Teal = "#80CBC4" - Green = "#A5D6A7" - LightGreen = "#C5E1A5" - Lime = "#E6EE9C" - Yellow = "#FFF59D" - Amber = "#FFE082" - Orange = "#FFCC80" - DeepOrange = "#FFAB91" - Brown = "#BCAAA4" - Grey = "#EEEEEE" - BlueGrey = "#B0BEC5" - - -LIGHT_STYLES = { - 'keyword': format(LightThemeColors.Blue, 'bold'), - 'operator': format(LightThemeColors.Red, 'bold'), - 'brace': format(LightThemeColors.Purple), - 'defclass': format(LightThemeColors.Indigo, 'bold'), - 'string': format(LightThemeColors.Amber), - 'string2': format(LightThemeColors.DeepPurple), - 'comment': format(LightThemeColors.Green, 'italic'), - 'self': format(LightThemeColors.Blue, 'bold'), - 'numbers': format(LightThemeColors.Teal), -} - - -DARK_STYLES = { - 'keyword': format(DarkThemeColors.Blue, 'bold'), - 'operator': format(DarkThemeColors.Red, 'bold'), - 'brace': format(DarkThemeColors.Purple), - 'defclass': format(DarkThemeColors.Indigo, 'bold'), - 'string': format(DarkThemeColors.Amber), - 'string2': format(DarkThemeColors.DeepPurple), - 'comment': format(DarkThemeColors.Green, 'italic'), - 'self': format(DarkThemeColors.Blue, 'bold'), - 'numbers': format(DarkThemeColors.Teal), -} - - -class PythonHighlighter(QSyntaxHighlighter): - """Syntax highlighter for the Python language. - """ - # Python keywords - keywords = [ - 'and', 'assert', 'break', 'class', 'continue', 'def', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', - 'None', 'True', 'False', 'async', 'await', - ] - - # Python operators - operators = [ - r'=', - # Comparison - r'==', r'!=', r'<', r'<=', r'>', r'>=', - # Arithmetic - r'\+', r'-', r'\*', r'/', r'//', r'\%', r'\*\*', - # In-place - r'\+=', r'-=', r'\*=', r'/=', r'\%=', - # Bitwise - r'\^', r'\|', r'\&', r'\~', r'>>', r'<<', - ] - - # Python braces - braces = [ - r'\{', r'\}', r'\(', r'\)', r'\[', r'\]', - ] - - def __init__(self, document): - QSyntaxHighlighter.__init__(self, document) - - # Multi-line strings (expression, flag, style) - # FIXME: The triple-quotes in these two lines will mess up the - # syntax highlighting from this point onward - self.tri_single = (QRegExp("'''"), 1, 'string2') - self.tri_double = (QRegExp('"""'), 2, 'string2') - - rules = [] - - # Keyword, operator, and brace rules - rules += [(r'\b%s\b' % w, 0, 'keyword') - for w in PythonHighlighter.keywords] - rules += [(r'%s' % o, 0, 'operator') - for o in PythonHighlighter.operators] - rules += [(r'%s' % b, 0, 'brace') - for b in PythonHighlighter.braces] - - # All other rules - rules += [ - - # 'self' - (r'\bself\b', 0, 'self'), - - # 'def' followed by an identifier - (r'\bdef\b\s*(\w+)', 1, 'defclass'), - # 'class' followed by an identifier - (r'\bclass\b\s*(\w+)', 1, 'defclass'), - - # Numeric literals - (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), - (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), - (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), - - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), - - # From '#' until a newline - (r'#[^\n]*', 0, 'comment'), - - ] - - # Build a QRegExp for each pattern - self.rules = [(QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - - @property - def styles(self): - app = QtGui.QApplication.instance() - return DARK_STYLES if app.property('darkMode') else LIGHT_STYLES - - def highlightBlock(self, text): - """Apply syntax highlighting to the given block of text. - """ - # Do other syntax formatting - for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) - format = self.styles[format] - - while index >= 0: - # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) - - self.setCurrentBlockState(0) - - # Do multi-line strings - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - - def match_multiline(self, text, delimiter, in_state, style): - """Do highlighting of multi-line strings. ``delimiter`` should be a - ``QRegExp`` for triple-single-quotes or triple-double-quotes, and - ``in_state`` should be a unique integer to represent the corresponding - state changes when inside those strings. Returns True if we're still - inside a multi-line string when this function is finished. - """ - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - start = delimiter.indexIn(text) - # Move past this match - add = delimiter.matchedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - end = delimiter.indexIn(text, start + add) - # Ending delimiter on this line? - if end >= add: - length = end - start + add + delimiter.matchedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, self.styles[style]) - # Look for the next match - start = delimiter.indexIn(text, start + length) - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False diff --git a/pyqtgraph/examples/template.py b/pyqtgraph/examples/template.py deleted file mode 100644 index d39c6fb..0000000 --- a/pyqtgraph/examples/template.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -""" - -Description of example - - -""" -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui, mkQApp -import numpy as np - -app = mkQApp() - -# win.setWindowTitle('pyqtgraph example: ____') - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/test_ExampleApp.py b/pyqtgraph/examples/test_ExampleApp.py deleted file mode 100644 index 7a1696d..0000000 --- a/pyqtgraph/examples/test_ExampleApp.py +++ /dev/null @@ -1,13 +0,0 @@ -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore - -from examples.ExampleApp import ExampleLoader - -loader = ExampleLoader() - -if __name__ == "__main__": - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file diff --git a/pyqtgraph/examples/test_examples.py b/pyqtgraph/examples/test_examples.py deleted file mode 100644 index 4339875..0000000 --- a/pyqtgraph/examples/test_examples.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function, division, absolute_import -from collections import namedtuple -from pyqtgraph import Qt - -import errno -import importlib -import itertools -import pytest -import os, sys -import platform -import subprocess -import time -from argparse import Namespace -if __name__ == "__main__" and (__package__ is None or __package__==''): - parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - sys.path.insert(0, parent_dir) - import examples - __package__ = "examples" - -from . import utils - -def buildFileList(examples, files=None): - if files is None: - files = [] - for key, val in examples.items(): - if isinstance(val, dict): - buildFileList(val, files) - elif isinstance(val, Namespace): - files.append((key, val.filename)) - else: - files.append((key, val)) - return files - - -path = os.path.abspath(os.path.dirname(__file__)) -files = [("Example App", "test_ExampleApp.py")] -for ex in [utils.examples, utils.others]: - files = buildFileList(ex, files) -files = sorted(set(files)) -frontends = { - Qt.PYQT5: False, - Qt.PYQT6: False, - Qt.PYSIDE2: False, - Qt.PYSIDE6: False, -} -# sort out which of the front ends are available -for frontend in frontends.keys(): - try: - importlib.import_module(frontend) - frontends[frontend] = True - except ImportError: - pass - -installedFrontends = sorted([ - frontend for frontend, isPresent in frontends.items() if isPresent -]) - -darwin_opengl_broken = (platform.system() == "Darwin" and - tuple(map(int, platform.mac_ver()[0].split("."))) >= (10, 16) and - (sys.version_info <= (3, 8, 7) or (3, 9) <= sys.version_info < (3, 9, 1))) - -darwin_opengl_reason = ("pyopenGL cannot find openGL library on big sur: " - "https://github.com/python/cpython/pull/21241") - -exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"]) -conditionalExamples = { - "hdf5.py": exceptionCondition( - False, - reason="Example requires user interaction" - ), - "RemoteSpeedTest.py": exceptionCondition( - False, - reason="Test is being problematic on CI machines" - ), - 'GLVolumeItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLIsosurface.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLSurfacePlot.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLScatterPlotItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLshaders.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLLinePlotItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLMeshItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLImageItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLBarGraphItem.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ), - 'GLViewWidget.py': exceptionCondition( - not darwin_opengl_broken, - reason=darwin_opengl_reason - ) -} - -@pytest.mark.skipif( - Qt.QT_LIB == "PySide2" - and tuple(map(int, Qt.PySide2.__version__.split("."))) >= (5, 14) - and tuple(map(int, Qt.PySide2.__version__.split("."))) < (5, 14, 2, 2), - reason="new PySide2 doesn't have loadUi functionality" -) -@pytest.mark.parametrize( - "frontend, f", - [ - pytest.param( - frontend, - f, - marks=pytest.mark.skipif( - conditionalExamples[f[1]].condition is False, - reason=conditionalExamples[f[1]].reason - ) if f[1] in conditionalExamples.keys() else (), - ) - for frontend, f, in itertools.product(installedFrontends, files) - ], - ids = [ - " {} - {} ".format(f[1], frontend) - for frontend, f in itertools.product( - installedFrontends, - files - ) - ] -) -def testExamples(frontend, f): - # runExampleFile(f[0], f[1], sys.executable, frontend) - - name, file = f - global path - fn = os.path.join(path, file) - os.chdir(path) - sys.stdout.write("{} ".format(name)) - sys.stdout.flush() - import1 = "import %s" % frontend if frontend != '' else '' - import2 = os.path.splitext(os.path.split(fn)[1])[0] - code = """ -try: - {0} - import initExample - import pyqtgraph as pg - import {1} - import sys - print("test complete") - sys.stdout.flush() - pg.Qt.QtCore.QTimer.singleShot(1000, pg.Qt.QtWidgets.QApplication.quit) - pg.Qt.QtWidgets.QApplication.instance().exec_() - names = [x for x in dir({1}) if not x.startswith('_')] - for name in names: - delattr({1}, name) -except: - print("test failed") - raise - -""".format(import1, import2) - if sys.platform.startswith('win'): - process = subprocess.Popen([sys.executable], - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE) - else: - process = subprocess.Popen(['exec %s -i' % (sys.executable)], - shell=True, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE) - process.stdin.write(code.encode('UTF-8')) - process.stdin.close() - output = '' - fail = False - while True: - try: - c = process.stdout.read(1).decode() - except IOError as err: - if err.errno == errno.EINTR: - # Interrupted system call; just try again. - c = '' - else: - raise - output += c - - if output.endswith('test complete'): - break - if output.endswith('test failed'): - fail = True - break - start = time.time() - killed = False - while process.poll() is None: - time.sleep(0.1) - if time.time() - start > 2.0 and not killed: - process.kill() - killed = True - #res = process.communicate() - res = (process.stdout.read(), process.stderr.read()) - if (fail or - 'exception' in res[1].decode().lower() or - 'error' in res[1].decode().lower()): - print(res[0].decode()) - print(res[1].decode()) - pytest.fail("{}\n{}\nFailed {} Example Test Located in {} " - .format(res[0].decode(), res[1].decode(), name, file), - pytrace=False) - -if __name__ == "__main__": - pytest.cmdline.main() diff --git a/pyqtgraph/examples/text.py b/pyqtgraph/examples/text.py deleted file mode 100644 index bf9bd6b..0000000 --- a/pyqtgraph/examples/text.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This example shows how to insert text into a scene using TextItem. This class -is for displaying text that is anchored to a particular location in the data -coordinate system, but which is always displayed unscaled. - -For text that scales with the data, use QTextItem. -For text that can be placed in a layout, use LabelItem. -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - - -x = np.linspace(-20, 20, 1000) -y = np.sin(x) / x -plot = pg.plot() ## create an empty plot widget -plot.setYRange(-1, 2) -plot.setWindowTitle('pyqtgraph example: text') -curve = plot.plot(x,y) ## add a single curve - -## Create text object, use HTML tags to specify color/size -text = pg.TextItem(html='
This is the
PEAK
', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100)) -plot.addItem(text) -text.setPos(0, y.max()) - -## Draw an arrowhead next to the text box -arrow = pg.ArrowItem(pos=(0, y.max()), angle=-45) -plot.addItem(arrow) - - -## Set up an animated arrow and text that track the curve -curvePoint = pg.CurvePoint(curve) -plot.addItem(curvePoint) -text2 = pg.TextItem("test", anchor=(0.5, -1.0)) -text2.setParentItem(curvePoint) -arrow2 = pg.ArrowItem(angle=90) -arrow2.setParentItem(curvePoint) - -## update position every 10ms -index = 0 -def update(): - global curvePoint, index - index = (index + 1) % len(x) - curvePoint.setPos(float(index)/(len(x)-1)) - text2.setText('[%0.1f, %0.1f]' % (x[index], y[index])) - -timer = QtCore.QTimer() -timer.timeout.connect(update) -timer.start(10) - - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/examples/utils.py b/pyqtgraph/examples/utils.py deleted file mode 100644 index a7ac8bd..0000000 --- a/pyqtgraph/examples/utils.py +++ /dev/null @@ -1,124 +0,0 @@ -from collections import OrderedDict -from argparse import Namespace - - -examples = OrderedDict([ - ('Command-line usage', 'CLIexample.py'), - ('Basic Plotting', Namespace(filename='Plotting.py', recommended=True)), - ('ImageView', 'ImageView.py'), - ('ParameterTree', 'parametertree.py'), - ('Crosshair / Mouse interaction', 'crosshair.py'), - ('Data Slicing', 'DataSlicing.py'), - ('Plot Customization', 'customPlot.py'), - ('Timestamps on x axis', 'DateAxisItem.py'), - ('Image Analysis', 'imageAnalysis.py'), - ('ViewBox Features', Namespace(filename='ViewBoxFeatures.py', recommended=True)), - ('Dock widgets', 'dockarea.py'), - ('Console', 'ConsoleWidget.py'), - ('Histograms', 'histogram.py'), - ('Beeswarm plot', 'beeswarm.py'), - ('Symbols', 'Symbols.py'), - ('Auto-range', 'PlotAutoRange.py'), - ('Remote Plotting', 'RemoteSpeedTest.py'), - ('Scrolling plots', 'scrollingPlots.py'), - ('HDF5 big data', 'hdf5.py'), - ('Demos', OrderedDict([ - ('Optics', 'optics_demos.py'), - ('Special relativity', 'relativity_demo.py'), - ('Verlet chain', 'verlet_chain_demo.py'), - ('Koch Fractal', 'fractal.py'), - ])), - ('GraphicsItems', OrderedDict([ - ('Scatter Plot', 'ScatterPlot.py'), - #('PlotItem', 'PlotItem.py'), - ('IsocurveItem', 'isocurve.py'), - ('GraphItem', 'GraphItem.py'), - ('ErrorBarItem', 'ErrorBarItem.py'), - ('FillBetweenItem', 'FillBetweenItem.py'), - ('ImageItem - video', 'ImageItem.py'), - ('ImageItem - draw', 'Draw.py'), - ('Non-uniform Image', 'NonUniformImage.py'), - ('Region-of-Interest', 'ROIExamples.py'), - ('Bar Graph', 'BarGraphItem.py'), - ('GraphicsLayout', 'GraphicsLayout.py'), - ('LegendItem', 'Legend.py'), - ('Text Item', 'text.py'), - ('Linked Views', 'linkedViews.py'), - ('Arrow', 'Arrow.py'), - ('ViewBox', 'ViewBoxFeatures.py'), - ('Custom Graphics', 'customGraphicsItem.py'), - ('Labeled Graph', 'CustomGraphItem.py'), - ('PColorMeshItem', 'PColorMeshItem.py'), - ])), - ('Benchmarks', OrderedDict([ - ('Video speed test', 'VideoSpeedTest.py'), - ('Line Plot update', 'PlotSpeedTest.py'), - ('Scatter Plot update', 'ScatterPlotSpeedTest.py'), - ('Multiple plots', 'MultiPlotSpeedTest.py'), - ])), - ('3D Graphics', OrderedDict([ - ('Volumetric', 'GLVolumeItem.py'), - ('Isosurface', 'GLIsosurface.py'), - ('Surface Plot', 'GLSurfacePlot.py'), - ('Scatter Plot', 'GLScatterPlotItem.py'), - ('Shaders', 'GLshaders.py'), - ('Line Plot', 'GLLinePlotItem.py'), - ('Mesh', 'GLMeshItem.py'), - ('Image', 'GLImageItem.py'), - ])), - ('Widgets', OrderedDict([ - ('PlotWidget', 'PlotWidget.py'), - ('SpinBox', 'SpinBox.py'), - ('ConsoleWidget', 'ConsoleWidget.py'), - ('Histogram / lookup table', 'HistogramLUT.py'), - ('TreeWidget', 'TreeWidget.py'), - ('ScatterPlotWidget', 'ScatterPlotWidget.py'), - ('DataTreeWidget', 'DataTreeWidget.py'), - ('GradientWidget', 'GradientWidget.py'), - ('TableWidget', 'TableWidget.py'), - ('ColorButton', 'ColorButton.py'), - #('CheckTable', '../widgets/CheckTable.py'), - #('VerticalLabel', '../widgets/VerticalLabel.py'), - ('JoystickButton', 'JoystickButton.py'), - ])), - ('Flowcharts', 'Flowchart.py'), - ('Custom Flowchart Nodes', 'FlowchartCustomNode.py'), -]) - - -# don't care about ordering -# but actually from Python 3.7, dict is ordered -others = dict([ - ('logAxis', 'logAxis.py'), - ('PanningPlot', 'PanningPlot.py'), - ('MultiplePlotAxes', 'MultiplePlotAxes.py'), - ('ROItypes', 'ROItypes.py'), - ('ScaleBar', 'ScaleBar.py'), - ('InfiniteLine', 'InfiniteLine.py'), - ('ViewBox', 'ViewBox.py'), - ('GradientEditor', 'GradientEditor.py'), - ('GLBarGraphItem', 'GLBarGraphItem.py'), - ('GLViewWidget', 'GLViewWidget.py'), - ('DiffTreeWidget', 'DiffTreeWidget.py'), - ('MultiPlotWidget', 'MultiPlotWidget.py'), - ('RemoteGraphicsView', 'RemoteGraphicsView.py'), - ('colorMaps', 'colorMaps.py'), - ('contextMenu', 'contextMenu.py'), - ('designerExample', 'designerExample.py'), - ('DateAxisItem_QtDesigner', 'DateAxisItem_QtDesigner.py'), - ('GraphicsScene', 'GraphicsScene.py'), - ('MouseSelection', 'MouseSelection.py'), -]) - - -# examples that are subsumed into other examples -trivial = dict([ - ('SimplePlot', 'SimplePlot.py'), # Plotting.py - ('LogPlotTest', 'LogPlotTest.py'), # Plotting.py - ('ViewLimits', 'ViewLimits.py'), # ViewBoxFeatures.py -]) - -# examples that are not suitable for CI testing -skiptest = dict([ - ('ProgressDialog', 'ProgressDialog.py'), # modal dialog -]) diff --git a/pyqtgraph/examples/verlet_chain/__init__.py b/pyqtgraph/examples/verlet_chain/__init__.py deleted file mode 100644 index f473190..0000000 --- a/pyqtgraph/examples/verlet_chain/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .chain import ChainSim \ No newline at end of file diff --git a/pyqtgraph/examples/verlet_chain/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/examples/verlet_chain/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 7d51c1d..0000000 Binary files a/pyqtgraph/examples/verlet_chain/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/verlet_chain/__pycache__/chain.cpython-36.pyc b/pyqtgraph/examples/verlet_chain/__pycache__/chain.cpython-36.pyc deleted file mode 100644 index f4102c8..0000000 Binary files a/pyqtgraph/examples/verlet_chain/__pycache__/chain.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/verlet_chain/__pycache__/relax.cpython-36.pyc b/pyqtgraph/examples/verlet_chain/__pycache__/relax.cpython-36.pyc deleted file mode 100644 index 7e39e0b..0000000 Binary files a/pyqtgraph/examples/verlet_chain/__pycache__/relax.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/examples/verlet_chain/chain.py b/pyqtgraph/examples/verlet_chain/chain.py deleted file mode 100644 index 1c4f240..0000000 --- a/pyqtgraph/examples/verlet_chain/chain.py +++ /dev/null @@ -1,113 +0,0 @@ -import pyqtgraph as pg -import numpy as np -import time -from . import relax - - -class ChainSim(pg.QtCore.QObject): - - stepped = pg.QtCore.Signal() - relaxed = pg.QtCore.Signal() - - def __init__(self): - pg.QtCore.QObject.__init__(self) - - self.damping = 0.1 # 0=full damping, 1=no damping - self.relaxPerStep = 10 - self.maxTimeStep = 0.01 - - self.pos = None # (Npts, 2) float - self.mass = None # (Npts) float - self.fixed = None # (Npts) bool - self.links = None # (Nlinks, 2), uint - self.lengths = None # (Nlinks), float - self.push = None # (Nlinks), bool - self.pull = None # (Nlinks), bool - - self.initialized = False - self.lasttime = None - self.lastpos = None - - def init(self): - if self.initialized: - return - - if self.fixed is None: - self.fixed = np.zeros(self.pos.shape[0], dtype=bool) - if self.push is None: - self.push = np.ones(self.links.shape[0], dtype=bool) - if self.pull is None: - self.pull = np.ones(self.links.shape[0], dtype=bool) - - - # precompute relative masses across links - l1 = self.links[:,0] - l2 = self.links[:,1] - m1 = self.mass[l1] - m2 = self.mass[l2] - self.mrel1 = (m1 / (m1+m2))[:,np.newaxis] - self.mrel1[self.fixed[l1]] = 1 # fixed point constraint - self.mrel1[self.fixed[l2]] = 0 - self.mrel2 = 1.0 - self.mrel1 - - for i in range(10): - self.relax(n=10) - - self.initialized = True - - def makeGraph(self): - #g1 = pg.GraphItem(pos=self.pos, adj=self.links[self.rope], pen=0.2, symbol=None) - brushes = np.where(self.fixed, pg.mkBrush(0,0,0,255), pg.mkBrush(50,50,200,255)) - g2 = pg.GraphItem(pos=self.pos, adj=self.links[self.push & self.pull], pen=0.5, brush=brushes, symbol='o', size=(self.mass**0.33), pxMode=False) - p = pg.ItemGroup() - #p.addItem(g1) - p.addItem(g2) - return p - - def update(self): - # approximate physics with verlet integration - - now = pg.ptime.time() - if self.lasttime is None: - dt = 0 - else: - dt = now - self.lasttime - self.lasttime = now - - # limit amount of work to be done between frames - if not relax.COMPILED: - dt = self.maxTimeStep - - if self.lastpos is None: - self.lastpos = self.pos - - # remember fixed positions - fixedpos = self.pos[self.fixed] - - while dt > 0: - dt1 = min(self.maxTimeStep, dt) - dt -= dt1 - - # compute motion since last timestep - dx = self.pos - self.lastpos - self.lastpos = self.pos - - # update positions for gravity and inertia - acc = np.array([[0, -5]]) * dt1 - inertia = dx * (self.damping**(dt1/self.mass))[:,np.newaxis] # with mass-dependent damping - self.pos = self.pos + inertia + acc - - self.pos[self.fixed] = fixedpos # fixed point constraint - - # correct for link constraints - self.relax(self.relaxPerStep) - self.stepped.emit() - - - def relax(self, n=50): - # speed up with C magic if possible - relax.relax(self.pos, self.links, self.mrel1, self.mrel2, self.lengths, self.push, self.pull, n) - self.relaxed.emit() - - - diff --git a/pyqtgraph/examples/verlet_chain/relax.py b/pyqtgraph/examples/verlet_chain/relax.py deleted file mode 100644 index 22c54d6..0000000 --- a/pyqtgraph/examples/verlet_chain/relax.py +++ /dev/null @@ -1,70 +0,0 @@ -import ctypes -import os - -so = os.path.join(os.path.dirname(__file__), 'maths.so') -try: - lib = ctypes.CDLL(so) - COMPILED = True -except OSError: - COMPILED = False - - -if COMPILED: - lib.relax.argtypes = [ - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_int, - ] - - def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters): - nlinks = links.shape[0] - lib.relax(pos.ctypes, links.ctypes, mrel1.ctypes, mrel2.ctypes, lengths.ctypes, push.ctypes, pull.ctypes, nlinks, iters) - -else: - def relax(pos, links, mrel1, mrel2, lengths, push, pull, iters): - lengths2 = lengths**2 - for i in range(iters): - #p1 = links[:, 0] - #p2 = links[:, 1] - #x1 = pos[p1] - #x2 = pos[p2] - - #dx = x2 - x1 - - #dist = (dx**2).sum(axis=1)**0.5 - - #mask = (npush & (dist < lengths)) | (npull & (dist > lengths)) - ##dist[mask] = lengths[mask] - #change = (lengths-dist) / dist - #change[mask] = 0 - - #dx *= change[:, np.newaxis] - #print dx - - ##pos[p1] -= mrel2 * dx - ##pos[p2] += mrel1 * dx - #for j in range(links.shape[0]): - #pos[links[j,0]] -= mrel2[j] * dx[j] - #pos[links[j,1]] += mrel1[j] * dx[j] - - - for l in range(links.shape[0]): - p1, p2 = links[l]; - x1 = pos[p1] - x2 = pos[p2] - - dx = x2 - x1 - dist2 = (dx**2).sum() - - if (push[l] and dist2 < lengths2[l]) or (pull[l] and dist2 > lengths2[l]): - dist = dist2 ** 0.5 - change = (lengths[l]-dist) / dist - dx *= change - pos[p1] -= mrel2[l] * dx - pos[p2] += mrel1[l] * dx diff --git a/pyqtgraph/examples/verlet_chain_demo.py b/pyqtgraph/examples/verlet_chain_demo.py deleted file mode 100644 index 1197344..0000000 --- a/pyqtgraph/examples/verlet_chain_demo.py +++ /dev/null @@ -1,126 +0,0 @@ -""" -Mechanical simulation of a chain using verlet integration. - -Use the mouse to interact with one of the chains. - -By default, this uses a slow, pure-python integrator to solve the chain link -positions. Unix users may compile a small math library to speed this up by -running the `examples/verlet_chain/make` script. - -""" - -import initExample ## Add path to library (just for examples; you do not need this) - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np - -import verlet_chain - -sim = verlet_chain.ChainSim() - -if verlet_chain.relax.COMPILED: - # Use more complex chain if compiled mad library is available. - chlen1 = 80 - chlen2 = 60 - linklen = 1 -else: - chlen1 = 10 - chlen2 = 8 - linklen = 8 - -npts = chlen1 + chlen2 - -sim.mass = np.ones(npts) -sim.mass[int(chlen1 * 0.8)] = 100 -sim.mass[chlen1-1] = 500 -sim.mass[npts-1] = 200 - -sim.fixed = np.zeros(npts, dtype=bool) -sim.fixed[0] = True -sim.fixed[chlen1] = True - -sim.pos = np.empty((npts, 2)) -sim.pos[:chlen1, 0] = 0 -sim.pos[chlen1:, 0] = 10 -sim.pos[:chlen1, 1] = np.arange(chlen1) * linklen -sim.pos[chlen1:, 1] = np.arange(chlen2) * linklen -# to prevent miraculous balancing acts: -sim.pos += np.random.normal(size=sim.pos.shape, scale=1e-3) - -links1 = [(j, i+j+1) for i in range(chlen1) for j in range(chlen1-i-1)] -links2 = [(j, i+j+1) for i in range(chlen2) for j in range(chlen2-i-1)] -sim.links = np.concatenate([np.array(links1), np.array(links2)+chlen1, np.array([[chlen1-1, npts-1]])]) - -p1 = sim.pos[sim.links[:,0]] -p2 = sim.pos[sim.links[:,1]] -dif = p2-p1 -sim.lengths = (dif**2).sum(axis=1) ** 0.5 -sim.lengths[(chlen1-1):len(links1)] *= 1.05 # let auxiliary links stretch a little -sim.lengths[(len(links1)+chlen2-1):] *= 1.05 -sim.lengths[-1] = 7 - -push1 = np.ones(len(links1), dtype=bool) -push1[chlen1:] = False -push2 = np.ones(len(links2), dtype=bool) -push2[chlen2:] = False -sim.push = np.concatenate([push1, push2, np.array([True], dtype=bool)]) - -sim.pull = np.ones(sim.links.shape[0], dtype=bool) -sim.pull[-1] = False - -# move chain initially just to generate some motion if the mouse is not over the window -mousepos = np.array([30, 20]) - - -def display(): - global view, sim - view.clear() - view.addItem(sim.makeGraph()) - -def relaxed(): - global app - display() - app.processEvents() - -def mouse(pos): - global mousepos - pos = view.mapSceneToView(pos) - mousepos = np.array([pos.x(), pos.y()]) - -def update(): - global mousepos - #sim.pos[0] = sim.pos[0] * 0.9 + mousepos * 0.1 - s = 0.9 - sim.pos[0] = sim.pos[0] * s + mousepos * (1.0-s) - sim.update() - -app = pg.mkQApp() -win = pg.GraphicsLayoutWidget() -win.show() -view = win.addViewBox() -view.setAspectLocked(True) -view.setXRange(-100, 100) -#view.autoRange() - -view.scene().sigMouseMoved.connect(mouse) - -#display() -#app.processEvents() - -sim.relaxed.connect(relaxed) -sim.init() -sim.relaxed.disconnect(relaxed) - -sim.stepped.connect(display) - -timer = pg.QtCore.QTimer() -timer.timeout.connect(update) -timer.start(16) - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/pyqtgraph/exceptionHandling.py b/pyqtgraph/exceptionHandling.py deleted file mode 100644 index 3182b7e..0000000 --- a/pyqtgraph/exceptionHandling.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module installs a wrapper around sys.excepthook which allows multiple -new exception handlers to be registered. - -Optionally, the wrapper also stops exceptions from causing long-term storage -of local stack frames. This has two major effects: - - Unhandled exceptions will no longer cause memory leaks - (If an exception occurs while a lot of data is present on the stack, - such as when loading large files, the data would ordinarily be kept - until the next exception occurs. We would rather release this memory - as soon as possible.) - - Some debuggers may have a hard time handling uncaught exceptions - -The module also provides a callback mechanism allowing others to respond -to exceptions. -""" - -import sys, time -#from lib.Manager import logMsg -import traceback -#from log import * - -#logging = False - -callbacks = [] -clear_tracebacks = False - -def register(fn): - """ - Register a callable to be invoked when there is an unhandled exception. - The callback will be passed the output of sys.exc_info(): (exception type, exception, traceback) - Multiple callbacks will be invoked in the order they were registered. - """ - callbacks.append(fn) - -def unregister(fn): - """Unregister a previously registered callback.""" - callbacks.remove(fn) - -def setTracebackClearing(clear=True): - """ - Enable or disable traceback clearing. - By default, clearing is disabled and Python will indefinitely store unhandled exception stack traces. - This function is provided since Python's default behavior can cause unexpected retention of - large memory-consuming objects. - """ - global clear_tracebacks - clear_tracebacks = clear - -class ExceptionHandler(object): - def __call__(self, *args): - ## Start by extending recursion depth just a bit. - ## If the error we are catching is due to recursion, we don't want to generate another one here. - recursionLimit = sys.getrecursionlimit() - try: - sys.setrecursionlimit(recursionLimit+100) - - - ## call original exception handler first (prints exception) - global original_excepthook, callbacks, clear_tracebacks - try: - print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())))) - except Exception: - sys.stderr.write("Warning: stdout is broken! Falling back to stderr.\n") - sys.stdout = sys.stderr - - ret = original_excepthook(*args) - - for cb in callbacks: - try: - cb(*args) - except Exception: - print(" --------------------------------------------------------------") - print(" Error occurred during exception callback %s" % str(cb)) - print(" --------------------------------------------------------------") - traceback.print_exception(*sys.exc_info()) - - - ## Clear long-term storage of last traceback to prevent memory-hogging. - ## (If an exception occurs while a lot of data is present on the stack, - ## such as when loading large files, the data would ordinarily be kept - ## until the next exception occurs. We would rather release this memory - ## as soon as possible.) - if clear_tracebacks is True: - sys.last_traceback = None - - finally: - sys.setrecursionlimit(recursionLimit) - - - def implements(self, interface=None): - ## this just makes it easy for us to detect whether an ExceptionHook is already installed. - if interface is None: - return ['ExceptionHandler'] - else: - return interface == 'ExceptionHandler' - - - -## replace built-in excepthook only if this has not already been done -if not (hasattr(sys.excepthook, 'implements') and sys.excepthook.implements('ExceptionHandler')): - original_excepthook = sys.excepthook - sys.excepthook = ExceptionHandler() - - - diff --git a/pyqtgraph/exporters/CSVExporter.py b/pyqtgraph/exporters/CSVExporter.py deleted file mode 100644 index aec27a1..0000000 --- a/pyqtgraph/exporters/CSVExporter.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from .Exporter import Exporter -from ..parametertree import Parameter -from .. import PlotItem -from ..python2_3 import asUnicode - -translate = QtCore.QCoreApplication.translate - -__all__ = ['CSVExporter'] - - -class CSVExporter(Exporter): - Name = "CSV from plot data" - windows = [] - def __init__(self, item): - Exporter.__init__(self, item) - self.params = Parameter(name='params', type='group', children=[ - {'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, - {'name': 'precision', 'title': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]}, - {'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']} - ]) - - def parameters(self): - return self.params - - def export(self, fileName=None): - - if not isinstance(self.item, PlotItem): - raise Exception("Must have a PlotItem selected for CSV export.") - - if fileName is None: - self.fileSaveDialog(filter=["*.csv", "*.tsv"]) - return - - data = [] - header = [] - - appendAllX = self.params['columnMode'] == '(x,y) per plot' - - for i, c in enumerate(self.item.curves): - cd = c.getData() - if cd[0] is None: - continue - data.append(cd) - if hasattr(c, 'implements') and c.implements('plotData') and c.name() is not None: - name = c.name().replace('"', '""') + '_' - xName, yName = '"'+name+'x"', '"'+name+'y"' - else: - xName = 'x%04d' % i - yName = 'y%04d' % i - if appendAllX or i == 0: - header.extend([xName, yName]) - else: - header.extend([yName]) - - if self.params['separator'] == 'comma': - sep = ',' - else: - sep = '\t' - - with open(fileName, 'w') as fd: - fd.write(sep.join(map(asUnicode, header)) + '\n') - i = 0 - numFormat = '%%0.%dg' % self.params['precision'] - numRows = max([len(d[0]) for d in data]) - for i in range(numRows): - for j, d in enumerate(data): - # write x value if this is the first column, or if we want - # x for all rows - if appendAllX or j == 0: - if d is not None and i < len(d[0]): - fd.write(numFormat % d[0][i] + sep) - else: - fd.write(' %s' % sep) - - # write y value - if d is not None and i < len(d[1]): - fd.write(numFormat % d[1][i] + sep) - else: - fd.write(' %s' % sep) - fd.write('\n') - - -CSVExporter.register() diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py deleted file mode 100644 index c89ef05..0000000 --- a/pyqtgraph/exporters/Exporter.py +++ /dev/null @@ -1,140 +0,0 @@ -from ..widgets.FileDialog import FileDialog -from ..Qt import QtGui, QtCore, QtSvg -from ..python2_3 import asUnicode, basestring -from ..GraphicsScene import GraphicsScene -import os, re -LastExportDirectory = None - - -class Exporter(object): - """ - Abstract class used for exporting graphics to file / printer / whatever. - """ - allowCopy = False # subclasses set this to True if they can use the copy buffer - Exporters = [] - - @classmethod - def register(cls): - """ - Used to register Exporter classes to appear in the export dialog. - """ - Exporter.Exporters.append(cls) - - def __init__(self, item): - """ - Initialize with the item to be exported. - Can be an individual graphics item or a scene. - """ - object.__init__(self) - self.item = item - - def parameters(self): - """Return the parameters used to configure this exporter.""" - raise Exception("Abstract method must be overridden in subclass.") - - def export(self, fileName=None, toBytes=False, copy=False): - """ - If *fileName* is None, pop-up a file dialog. - If *toBytes* is True, return a bytes object rather than writing to file. - If *copy* is True, export to the copy buffer rather than writing to file. - """ - raise Exception("Abstract method must be overridden in subclass.") - - def fileSaveDialog(self, filter=None, opts=None): - ## Show a file dialog, call self.export(fileName) when finished. - if opts is None: - opts = {} - self.fileDialog = FileDialog() - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - if filter is not None: - if isinstance(filter, basestring): - self.fileDialog.setNameFilter(filter) - elif isinstance(filter, list): - self.fileDialog.setNameFilters(filter) - global LastExportDirectory - exportDir = LastExportDirectory - if exportDir is not None: - self.fileDialog.setDirectory(exportDir) - self.fileDialog.show() - self.fileDialog.opts = opts - self.fileDialog.fileSelected.connect(self.fileSaveFinished) - return - - def fileSaveFinished(self, fileName): - fileName = asUnicode(fileName) - global LastExportDirectory - LastExportDirectory = os.path.split(fileName)[0] - - ## If file name does not match selected extension, append it now - ext = os.path.splitext(fileName)[1].lower().lstrip('.') - selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter())) - if selectedExt is not None: - selectedExt = selectedExt.groups()[0].lower() - if ext != selectedExt: - fileName = fileName + '.' + selectedExt.lstrip('.') - - self.export(fileName=fileName, **self.fileDialog.opts) - - def getScene(self): - if isinstance(self.item, GraphicsScene): - return self.item - else: - return self.item.scene() - - def getSourceRect(self): - if isinstance(self.item, GraphicsScene): - w = self.item.getViewWidget() - return w.viewportTransform().inverted()[0].mapRect(w.rect()) - else: - return self.item.sceneBoundingRect() - - def getTargetRect(self): - if isinstance(self.item, GraphicsScene): - return self.item.getViewWidget().rect() - else: - return self.item.mapRectToDevice(self.item.boundingRect()) - - def setExportMode(self, export, opts=None): - """ - Call setExportMode(export, opts) on all items that will - be painted during the export. This informs the item - that it is about to be painted for export, allowing it to - alter its appearance temporarily - - - *export* - bool; must be True before exporting and False afterward - *opts* - dict; common parameters are 'antialias' and 'background' - """ - if opts is None: - opts = {} - for item in self.getPaintItems(): - if hasattr(item, 'setExportMode'): - item.setExportMode(export, opts) - - def getPaintItems(self, root=None): - """Return a list of all items that should be painted in the correct order.""" - if root is None: - root = self.item - preItems = [] - postItems = [] - if isinstance(root, QtGui.QGraphicsScene): - childs = [i for i in root.items() if i.parentItem() is None] - rootItem = [] - else: - childs = root.childItems() - rootItem = [root] - childs.sort(key=lambda a: a.zValue()) - while len(childs) > 0: - ch = childs.pop(0) - tree = self.getPaintItems(ch) - if (ch.flags() & ch.ItemStacksBehindParent) or \ - (ch.zValue() < 0 and (ch.flags() & ch.ItemNegativeZStacksBehindParent)): - preItems.extend(tree) - else: - postItems.extend(tree) - - return preItems + rootItem + postItems - - def render(self, painter, targetRect, sourceRect, item=None): - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) diff --git a/pyqtgraph/exporters/HDF5Exporter.py b/pyqtgraph/exporters/HDF5Exporter.py deleted file mode 100644 index 83499a8..0000000 --- a/pyqtgraph/exporters/HDF5Exporter.py +++ /dev/null @@ -1,74 +0,0 @@ -from ..Qt import QtGui, QtCore -from .Exporter import Exporter -from ..parametertree import Parameter -from .. import PlotItem - -import numpy -try: - import h5py - HAVE_HDF5 = True -except ImportError: - HAVE_HDF5 = False - -translate = QtCore.QCoreApplication.translate - -__all__ = ['HDF5Exporter'] - - -class HDF5Exporter(Exporter): - Name = "HDF5 Export: plot (x,y)" - windows = [] - allowCopy = False - - def __init__(self, item): - Exporter.__init__(self, item) - self.params = Parameter(name='params', type='group', children=[ - {'name': 'Name', 'title': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export', }, - {'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', - 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}, - ]) - - def parameters(self): - return self.params - - def export(self, fileName=None): - if not HAVE_HDF5: - raise RuntimeError("This exporter requires the h5py package, " - "but it was not importable.") - - if not isinstance(self.item, PlotItem): - raise Exception("Must have a PlotItem selected for HDF5 export.") - - if fileName is None: - self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"]) - return - dsname = self.params['Name'] - fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" - data = [] - - appendAllX = self.params['columnMode'] == '(x,y) per plot' - # Check if the arrays are ragged - len_first = len(self.item.curves[0].getData()[0]) if self.item.curves[0] else None - ragged = any(len(i.getData()[0]) != len_first for i in self.item.curves) - - if ragged: - dgroup = fd.create_group(dsname) - for i, c in enumerate(self.item.curves): - d = c.getData() - fdata = numpy.array([d[0], d[1]]).astype('double') - cname = c.name() if c.name() is not None else str(i) - dset = dgroup.create_dataset(cname, data=fdata) - else: - for i, c in enumerate(self.item.curves): - d = c.getData() - if appendAllX or i == 0: - data.append(d[0]) - data.append(d[1]) - - fdata = numpy.array(data).astype('double') - dset = fd.create_dataset(dsname, data=fdata) - - fd.close() - -if HAVE_HDF5: - HDF5Exporter.register() diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py deleted file mode 100644 index 1e1ac10..0000000 --- a/pyqtgraph/exporters/ImageExporter.py +++ /dev/null @@ -1,119 +0,0 @@ -from .Exporter import Exporter -from ..parametertree import Parameter -from ..Qt import QtGui, QtCore, QtSvg, QT_LIB -from .. import functions as fn -import numpy as np - -translate = QtCore.QCoreApplication.translate -__all__ = ['ImageExporter'] - -class ImageExporter(Exporter): - Name = "Image File (PNG, TIF, JPG, ...)" - allowCopy = True - - def __init__(self, item): - Exporter.__init__(self, item) - tr = self.getTargetRect() - if isinstance(item, QtGui.QGraphicsItem): - scene = item.scene() - else: - scene = item - bgbrush = scene.views()[0].backgroundBrush() - bg = bgbrush.color() - if bgbrush.style() == QtCore.Qt.NoBrush: - bg.setAlpha(0) - - self.params = Parameter(name='params', type='group', children=[ - {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()), - 'limits': (0, None)}, - {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()), - 'limits': (0, None)}, - {'name': 'antialias', 'title': translate("Exporter", 'antialias'), 'type': 'bool', 'value': True}, - {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, - {'name': 'invertValue', 'title': translate("Exporter", 'invertValue'), 'type': 'bool', 'value': False} - ]) - self.params.param('width').sigValueChanged.connect(self.widthChanged) - self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = float(sr.height()) / sr.width() - self.params.param('height').setValue(int(self.params['width'] * ar), blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = float(sr.width()) / sr.height() - self.params.param('width').setValue(int(self.params['height'] * ar), blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - @staticmethod - def getSupportedImageFormats(): - filter = ["*."+f.data().decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] - preferred = ['*.png', '*.tif', '*.jpg'] - for p in preferred[::-1]: - if p in filter: - filter.remove(p) - filter.insert(0, p) - return filter - - def export(self, fileName=None, toBytes=False, copy=False): - if fileName is None and not toBytes and not copy: - filter = self.getSupportedImageFormats() - self.fileSaveDialog(filter=filter) - return - - w = int(self.params['width']) - h = int(self.params['height']) - if w == 0 or h == 0: - raise Exception("Cannot export image with size=0 (requested " - "export size is %dx%d)" % (w, h)) - - targetRect = QtCore.QRect(0, 0, w, h) - sourceRect = self.getSourceRect() - - bg = np.empty((h, w, 4), dtype=np.ubyte) - color = self.params['background'] - bg[:,:,0] = color.blue() - bg[:,:,1] = color.green() - bg[:,:,2] = color.red() - bg[:,:,3] = color.alpha() - - self.png = fn.makeQImage(bg, alpha=True, copy=False, transpose=False) - self.bg = bg - - ## set resolution of image: - origTargetRect = self.getTargetRect() - resolutionScale = targetRect.width() / origTargetRect.width() - #self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale) - #self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale) - - painter = QtGui.QPainter(self.png) - #dtr = painter.deviceTransform() - try: - self.setExportMode(True, { - 'antialias': self.params['antialias'], - 'background': self.params['background'], - 'painter': painter, - 'resolutionScale': resolutionScale}) - painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) - finally: - self.setExportMode(False) - painter.end() - - if self.params['invertValue']: - mn = bg[...,:3].min(axis=2) - mx = bg[...,:3].max(axis=2) - d = (255 - mx) - mn - bg[...,:3] += d[...,np.newaxis] - - if copy: - QtGui.QApplication.clipboard().setImage(self.png) - elif toBytes: - return self.png - else: - return self.png.save(fileName) - -ImageExporter.register() diff --git a/pyqtgraph/exporters/Matplotlib.py b/pyqtgraph/exporters/Matplotlib.py deleted file mode 100644 index b02b316..0000000 --- a/pyqtgraph/exporters/Matplotlib.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from .Exporter import Exporter -from .. import PlotItem -from .. import functions as fn - -__all__ = ['MatplotlibExporter'] - -""" -It is helpful when using the matplotlib Exporter if your -.matplotlib/matplotlibrc file is configured appropriately. -The following are suggested for getting usable PDF output that -can be edited in Illustrator, etc. - -backend : Qt4Agg -text.usetex : True # Assumes you have a findable LaTeX installation -interactive : False -font.family : sans-serif -font.sans-serif : 'Arial' # (make first in list) -mathtext.default : sf -figure.facecolor : white # personal preference -# next setting allows pdf font to be readable in Adobe Illustrator -pdf.fonttype : 42 # set fonts to TrueType (otherwise it will be 3 - # and the text will be vectorized. -text.dvipnghack : True # primarily to clean up font appearance on Mac - -The advantage is that there is less to do to get an exported file cleaned and ready for -publication. Fonts are not vectorized (outlined), and window colors are white. - -""" - -class MatplotlibExporter(Exporter): - Name = "Matplotlib Window" - windows = [] - - def __init__(self, item): - Exporter.__init__(self, item) - - def parameters(self): - return None - - def cleanAxes(self, axl): - if type(axl) is not list: - axl = [axl] - for ax in axl: - if ax is None: - continue - for loc, spine in ax.spines.items(): - if loc in ['left', 'bottom']: - pass - elif loc in ['right', 'top']: - spine.set_color('none') - # do not draw the spine - else: - raise ValueError('Unknown spine location: %s' % loc) - # turn off ticks when there is no spine - ax.xaxis.set_ticks_position('bottom') - - def export(self, fileName=None): - if not isinstance(self.item, PlotItem): - raise Exception("MatplotlibExporter currently only works with PlotItem") - - mpw = MatplotlibWindow() - MatplotlibExporter.windows.append(mpw) - - fig = mpw.getFigure() - - xax = self.item.getAxis('bottom') - yax = self.item.getAxis('left') - - # get labels from the graphic item - xlabel = xax.label.toPlainText() - ylabel = yax.label.toPlainText() - title = self.item.titleLabel.text - - # if axes use autoSIPrefix, scale the data so mpl doesn't add its own - # scale factor label - xscale = yscale = 1.0 - if xax.autoSIPrefix: - xscale = xax.autoSIPrefixScale - if yax.autoSIPrefix: - yscale = yax.autoSIPrefixScale - - ax = fig.add_subplot(111, title=title) - ax.clear() - self.cleanAxes(ax) - for item in self.item.curves: - x, y = item.getData() - x = x * xscale - y = y * yscale - - opts = item.opts - pen = fn.mkPen(opts['pen']) - if pen.style() == QtCore.Qt.NoPen: - linestyle = '' - else: - linestyle = '-' - color = tuple([c/255. for c in fn.colorTuple(pen.color())]) - symbol = opts['symbol'] - if symbol == 't': - symbol = '^' - symbolPen = fn.mkPen(opts['symbolPen']) - symbolBrush = fn.mkBrush(opts['symbolBrush']) - markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())]) - markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())]) - markersize = opts['symbolSize'] - - if opts['fillLevel'] is not None and opts['fillBrush'] is not None: - fillBrush = fn.mkBrush(opts['fillBrush']) - fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())]) - ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor) - - pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), - linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor, - markersize=markersize) - - xr, yr = self.item.viewRange() - ax.set_xbound(xr[0]*xscale, xr[1]*xscale) - ax.set_ybound(yr[0]*yscale, yr[1]*yscale) - - ax.set_xlabel(xlabel) # place the labels. - ax.set_ylabel(ylabel) - mpw.draw() - -MatplotlibExporter.register() - - -class MatplotlibWindow(QtGui.QMainWindow): - def __init__(self): - from ..widgets import MatplotlibWidget - QtGui.QMainWindow.__init__(self) - self.mpl = MatplotlibWidget.MatplotlibWidget() - self.setCentralWidget(self.mpl) - self.show() - - def __getattr__(self, attr): - return getattr(self.mpl, attr) - - def closeEvent(self, ev): - MatplotlibExporter.windows.remove(self) - self.deleteLater() diff --git a/pyqtgraph/exporters/PrintExporter.py b/pyqtgraph/exporters/PrintExporter.py deleted file mode 100644 index 1b9860b..0000000 --- a/pyqtgraph/exporters/PrintExporter.py +++ /dev/null @@ -1,72 +0,0 @@ -from .Exporter import Exporter -from ..parametertree import Parameter -from ..Qt import QtGui, QtCore, QtSvg -import re - -translate = QtCore.QCoreApplication.translate - -__all__ = ['PrintExporter'] -#__all__ = [] ## Printer is disabled for now--does not work very well. - -class PrintExporter(Exporter): - Name = "Printer" - def __init__(self, item): - Exporter.__init__(self, item) - tr = self.getTargetRect() - self.params = Parameter(name='params', type='group', children=[ - {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': 0.1, - 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, - {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', - 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, - ]) - self.params.param('width').sigValueChanged.connect(self.widthChanged) - self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = sr.height() / sr.width() - self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = sr.width() / sr.height() - self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - def export(self, fileName=None): - printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) - dialog = QtGui.QPrintDialog(printer) - dialog.setWindowTitle(translate('Exporter', "Print Document")) - if dialog.exec_() != QtGui.QDialog.Accepted: - return - - #dpi = QtGui.QDesktopWidget().physicalDpiX() - - #self.svg.setSize(QtCore.QSize(100,100)) - #self.svg.setResolution(600) - #res = printer.resolution() - sr = self.getSourceRect() - #res = sr.width() * .4 / (self.params['width'] * 100 / 2.54) - res = QtGui.QGuiApplication.primaryScreen().physicalDotsPerInchX() - printer.setResolution(res) - rect = printer.pageRect() - center = rect.center() - h = self.params['height'] * res * 100. / 2.54 - w = self.params['width'] * res * 100. / 2.54 - x = center.x() - w/2. - y = center.y() - h/2. - - targetRect = QtCore.QRect(x, y, w, h) - sourceRect = self.getSourceRect() - painter = QtGui.QPainter(printer) - try: - self.setExportMode(True, {'painter': painter}) - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) - finally: - self.setExportMode(False) - painter.end() - - -#PrintExporter.register() diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py deleted file mode 100644 index 465c452..0000000 --- a/pyqtgraph/exporters/SVGExporter.py +++ /dev/null @@ -1,463 +0,0 @@ -from .Exporter import Exporter -from ..python2_3 import asUnicode -from ..parametertree import Parameter -from ..Qt import QtGui, QtCore, QtSvg, QT_LIB -from .. import debug -from .. import functions as fn -import re -import xml.dom.minidom as xml -import numpy as np - -translate = QtCore.QCoreApplication.translate - -__all__ = ['SVGExporter'] - -class SVGExporter(Exporter): - Name = "Scalable Vector Graphics (SVG)" - allowCopy=True - - def __init__(self, item): - Exporter.__init__(self, item) - tr = self.getTargetRect() - - if isinstance(item, QtGui.QGraphicsItem): - scene = item.scene() - else: - scene = item - bgbrush = scene.views()[0].backgroundBrush() - bg = bgbrush.color() - if bgbrush.style() == QtCore.Qt.NoBrush: - bg.setAlpha(0) - - self.params = Parameter(name='params', type='group', children=[ - {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, - {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(), - 'limits': (0, None)}, - {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', 'value': tr.height(), - 'limits': (0, None)}, - #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, - #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, - {'name': 'scaling stroke', 'title': translate("Exporter", 'scaling stroke'), 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, " - "which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."}, - ]) - self.params.param('width').sigValueChanged.connect(self.widthChanged) - self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = sr.height() / sr.width() - self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = sr.width() / sr.height() - self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - def export(self, fileName=None, toBytes=False, copy=False): - if toBytes is False and copy is False and fileName is None: - self.fileSaveDialog(filter=f"{translate('Exporter', 'Scalable Vector Graphics')} (*.svg)") - return - - ## Qt's SVG generator is not complete. (notably, it lacks clipping) - ## Instead, we will use Qt to generate SVG for each item independently, - ## then manually reconstruct the entire document. - options = {ch.name():ch.value() for ch in self.params.children()} - options['background'] = self.params['background'] - options['width'] = self.params['width'] - options['height'] = self.params['height'] - xml = generateSvg(self.item, options) - - if toBytes: - return xml.encode('UTF-8') - elif copy: - md = QtCore.QMimeData() - md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) - QtGui.QApplication.clipboard().setMimeData(md) - else: - with open(fileName, 'wb') as fh: - fh.write(asUnicode(xml).encode('utf-8')) - -# Includes space for extra attributes -xmlHeader = """\ - - -pyqtgraph SVG export -Generated with Qt and pyqtgraph - -""" - -def generateSvg(item, options={}): - global xmlHeader - try: - node, defs = _generateItemSvg(item, options=options) - finally: - ## reset export mode for all items in the tree - if isinstance(item, QtGui.QGraphicsScene): - items = item.items() - else: - items = [item] - for i in items: - items.extend(i.childItems()) - for i in items: - if hasattr(i, 'setExportMode'): - i.setExportMode(False) - - cleanXml(node) - - defsXml = "\n" - for d in defs: - defsXml += d.toprettyxml(indent=' ') - defsXml += "\n" - svgAttributes = ' viewBox ="0 0 %f %f"' % (options["width"], options["height"]) - c = options['background'] - backgroundtag = '\n' % (c.red(), c.blue(), c.green(), c.alpha()/255.0) - return (xmlHeader % svgAttributes) + backgroundtag + defsXml + node.toprettyxml(indent=' ') + "\n\n" - - -def _generateItemSvg(item, nodes=None, root=None, options={}): - ## This function is intended to work around some issues with Qt's SVG generator - ## and SVG in general. - ## 1) Qt SVG does not implement clipping paths. This is absurd. - ## The solution is to let Qt generate SVG for each item independently, - ## then glue them together manually with clipping. - ## - ## The format Qt generates for all items looks like this: - ## - ## - ## - ## one or more of: or or - ## - ## - ## one or more of: or or - ## - ## . . . - ## - ## - ## 2) There seems to be wide disagreement over whether path strokes - ## should be scaled anisotropically. - ## see: http://web.mit.edu/jonas/www/anisotropy/ - ## Given that both inkscape and illustrator seem to prefer isotropic - ## scaling, we will optimize for those cases. - ## - ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but - ## inkscape only supports 1.1. - ## - ## Both 2 and 3 can be addressed by drawing all items in world coordinates. - - profiler = debug.Profiler() - - if nodes is None: ## nodes maps all node IDs to their XML element. - ## this allows us to ensure all elements receive unique names. - nodes = {} - - if root is None: - root = item - - ## Skip hidden items - if hasattr(item, 'isVisible') and not item.isVisible(): - return None - - ## If this item defines its own SVG generator, use that. - if hasattr(item, 'generateSvg'): - return item.generateSvg(nodes) - - - ## Generate SVG text for just this item (exclude its children; we'll handle them later) - tr = QtGui.QTransform() - if isinstance(item, QtGui.QGraphicsScene): - xmlStr = "\n\n" - doc = xml.parseString(xmlStr) - childs = [i for i in item.items() if i.parentItem() is None] - elif item.__class__.paint == QtGui.QGraphicsItem.paint: - xmlStr = "\n\n" - doc = xml.parseString(xmlStr) - childs = item.childItems() - else: - childs = item.childItems() - tr = itemTransform(item, item.scene()) - - ## offset to corner of root item - if isinstance(root, QtGui.QGraphicsScene): - rootPos = QtCore.QPoint(0,0) - else: - rootPos = root.scenePos() - tr2 = QtGui.QTransform() - tr2.translate(-rootPos.x(), -rootPos.y()) - tr = tr * tr2 - - arr = QtCore.QByteArray() - buf = QtCore.QBuffer(arr) - svg = QtSvg.QSvgGenerator() - svg.setOutputDevice(buf) - dpi = QtGui.QGuiApplication.primaryScreen().logicalDotsPerInchX() - svg.setResolution(int(dpi)) - - p = QtGui.QPainter() - p.begin(svg) - if hasattr(item, 'setExportMode'): - item.setExportMode(True, {'painter': p}) - try: - p.setTransform(tr) - opt = QtGui.QStyleOptionGraphicsItem() - if item.flags() & QtGui.QGraphicsItem.ItemUsesExtendedStyleOption: - opt.exposedRect = item.boundingRect() - item.paint(p, opt, None) - finally: - p.end() - ## Can't do this here--we need to wait until all children have painted as well. - ## this is taken care of in generateSvg instead. - #if hasattr(item, 'setExportMode'): - #item.setExportMode(False) - doc = xml.parseString(arr.data()) - - try: - ## Get top-level group for this item - g1 = doc.getElementsByTagName('g')[0] - ## get list of sub-groups - g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] - - defs = doc.getElementsByTagName('defs') - if len(defs) > 0: - defs = [n for n in defs[0].childNodes if isinstance(n, xml.Element)] - except: - print(doc.toxml()) - raise - - profiler('render') - - ## Get rid of group transformation matrices by applying - ## transformation to inner coordinates - correctCoordinates(g1, defs, item, options) - profiler('correct') - - ## decide on a name for this item - baseName = item.__class__.__name__ - i = 1 - while True: - name = baseName + "_%d" % i - if name not in nodes: - break - i += 1 - nodes[name] = g1 - g1.setAttribute('id', name) - - ## If this item clips its children, we need to take care of that. - childGroup = g1 ## add children directly to this node unless we are clipping - if not isinstance(item, QtGui.QGraphicsScene): - ## See if this item clips its children - if item.flags() & item.ItemClipsChildrenToShape: - ## Generate svg for just the path - path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) - item.scene().addItem(path) - try: - pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0] - # assume for this path is empty.. possibly problematic. - finally: - item.scene().removeItem(path) - - ## and for the clipPath element - clip = name + '_clip' - clipNode = g1.ownerDocument.createElement('clipPath') - clipNode.setAttribute('id', clip) - clipNode.appendChild(pathNode) - g1.appendChild(clipNode) - - childGroup = g1.ownerDocument.createElement('g') - childGroup.setAttribute('clip-path', 'url(#%s)' % clip) - g1.appendChild(childGroup) - profiler('clipping') - - ## Add all child items as sub-elements. - childs.sort(key=lambda c: c.zValue()) - for ch in childs: - csvg = _generateItemSvg(ch, nodes, root, options=options) - if csvg is None: - continue - cg, cdefs = csvg - childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) - defs.extend(cdefs) - - profiler('children') - return g1, defs - - -def correctCoordinates(node, defs, item, options): - # TODO: correct gradient coordinates inside defs - - ## Remove transformation matrices from tags by applying matrix to coordinates inside. - ## Each item is represented by a single top-level group with one or more groups inside. - ## Each inner group contains one or more drawing primitives, possibly of different types. - groups = node.getElementsByTagName('g') - - ## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart. - ## (if at some point we start correcting text transforms as well, then it should be safe to remove this) - groups2 = [] - for grp in groups: - subGroups = [grp.cloneNode(deep=False)] - textGroup = None - for ch in grp.childNodes[:]: - if isinstance(ch, xml.Element): - if textGroup is None: - textGroup = ch.tagName == 'text' - if ch.tagName == 'text': - if textGroup is False: - subGroups.append(grp.cloneNode(deep=False)) - textGroup = True - else: - if textGroup is True: - subGroups.append(grp.cloneNode(deep=False)) - textGroup = False - subGroups[-1].appendChild(ch) - groups2.extend(subGroups) - for sg in subGroups: - node.insertBefore(sg, grp) - node.removeChild(grp) - groups = groups2 - - - for grp in groups: - matrix = grp.getAttribute('transform') - match = re.match(r'matrix\((.*)\)', matrix) - if match is None: - vals = [1,0,0,1,0,0] - else: - vals = [float(a) for a in match.groups()[0].split(',')] - tr = np.array([[vals[0], vals[2], vals[4]], [vals[1], vals[3], vals[5]]]) - - removeTransform = False - for ch in grp.childNodes: - if not isinstance(ch, xml.Element): - continue - if ch.tagName == 'polyline': - removeTransform = True - coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')]) - coords = fn.transformCoordinates(tr, coords, transpose=True) - ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords])) - elif ch.tagName == 'path': - removeTransform = True - newCoords = '' - oldCoords = ch.getAttribute('d').strip() - if oldCoords == '': - continue - for c in oldCoords.split(' '): - x,y = c.split(',') - if x[0].isalpha(): - t = x[0] - x = x[1:] - else: - t = '' - nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True) - newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' ' - # If coords start with L instead of M, then the entire path will not be rendered. - # (This can happen if the first point had nan values in it--Qt will skip it on export) - if newCoords[0] != 'M': - newCoords = 'M' + newCoords[1:] - ch.setAttribute('d', newCoords) - elif ch.tagName == 'text': - removeTransform = False - ## leave text alone for now. Might need this later to correctly render text with outline. - #c = np.array([ - #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], - #[float(ch.getAttribute('font-size')), 0], - #[0,0]]) - #c = fn.transformCoordinates(tr, c, transpose=True) - #ch.setAttribute('x', str(c[0,0])) - #ch.setAttribute('y', str(c[0,1])) - #fs = c[1]-c[2] - #fs = (fs**2).sum()**0.5 - #ch.setAttribute('font-size', str(fs)) - - ## Correct some font information - families = ch.getAttribute('font-family').split(',') - if len(families) == 1: - font = QtGui.QFont(families[0].strip('" ')) - if font.styleHint() == font.StyleHint.SansSerif: - families.append('sans-serif') - elif font.styleHint() == font.StyleHint.Serif: - families.append('serif') - elif font.styleHint() == font.StyleHint.Courier: - families.append('monospace') - ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) - - ## correct line widths if needed - if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke' and grp.getAttribute('stroke-width') != '': - w = float(grp.getAttribute('stroke-width')) - s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True) - w = ((s[0]-s[1])**2).sum()**0.5 - ch.setAttribute('stroke-width', str(w)) - - # Remove non-scaling-stroke if requested - if options.get('scaling stroke') is True and ch.getAttribute('vector-effect') == 'non-scaling-stroke': - ch.removeAttribute('vector-effect') - - if removeTransform: - grp.removeAttribute('transform') - - -SVGExporter.register() - - -def itemTransform(item, root): - ## Return the transformation mapping item to root - ## (actually to parent coordinate system of root) - - if item is root: - tr = QtGui.QTransform() - tr.translate(*item.pos()) - tr = tr * item.transform() - return tr - - - if item.flags() & item.ItemIgnoresTransformations: - pos = item.pos() - parent = item.parentItem() - if parent is not None: - pos = itemTransform(parent, root).map(pos) - tr = QtGui.QTransform() - tr.translate(pos.x(), pos.y()) - tr = item.transform() * tr - else: - ## find next parent that is either the root item or - ## an item that ignores its transformation - nextRoot = item - while True: - nextRoot = nextRoot.parentItem() - if nextRoot is None: - nextRoot = root - break - if nextRoot is root or (nextRoot.flags() & nextRoot.ItemIgnoresTransformations): - break - - if isinstance(nextRoot, QtGui.QGraphicsScene): - tr = item.sceneTransform() - else: - tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0] - - return tr - - -def cleanXml(node): - ## remove extraneous text; let the xml library do the formatting. - hasElement = False - nonElement = [] - for ch in node.childNodes: - if isinstance(ch, xml.Element): - hasElement = True - cleanXml(ch) - else: - nonElement.append(ch) - - if hasElement: - for ch in nonElement: - node.removeChild(ch) - elif node.tagName == 'g': ## remove childless groups - node.parentNode.removeChild(node) diff --git a/pyqtgraph/exporters/__init__.py b/pyqtgraph/exporters/__init__.py deleted file mode 100644 index 62ab133..0000000 --- a/pyqtgraph/exporters/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .Exporter import Exporter -from .ImageExporter import * -from .SVGExporter import * -from .Matplotlib import * -from .CSVExporter import * -from .PrintExporter import * -from .HDF5Exporter import * - -def listExporters(): - return Exporter.Exporters[:] - diff --git a/pyqtgraph/exporters/__pycache__/CSVExporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/CSVExporter.cpython-36.pyc deleted file mode 100644 index dfea29a..0000000 Binary files a/pyqtgraph/exporters/__pycache__/CSVExporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/Exporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/Exporter.cpython-36.pyc deleted file mode 100644 index 38b9077..0000000 Binary files a/pyqtgraph/exporters/__pycache__/Exporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/HDF5Exporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/HDF5Exporter.cpython-36.pyc deleted file mode 100644 index c10c9f1..0000000 Binary files a/pyqtgraph/exporters/__pycache__/HDF5Exporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/ImageExporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/ImageExporter.cpython-36.pyc deleted file mode 100644 index b93c50a..0000000 Binary files a/pyqtgraph/exporters/__pycache__/ImageExporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/Matplotlib.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/Matplotlib.cpython-36.pyc deleted file mode 100644 index 7206c7b..0000000 Binary files a/pyqtgraph/exporters/__pycache__/Matplotlib.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/PrintExporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/PrintExporter.cpython-36.pyc deleted file mode 100644 index fe91c8b..0000000 Binary files a/pyqtgraph/exporters/__pycache__/PrintExporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/SVGExporter.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/SVGExporter.cpython-36.pyc deleted file mode 100644 index 247c693..0000000 Binary files a/pyqtgraph/exporters/__pycache__/SVGExporter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/exporters/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index e717e2d..0000000 Binary files a/pyqtgraph/exporters/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__init__.py b/pyqtgraph/exporters/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/exporters/tests/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 7c564ac..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__pycache__/test_csv.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/test_csv.cpython-36.pyc deleted file mode 100644 index 5ab471e..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/test_csv.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__pycache__/test_hdf5.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/test_hdf5.cpython-36.pyc deleted file mode 100644 index 4d56e1a..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/test_hdf5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__pycache__/test_image.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/test_image.cpython-36.pyc deleted file mode 100644 index 60da89f..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/test_image.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__pycache__/test_matplotlib.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/test_matplotlib.cpython-36.pyc deleted file mode 100644 index 2a0b374..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/test_matplotlib.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/__pycache__/test_svg.cpython-36.pyc b/pyqtgraph/exporters/tests/__pycache__/test_svg.cpython-36.pyc deleted file mode 100644 index 7153c0c..0000000 Binary files a/pyqtgraph/exporters/tests/__pycache__/test_svg.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/exporters/tests/test_csv.py b/pyqtgraph/exporters/tests/test_csv.py deleted file mode 100644 index 9cffc64..0000000 --- a/pyqtgraph/exporters/tests/test_csv.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -CSV export test -""" -from __future__ import division, print_function, absolute_import -import pyqtgraph as pg -import csv -import os -import tempfile - -app = pg.mkQApp() - - -def approxeq(a, b): - return (a-b) <= ((a + b) * 1e-6) - - -def test_CSVExporter(): - tempfilename = tempfile.NamedTemporaryFile(suffix='.csv').name - print("using %s as a temporary file" % tempfilename) - - plt = pg.plot() - y1 = [1,3,2,3,1,6,9,8,4,2] - plt.plot(y=y1, name='myPlot') - - y2 = [3,4,6,1,2,4,2,3,5,3,5,1,3] - x2 = pg.np.linspace(0, 1.0, len(y2)) - plt.plot(x=x2, y=y2) - - y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3] - x3 = pg.np.linspace(0, 1.0, len(y3)+1) - plt.plot(x=x3, y=y3, stepMode="center") - - ex = pg.exporters.CSVExporter(plt.plotItem) - ex.export(fileName=tempfilename) - - with open(tempfilename, 'r') as csv_file: - r = csv.reader(csv_file) - lines = [line for line in r] - header = lines.pop(0) - assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002'] - - i = 0 - for vals in lines: - vals = list(map(str.strip, vals)) - assert (i >= len(y1) and vals[0] == '') or approxeq(float(vals[0]), i) - assert (i >= len(y1) and vals[1] == '') or approxeq(float(vals[1]), y1[i]) - - assert (i >= len(x2) and vals[2] == '') or approxeq(float(vals[2]), x2[i]) - assert (i >= len(y2) and vals[3] == '') or approxeq(float(vals[3]), y2[i]) - - assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i]) - assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i]) - i += 1 - - os.unlink(tempfilename) - -if __name__ == '__main__': - test_CSVExporter() diff --git a/pyqtgraph/exporters/tests/test_hdf5.py b/pyqtgraph/exporters/tests/test_hdf5.py deleted file mode 100644 index 69bb8ae..0000000 --- a/pyqtgraph/exporters/tests/test_hdf5.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import pyqtgraph as pg -from pyqtgraph.exporters import HDF5Exporter -import numpy as np -from numpy.testing import assert_equal -import h5py -import os - - -@pytest.fixture -def tmp_h5(tmp_path): - yield tmp_path / "data.h5" - - -@pytest.mark.parametrize("combine", [False, True]) -def test_HDF5Exporter(tmp_h5, combine): - # Basic test of functionality: multiple curves with shared x array. Tests - # both options for stacking the data (columnMode). - x = np.linspace(0, 1, 100) - y1 = np.sin(x) - y2 = np.cos(x) - - plt = pg.plot() - plt.plot(x=x, y=y1) - plt.plot(x=x, y=y2) - - ex = HDF5Exporter(plt.plotItem) - - if combine: - ex.parameters()['columnMode'] = '(x,y,y,y) for all plots' - - ex.export(fileName=tmp_h5) - - with h5py.File(tmp_h5, 'r') as f: - # should be a single dataset with the name of the exporter - dset = f[ex.parameters()['Name']] - assert isinstance(dset, h5py.Dataset) - - if combine: - assert_equal(np.array([x, y1, y2]), dset) - else: - assert_equal(np.array([x, y1, x, y2]), dset) - - -def test_HDF5Exporter_unequal_lengths(tmp_h5): - # Test export with multiple curves of different size. The exporter should - # detect this and create multiple hdf5 datasets under a group. - x1 = np.linspace(0, 1, 10) - y1 = np.sin(x1) - x2 = np.linspace(0, 1, 100) - y2 = np.cos(x2) - - plt = pg.plot() - plt.plot(x=x1, y=y1, name='plot0') - plt.plot(x=x2, y=y2) - - ex = HDF5Exporter(plt.plotItem) - ex.export(fileName=tmp_h5) - - with h5py.File(tmp_h5, 'r') as f: - # should be a group with the name of the exporter - group = f[ex.parameters()['Name']] - assert isinstance(group, h5py.Group) - - # should be a dataset under the group with the name of the PlotItem - assert_equal(np.array([x1, y1]), group['plot0']) - - # should be a dataset under the group with a default name that's the - # index of the curve in the PlotItem - assert_equal(np.array([x2, y2]), group['1']) diff --git a/pyqtgraph/exporters/tests/test_image.py b/pyqtgraph/exporters/tests/test_image.py deleted file mode 100644 index 6f52ece..0000000 --- a/pyqtgraph/exporters/tests/test_image.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -import pyqtgraph as pg -from pyqtgraph.exporters import ImageExporter - -app = pg.mkQApp() - - -def test_ImageExporter_filename_dialog(): - """Tests ImageExporter code path that opens a file dialog. Regression test - for pull request 1133.""" - p = pg.plot() - exp = ImageExporter(p.getPlotItem()) - exp.export() diff --git a/pyqtgraph/exporters/tests/test_matplotlib.py b/pyqtgraph/exporters/tests/test_matplotlib.py deleted file mode 100644 index 3e3e98d..0000000 --- a/pyqtgraph/exporters/tests/test_matplotlib.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import pyqtgraph as pg -from pyqtgraph.exporters import MatplotlibExporter -pytest.importorskip("matplotlib") - -app = pg.mkQApp() - -skip_qt6 = pytest.mark.skipif( - pg.QT_LIB in ["PySide6", "PyQt6"], - reason= ( - "Matplotlib has no Qt6 support yet, " - "see https://github.com/matplotlib/matplotlib/pull/19255" - ) -) - - -@skip_qt6 -def test_MatplotlibExporter(): - plt = pg.plot() - - # curve item - plt.plot([0, 1, 2], [0, 1, 2]) - # scatter item - plt.plot([0, 1, 2], [1, 2, 3], pen=None, symbolBrush='r') - # curve + scatter - plt.plot([0, 1, 2], [2, 3, 4], pen='k', symbolBrush='r') - - exp = MatplotlibExporter(plt.getPlotItem()) - exp.export() - -@skip_qt6 -def test_MatplotlibExporter_nonplotitem(): - # attempting to export something other than a PlotItem raises an exception - plt = pg.plot() - plt.plot([0, 1, 2], [2, 3, 4]) - exp = MatplotlibExporter(plt.getPlotItem().getViewBox()) - with pytest.raises(Exception): - exp.export() - -@skip_qt6 -@pytest.mark.parametrize('scale', [1e10, 1e-9]) -def test_MatplotlibExporter_siscale(scale): - # coarse test to verify that plot data is scaled before export when - # autoSIPrefix is in effect (so mpl doesn't add its own multiplier label) - plt = pg.plot([0, 1, 2], [(i+1)*scale for i in range(3)]) - # set the label so autoSIPrefix works - plt.setLabel('left', 'magnitude') - exp = MatplotlibExporter(plt.getPlotItem()) - exp.export() - - mpw = MatplotlibExporter.windows[-1] - fig = mpw.getFigure() - ymin, ymax = fig.axes[0].get_ylim() - - if scale < 1: - assert ymax > scale - else: - assert ymax < scale diff --git a/pyqtgraph/exporters/tests/test_svg.py b/pyqtgraph/exporters/tests/test_svg.py deleted file mode 100644 index 91aadf1..0000000 --- a/pyqtgraph/exporters/tests/test_svg.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -SVG export test -""" -from __future__ import division, print_function, absolute_import -import pyqtgraph as pg -import tempfile -import os - - -app = pg.mkQApp() - - -def test_plotscene(): - tempfilename = tempfile.NamedTemporaryFile(suffix='.svg').name - print("using %s as a temporary file" % tempfilename) - pg.setConfigOption('foreground', (0,0,0)) - w = pg.GraphicsLayoutWidget() - w.show() - p1 = w.addPlot() - p2 = w.addPlot() - p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'}) - p1.setXRange(0,5) - p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3}) - app.processEvents() - app.processEvents() - - ex = pg.exporters.SVGExporter(w.scene()) - ex.export(fileName=tempfilename) - # clean up after the test is done - os.unlink(tempfilename) - w.close() - -def test_simple(): - tempfilename = tempfile.NamedTemporaryFile(suffix='.svg').name - print("using %s as a temporary file" % tempfilename) - - view = pg.GraphicsView() - view.show() - - scene = view.sceneObj - - rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100) - scene.addItem(rect) - rect.setPos(20,20) - tr = pg.QtGui.QTransform() - rect.setTransform(tr.translate(50, 50).rotate(30).scale(0.5, 0.5)) - - rect1 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100) - rect1.setParentItem(rect) - rect1.setFlag(rect1.ItemIgnoresTransformations) - rect1.setPos(20, 20) - rect1.setScale(2) - - el1 = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 100) - el1.setParentItem(rect1) - grp = pg.ItemGroup() - grp.setParentItem(rect) - tr = pg.QtGui.QTransform() - grp.setTransform(tr.translate(200, 0).rotate(30)) - - rect2 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 25) - rect2.setFlag(rect2.ItemClipsChildrenToShape) - rect2.setParentItem(grp) - rect2.setPos(0,25) - rect2.setRotation(30) - el = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 50) - tr = pg.QtGui.QTransform() - el.setTransform(tr.translate(10, -5).scale(0.5, 2)) - - el.setParentItem(rect2) - - grp2 = pg.ItemGroup() - scene.addItem(grp2) - grp2.setScale(100) - - rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2) - rect3.setPen(pg.mkPen(width=1, cosmetic=False)) - grp2.addItem(rect3) - - ex = pg.exporters.SVGExporter(scene) - ex.export(fileName=tempfilename) - os.unlink(tempfilename) diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py deleted file mode 100644 index 29d6ede..0000000 --- a/pyqtgraph/flowchart/Flowchart.py +++ /dev/null @@ -1,935 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui, QT_LIB -from .Node import * -from collections import OrderedDict -from ..widgets.TreeWidget import * -from .. import FileDialog, DataTreeWidget - -import importlib -FlowchartCtrlTemplate = importlib.import_module( - f'.FlowchartCtrlTemplate_{QT_LIB.lower()}', package=__package__) - -from .Terminal import Terminal -from numpy import ndarray -from .library import LIBRARY -from ..debug import printExc -from .. import configfile as configfile -from .. import dockarea as dockarea -from . import FlowchartGraphicsView -from .. import functions as fn -from ..python2_3 import asUnicode - -def strDict(d): - return dict([(str(k), v) for k, v in d.items()]) - - - - -class Flowchart(Node): - sigFileLoaded = QtCore.Signal(object) - sigFileSaved = QtCore.Signal(object) - - - #sigOutputChanged = QtCore.Signal() ## inherited from Node - sigChartLoaded = QtCore.Signal() - sigStateChanged = QtCore.Signal() # called when output is expected to have changed - sigChartChanged = QtCore.Signal(object, object, object) # called when nodes are added, removed, or renamed. - # (self, action, node) - - def __init__(self, terminals=None, name=None, filePath=None, library=None): - self.library = library or LIBRARY - if name is None: - name = "Flowchart" - if terminals is None: - terminals = {} - self.filePath = filePath - Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later - - - self.inputWasSet = False ## flag allows detection of changes in the absence of input change. - self._nodes = {} - self.nextZVal = 10 - #self.connects = [] - #self._chartGraphicsItem = FlowchartGraphicsItem(self) - self._widget = None - self._scene = None - self.processing = False ## flag that prevents recursive node updates - - self.widget() - - self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) - self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) - self.addNode(self.inputNode, 'Input', [-150, 0]) - self.addNode(self.outputNode, 'Output', [300, 0]) - - self.outputNode.sigOutputChanged.connect(self.outputChanged) - self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) - self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) - self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) - self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) - self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - - self.viewBox.autoRange(padding = 0.04) - - for name, opts in terminals.items(): - self.addTerminal(name, **opts) - - def setLibrary(self, lib): - self.library = lib - self.widget().chartWidget.buildMenu() - - def setInput(self, **args): - """Set the input values of the flowchart. This will automatically propagate - the new values throughout the flowchart, (possibly) causing the output to change. - """ - #print "setInput", args - #Node.setInput(self, **args) - #print " ....." - self.inputWasSet = True - self.inputNode.setOutput(**args) - - def outputChanged(self): - ## called when output of internal node has changed - vals = self.outputNode.inputValues() - self.widget().outputChanged(vals) - self.setOutput(**vals) - #self.sigOutputChanged.emit(self) - - def output(self): - """Return a dict of the values on the Flowchart's output terminals. - """ - return self.outputNode.inputValues() - - def nodes(self): - return self._nodes - - def addTerminal(self, name, **opts): - term = Node.addTerminal(self, name, **opts) - name = term.name() - if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node - opts['io'] = 'out' - opts['multi'] = False - self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) - try: - term2 = self.inputNode.addTerminal(name, **opts) - finally: - self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - - else: - opts['io'] = 'in' - #opts['multi'] = False - self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) - try: - term2 = self.outputNode.addTerminal(name, **opts) - finally: - self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - return term - - def removeTerminal(self, name): - #print "remove:", name - term = self[name] - inTerm = self.internalTerminal(term) - Node.removeTerminal(self, name) - inTerm.node().removeTerminal(inTerm.name()) - - def internalTerminalRenamed(self, term, oldName): - self[oldName].rename(term.name()) - - def internalTerminalAdded(self, node, term): - if term._io == 'in': - io = 'out' - else: - io = 'in' - Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) - - def internalTerminalRemoved(self, node, term): - try: - Node.removeTerminal(self, term.name()) - except KeyError: - pass - - def terminalRenamed(self, term, oldName): - newName = term.name() - #print "flowchart rename", newName, oldName - #print self.terminals - Node.terminalRenamed(self, self[oldName], oldName) - #print self.terminals - for n in [self.inputNode, self.outputNode]: - if oldName in n.terminals: - n[oldName].rename(newName) - - def createNode(self, nodeType, name=None, pos=None): - """Create a new Node and add it to this flowchart. - """ - if name is None: - n = 0 - while True: - name = "%s.%d" % (nodeType, n) - if name not in self._nodes: - break - n += 1 - - node = self.library.getNodeType(nodeType)(name) - self.addNode(node, name, pos) - return node - - def addNode(self, node, name, pos=None): - """Add an existing Node to this flowchart. - - See also: createNode() - """ - if pos is None: - pos = [0, 0] - if type(pos) in [QtCore.QPoint, QtCore.QPointF]: - pos = [pos.x(), pos.y()] - item = node.graphicsItem() - item.setZValue(self.nextZVal*2) - self.nextZVal += 1 - self.viewBox.addItem(item) - item.moveBy(*pos) - self._nodes[name] = node - if node is not self.inputNode and node is not self.outputNode: - self.widget().addNode(node) - node.sigClosed.connect(self.nodeClosed) - node.sigRenamed.connect(self.nodeRenamed) - node.sigOutputChanged.connect(self.nodeOutputChanged) - self.sigChartChanged.emit(self, 'add', node) - - def removeNode(self, node): - """Remove a Node from this flowchart. - """ - node.close() - - def nodeClosed(self, node): - del self._nodes[node.name()] - self.widget().removeNode(node) - for signal, slot in [('sigClosed', self.nodeClosed), - ('sigRenamed', self.nodeRenamed), - ('sigOutputChanged', self.nodeOutputChanged)]: - try: - getattr(node, signal).disconnect(slot) - except (TypeError, RuntimeError): - pass - self.sigChartChanged.emit(self, 'remove', node) - - def nodeRenamed(self, node, oldName): - del self._nodes[oldName] - self._nodes[node.name()] = node - self.widget().nodeRenamed(node, oldName) - self.sigChartChanged.emit(self, 'rename', node) - - def arrangeNodes(self): - pass - - def internalTerminal(self, term): - """If the terminal belongs to the external Node, return the corresponding internal terminal""" - if term.node() is self: - if term.isInput(): - return self.inputNode[term.name()] - else: - return self.outputNode[term.name()] - else: - return term - - def connectTerminals(self, term1, term2): - """Connect two terminals together within this flowchart.""" - term1 = self.internalTerminal(term1) - term2 = self.internalTerminal(term2) - term1.connectTo(term2) - - def process(self, **args): - """ - Process data through the flowchart, returning the output. - - Keyword arguments must be the names of input terminals. - The return value is a dict with one key per output terminal. - - """ - data = {} ## Stores terminal:value pairs - - ## determine order of operations - ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] - ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal - order = self.processOrder() - #print "ORDER:", order - - ## Record inputs given to process() - for n, t in self.inputNode.outputs().items(): - # if n not in args: - # raise Exception("Parameter %s required to process this chart." % n) - if n in args: - data[t] = args[n] - - ret = {} - - ## process all in order - for c, arg in order: - - if c == 'p': ## Process a single node - #print "===> process:", arg - node = arg - if node is self.inputNode: - continue ## input node has already been processed. - - - ## get input and output terminals for this node - outs = list(node.outputs().values()) - ins = list(node.inputs().values()) - - ## construct input value dictionary - args = {} - for inp in ins: - inputs = inp.inputTerminals() - if len(inputs) == 0: - continue - if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs - args[inp.name()] = dict([(i, data[i]) for i in inputs if i in data]) - else: ## single-inputs terminals only need the single input value available - args[inp.name()] = data[inputs[0]] - - if node is self.outputNode: - ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart - else: - try: - if node.isBypassed(): - result = node.processBypassed(args) - else: - result = node.process(display=False, **args) - except: - print("Error processing node %s. Args are: %s" % (str(node), str(args))) - raise - for out in outs: - #print " Output:", out, out.name() - #print out.name() - try: - data[out] = result[out.name()] - except KeyError: - pass - elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) - #print "===> delete", arg - if arg in data: - del data[arg] - - return ret - - def processOrder(self): - """Return the order of operations required to process this chart. - The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] - where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal - """ - - ## first collect list of nodes/terminals and their dependencies - deps = {} - tdeps = {} ## {terminal: [nodes that depend on terminal]} - for name, node in self._nodes.items(): - deps[node] = node.dependentNodes() - for t in node.outputs().values(): - tdeps[t] = t.dependentNodes() - - #print "DEPS:", deps - ## determine correct node-processing order - order = fn.toposort(deps) - #print "ORDER1:", order - - ## construct list of operations - ops = [('p', n) for n in order] - - ## determine when it is safe to delete terminal values - dels = [] - for t, nodes in tdeps.items(): - lastInd = 0 - lastNode = None - for n in nodes: ## determine which node is the last to be processed according to order - if n is self: - lastInd = None - break - else: - try: - ind = order.index(n) - except ValueError: - continue - if lastNode is None or ind > lastInd: - lastNode = n - lastInd = ind - if lastInd is not None: - dels.append((lastInd+1, t)) - dels.sort(key=lambda a: a[0], reverse=True) - for i, t in dels: - ops.insert(i, ('d', t)) - return ops - - - def nodeOutputChanged(self, startNode): - """Triggered when a node's output values have changed. (NOT called during process()) - Propagates new data forward through network.""" - ## first collect list of nodes/terminals and their dependencies - - if self.processing: - return - self.processing = True - try: - deps = {} - for name, node in self._nodes.items(): - deps[node] = [] - for t in node.outputs().values(): - deps[node].extend(t.dependentNodes()) - - ## determine order of updates - order = fn.toposort(deps, nodes=[startNode]) - order.reverse() - - ## keep track of terminals that have been updated - terms = set(startNode.outputs().values()) - - #print "======= Updating", startNode - # print("Order:", order) - for node in order[1:]: - # print("Processing node", node) - update = False - for term in list(node.inputs().values()): - # print(" checking terminal", term) - deps = list(term.connections().keys()) - for d in deps: - if d in terms: - # print(" ..input", d, "changed") - update |= True - term.inputChanged(d, process=False) - if update: - # print(" processing..") - node.update() - terms |= set(node.outputs().values()) - - finally: - self.processing = False - if self.inputWasSet: - self.inputWasSet = False - else: - self.sigStateChanged.emit() - - def chartGraphicsItem(self): - """Return the graphicsItem that displays the internal nodes and - connections of this flowchart. - - Note that the similar method `graphicsItem()` is inherited from Node - and returns the *external* graphical representation of this flowchart.""" - return self.viewBox - - def widget(self): - """Return the control widget for this flowchart. - - This widget provides GUI access to the parameters for each node and a - graphical representation of the flowchart. - """ - if self._widget is None: - self._widget = FlowchartCtrlWidget(self) - self.scene = self._widget.scene() - self.viewBox = self._widget.viewBox() - return self._widget - - def listConnections(self): - conn = set() - for n in self._nodes.values(): - terms = n.outputs() - for n, t in terms.items(): - for c in t.connections(): - conn.add((t, c)) - return conn - - def saveState(self): - """Return a serializable data structure representing the current state of this flowchart. - """ - state = Node.saveState(self) - state['nodes'] = [] - state['connects'] = [] - - for name, node in self._nodes.items(): - cls = type(node) - if hasattr(cls, 'nodeName'): - clsName = cls.nodeName - pos = node.graphicsItem().pos() - ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} - state['nodes'].append(ns) - - conn = self.listConnections() - for a, b in conn: - state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) - - state['inputNode'] = self.inputNode.saveState() - state['outputNode'] = self.outputNode.saveState() - - return state - - def restoreState(self, state, clear=False): - """Restore the state of this flowchart from a previous call to `saveState()`. - """ - self.blockSignals(True) - try: - if clear: - self.clear() - Node.restoreState(self, state) - nodes = state['nodes'] - nodes.sort(key=lambda a: a['pos'][0]) - for n in nodes: - if n['name'] in self._nodes: - self._nodes[n['name']].restoreState(n['state']) - continue - try: - node = self.createNode(n['class'], name=n['name']) - node.restoreState(n['state']) - except: - printExc("Error creating node %s: (continuing anyway)" % n['name']) - - self.inputNode.restoreState(state.get('inputNode', {})) - self.outputNode.restoreState(state.get('outputNode', {})) - - #self.restoreTerminals(state['terminals']) - for n1, t1, n2, t2 in state['connects']: - try: - self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) - except: - print(self._nodes[n1].terminals) - print(self._nodes[n2].terminals) - printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) - - finally: - self.blockSignals(False) - - self.outputChanged() - self.sigChartLoaded.emit() - self.sigStateChanged.emit() - - def loadFile(self, fileName=None, startDir=None): - """Load a flowchart (``*.fc``) file. - """ - if fileName is None: - if startDir is None: - startDir = self.filePath - if startDir is None: - startDir = '.' - self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") - self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.loadFile) - return - ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. - fileName = asUnicode(fileName) - state = configfile.readConfigFile(fileName) - self.restoreState(state, clear=True) - self.viewBox.autoRange() - self.sigFileLoaded.emit(fileName) - - def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): - """Save this flowchart to a .fc file - """ - if fileName is None: - if startDir is None: - startDir = self.filePath - if startDir is None: - startDir = '.' - self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") - self.fileDialog.setDefaultSuffix("fc") - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.saveFile) - return - fileName = asUnicode(fileName) - configfile.writeConfigFile(self.saveState(), fileName) - self.sigFileSaved.emit(fileName) - - def clear(self): - """Remove all nodes from this flowchart except the original input/output nodes. - """ - for n in list(self._nodes.values()): - if n is self.inputNode or n is self.outputNode: - continue - n.close() ## calls self.nodeClosed(n) by signal - #self.clearTerminals() - self.widget().clear() - - def clearTerminals(self): - Node.clearTerminals(self) - self.inputNode.clearTerminals() - self.outputNode.clearTerminals() - - -class FlowchartGraphicsItem(GraphicsObject): - - def __init__(self, chart): - GraphicsObject.__init__(self) - self.chart = chart ## chart is an instance of Flowchart() - self.updateTerminals() - - def updateTerminals(self): - self.terminals = {} - bounds = self.boundingRect() - inp = self.chart.inputs() - dy = bounds.height() / (len(inp)+1) - y = dy - for n, t in inp.items(): - item = t.graphicsItem() - self.terminals[n] = item - item.setParentItem(self) - item.setAnchor(bounds.width(), y) - y += dy - out = self.chart.outputs() - dy = bounds.height() / (len(out)+1) - y = dy - for n, t in out.items(): - item = t.graphicsItem() - self.terminals[n] = item - item.setParentItem(self) - item.setAnchor(0, y) - y += dy - - def boundingRect(self): - #print "FlowchartGraphicsItem.boundingRect" - return QtCore.QRectF() - - def paint(self, p, *args): - #print "FlowchartGraphicsItem.paint" - pass - #p.drawRect(self.boundingRect()) - - -class FlowchartCtrlWidget(QtGui.QWidget): - """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" - - def __init__(self, chart): - self.items = {} - #self.loadDir = loadDir ## where to look initially for chart files - self.currentFileName = None - QtGui.QWidget.__init__(self) - self.chart = chart - self.ui = FlowchartCtrlTemplate.Ui_Form() - self.ui.setupUi(self) - self.ui.ctrlList.setColumnCount(2) - #self.ui.ctrlList.setColumnWidth(0, 200) - self.ui.ctrlList.setColumnWidth(1, 20) - self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollPerPixel) - self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.chartWidget = FlowchartWidget(chart, self) - #self.chartWidget.viewBox().autoRange() - self.cwWin = QtGui.QMainWindow() - self.cwWin.setWindowTitle('Flowchart') - self.cwWin.setCentralWidget(self.chartWidget) - self.cwWin.resize(1000,800) - - h = self.ui.ctrlList.header() - h.setSectionResizeMode(0, h.ResizeMode.Stretch) - - self.ui.ctrlList.itemChanged.connect(self.itemChanged) - self.ui.loadBtn.clicked.connect(self.loadClicked) - self.ui.saveBtn.clicked.connect(self.saveClicked) - self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) - self.ui.showChartBtn.toggled.connect(self.chartToggled) - self.chart.sigFileLoaded.connect(self.setCurrentFile) - self.ui.reloadBtn.clicked.connect(self.reloadClicked) - self.chart.sigFileSaved.connect(self.fileSaved) - - - - #def resizeEvent(self, ev): - #QtGui.QWidget.resizeEvent(self, ev) - #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) - - def chartToggled(self, b): - if b: - self.cwWin.show() - else: - self.cwWin.hide() - - def reloadClicked(self): - try: - self.chartWidget.reloadLibrary() - self.ui.reloadBtn.success("Reloaded.") - except: - self.ui.reloadBtn.success("Error.") - raise - - - def loadClicked(self): - newFile = self.chart.loadFile() - #self.setCurrentFile(newFile) - - def fileSaved(self, fileName): - self.setCurrentFile(asUnicode(fileName)) - self.ui.saveBtn.success("Saved.") - - def saveClicked(self): - if self.currentFileName is None: - self.saveAsClicked() - else: - try: - self.chart.saveFile(self.currentFileName) - #self.ui.saveBtn.success("Saved.") - except: - self.ui.saveBtn.failure("Error") - raise - - def saveAsClicked(self): - try: - if self.currentFileName is None: - newFile = self.chart.saveFile() - else: - newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) - #self.ui.saveAsBtn.success("Saved.") - #print "Back to saveAsClicked." - except: - self.ui.saveBtn.failure("Error") - raise - - #self.setCurrentFile(newFile) - - def setCurrentFile(self, fileName): - self.currentFileName = asUnicode(fileName) - if fileName is None: - self.ui.fileNameLabel.setText("[ new ]") - else: - self.ui.fileNameLabel.setText("%s" % os.path.split(self.currentFileName)[1]) - self.resizeEvent(None) - - def itemChanged(self, *args): - pass - - def scene(self): - return self.chartWidget.scene() ## returns the GraphicsScene object - - def viewBox(self): - return self.chartWidget.viewBox() - - def nodeRenamed(self, node, oldName): - self.items[node].setText(0, node.name()) - - def addNode(self, node): - ctrl = node.ctrlWidget() - #if ctrl is None: - #return - item = QtGui.QTreeWidgetItem([node.name(), '', '']) - self.ui.ctrlList.addTopLevelItem(item) - byp = QtGui.QPushButton('X') - byp.setCheckable(True) - byp.setFixedWidth(20) - item.bypassBtn = byp - self.ui.ctrlList.setItemWidget(item, 1, byp) - byp.node = node - node.bypassButton = byp - byp.setChecked(node.isBypassed()) - byp.clicked.connect(self.bypassClicked) - - if ctrl is not None: - item2 = QtGui.QTreeWidgetItem() - item.addChild(item2) - self.ui.ctrlList.setItemWidget(item2, 0, ctrl) - - self.items[node] = item - - def removeNode(self, node): - if node in self.items: - item = self.items[node] - #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) - try: - item.bypassBtn.clicked.disconnect(self.bypassClicked) - except (TypeError, RuntimeError): - pass - self.ui.ctrlList.removeTopLevelItem(item) - - def bypassClicked(self): - btn = QtCore.QObject.sender(self) - btn.node.bypass(btn.isChecked()) - - def chartWidget(self): - return self.chartWidget - - def outputChanged(self, data): - pass - #self.ui.outputTree.setData(data, hideRoot=True) - - def clear(self): - self.chartWidget.clear() - - def select(self, node): - item = self.items[node] - self.ui.ctrlList.setCurrentItem(item) - - def clearSelection(self): - self.ui.ctrlList.selectionModel().clearSelection() - - -class FlowchartWidget(dockarea.DockArea): - """Includes the actual graphical flowchart and debugging interface""" - def __init__(self, chart, ctrl): - #QtGui.QWidget.__init__(self) - dockarea.DockArea.__init__(self) - self.chart = chart - self.ctrl = ctrl - self.hoverItem = None - #self.setMinimumWidth(250) - #self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)) - - #self.ui = FlowchartTemplate.Ui_Form() - #self.ui.setupUi(self) - - ## build user interface (it was easier to do it here than via developer) - self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) - self.viewDock = dockarea.Dock('view', size=(1000,600)) - self.viewDock.addWidget(self.view) - self.viewDock.hideTitleBar() - self.addDock(self.viewDock) - - - self.hoverText = QtGui.QTextEdit() - self.hoverText.setReadOnly(True) - self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) - self.hoverDock.addWidget(self.hoverText) - self.addDock(self.hoverDock, 'bottom') - - self.selInfo = QtGui.QWidget() - self.selInfoLayout = QtGui.QGridLayout() - self.selInfo.setLayout(self.selInfoLayout) - self.selDescLabel = QtGui.QLabel() - self.selNameLabel = QtGui.QLabel() - self.selDescLabel.setWordWrap(True) - self.selectedTree = DataTreeWidget() - #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - #self.selInfoLayout.addWidget(self.selNameLabel) - self.selInfoLayout.addWidget(self.selDescLabel) - self.selInfoLayout.addWidget(self.selectedTree) - self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) - self.selDock.addWidget(self.selInfo) - self.addDock(self.selDock, 'bottom') - - self._scene = self.view.scene() - self._viewBox = self.view.viewBox() - #self._scene = QtGui.QGraphicsScene() - #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() - #self.view.setScene(self._scene) - - self.buildMenu() - #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased - - self._scene.selectionChanged.connect(self.selectionChanged) - self._scene.sigMouseHover.connect(self.hoverOver) - #self.view.sigClicked.connect(self.showViewMenu) - #self._scene.sigSceneContextMenu.connect(self.showViewMenu) - #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) - - - def reloadLibrary(self): - #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) - self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) - self.nodeMenu = None - self.subMenus = [] - self.chart.library.reload() - self.buildMenu() - - def buildMenu(self, pos=None): - def buildSubMenu(node, rootMenu, subMenus, pos=None): - for section, node in node.items(): - if isinstance(node, OrderedDict): - menu = QtGui.QMenu(section) - rootMenu.addMenu(menu) - buildSubMenu(node, menu, subMenus, pos=pos) - subMenus.append(menu) - else: - act = rootMenu.addAction(section) - act.nodeType = section - act.pos = pos - self.nodeMenu = QtGui.QMenu() - self.subMenus = [] - buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos) - self.nodeMenu.triggered.connect(self.nodeMenuTriggered) - return self.nodeMenu - - def menuPosChanged(self, pos): - self.menuPos = pos - - def showViewMenu(self, ev): - #QtGui.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) - #if ev.button() == QtCore.Qt.RightButton: - #self.menuPos = self.view.mapToScene(ev.pos()) - #self.nodeMenu.popup(ev.globalPos()) - #print "Flowchart.showViewMenu called" - - #self.menuPos = ev.scenePos() - self.buildMenu(ev.scenePos()) - self.nodeMenu.popup(ev.screenPos()) - - def scene(self): - return self._scene ## the GraphicsScene item - - def viewBox(self): - return self._viewBox ## the viewBox that items should be added to - - def nodeMenuTriggered(self, action): - nodeType = action.nodeType - if action.pos is not None: - pos = action.pos - else: - pos = self.menuPos - pos = self.viewBox().mapSceneToView(pos) - - self.chart.createNode(nodeType, pos=pos) - - - def selectionChanged(self): - #print "FlowchartWidget.selectionChanged called." - items = self._scene.selectedItems() - #print " scene.selectedItems: ", items - if len(items) == 0: - data = None - else: - item = items[0] - if hasattr(item, 'node') and isinstance(item.node, Node): - n = item.node - if n in self.ctrl.items: - self.ctrl.select(n) - else: - self.ctrl.clearSelection() - data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} - self.selNameLabel.setText(n.name()) - if hasattr(n, 'nodeName'): - self.selDescLabel.setText("%s: %s" % (n.nodeName, n.__class__.__doc__)) - else: - self.selDescLabel.setText("") - if n.exception is not None: - data['exception'] = n.exception - else: - data = None - self.selectedTree.setData(data, hideRoot=True) - - def hoverOver(self, items): - #print "FlowchartWidget.hoverOver called." - term = None - for item in items: - if item is self.hoverItem: - return - self.hoverItem = item - if hasattr(item, 'term') and isinstance(item.term, Terminal): - term = item.term - break - if term is None: - self.hoverText.setPlainText("") - else: - val = term.value() - if isinstance(val, ndarray): - val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) - else: - val = str(val) - if len(val) > 400: - val = val[:400] + "..." - self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) - #self.hoverLabel.setCursorPosition(0) - - - - def clear(self): - #self.outputTree.setData(None) - self.selectedTree.setData(None) - self.hoverText.setPlainText('') - self.selNameLabel.setText('') - self.selDescLabel.setText('') - - -class FlowchartNode(Node): - pass diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py deleted file mode 100644 index 958f2aa..0000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt5.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui' -# -# Created: Wed Mar 26 15:09:28 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(217, 499) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setVerticalSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.loadBtn = QtWidgets.QPushButton(Form) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName("saveBtn") - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName("saveAsBtn") - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - self.reloadBtn.setObjectName("reloadBtn") - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - self.showChartBtn = QtWidgets.QPushButton(Form) - self.showChartBtn.setCheckable(True) - self.showChartBtn.setObjectName("showChartBtn") - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - self.ctrlList = TreeWidget(Form) - self.ctrlList.setObjectName("ctrlList") - self.ctrlList.headerItem().setText(0, "1") - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - self.fileNameLabel = QtWidgets.QLabel(Form) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setText("") - self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) - self.fileNameLabel.setObjectName("fileNameLabel") - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.loadBtn.setText(_translate("Form", "Load..")) - self.saveBtn.setText(_translate("Form", "Save")) - self.saveAsBtn.setText(_translate("Form", "As..")) - self.reloadBtn.setText(_translate("Form", "Reload Libs")) - self.showChartBtn.setText(_translate("Form", "Flowchart")) - -from ..widgets.FeedbackButton import FeedbackButton -from ..widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py deleted file mode 100644 index 5cb9199..0000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py +++ /dev/null @@ -1,65 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\flowchart\FlowchartCtrlTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(217, 499) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setVerticalSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.loadBtn = QtWidgets.QPushButton(Form) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName("saveBtn") - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName("saveAsBtn") - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - self.reloadBtn.setObjectName("reloadBtn") - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - self.showChartBtn = QtWidgets.QPushButton(Form) - self.showChartBtn.setCheckable(True) - self.showChartBtn.setObjectName("showChartBtn") - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - self.ctrlList = TreeWidget(Form) - self.ctrlList.setObjectName("ctrlList") - self.ctrlList.headerItem().setText(0, "1") - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - self.fileNameLabel = QtWidgets.QLabel(Form) - font = QtGui.QFont() - font.setBold(True) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setText("") - self.fileNameLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) - self.fileNameLabel.setObjectName("fileNameLabel") - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.loadBtn.setText(_translate("Form", "Load..")) - self.saveBtn.setText(_translate("Form", "Save")) - self.saveAsBtn.setText(_translate("Form", "As..")) - self.reloadBtn.setText(_translate("Form", "Reload Libs")) - self.showChartBtn.setText(_translate("Form", "Flowchart")) -from ..widgets.FeedbackButton import FeedbackButton -from ..widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside2.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside2.py deleted file mode 100644 index 2e7a7a0..0000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside2.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'FlowchartCtrlTemplate.ui' -# -# Created: Sun Sep 18 19:16:46 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(217, 499) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setVerticalSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.loadBtn = QtWidgets.QPushButton(Form) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName("saveBtn") - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName("saveAsBtn") - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - self.reloadBtn.setObjectName("reloadBtn") - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - self.showChartBtn = QtWidgets.QPushButton(Form) - self.showChartBtn.setCheckable(True) - self.showChartBtn.setObjectName("showChartBtn") - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - self.ctrlList = TreeWidget(Form) - self.ctrlList.setObjectName("ctrlList") - self.ctrlList.headerItem().setText(0, "1") - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - self.fileNameLabel = QtWidgets.QLabel(Form) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setText("") - self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) - self.fileNameLabel.setObjectName("fileNameLabel") - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) - self.loadBtn.setText(QtWidgets.QApplication.translate("Form", "Load..", None, -1)) - self.saveBtn.setText(QtWidgets.QApplication.translate("Form", "Save", None, -1)) - self.saveAsBtn.setText(QtWidgets.QApplication.translate("Form", "As..", None, -1)) - self.reloadBtn.setText(QtWidgets.QApplication.translate("Form", "Reload Libs", None, -1)) - self.showChartBtn.setText(QtWidgets.QApplication.translate("Form", "Flowchart", None, -1)) - -from ..widgets.FeedbackButton import FeedbackButton -from ..widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside6.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside6.py deleted file mode 100644 index 9a1e521..0000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside6.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'FlowchartCtrlTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from ..widgets.TreeWidget import TreeWidget -from ..widgets.FeedbackButton import FeedbackButton - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(217, 499) - self.gridLayout = QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName(u"gridLayout") - self.gridLayout.setVerticalSpacing(0) - self.loadBtn = QPushButton(Form) - self.loadBtn.setObjectName(u"loadBtn") - - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName(u"saveBtn") - - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName(u"saveAsBtn") - - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setObjectName(u"reloadBtn") - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - - self.showChartBtn = QPushButton(Form) - self.showChartBtn.setObjectName(u"showChartBtn") - self.showChartBtn.setCheckable(True) - - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - - self.ctrlList = TreeWidget(Form) - __qtreewidgetitem = QTreeWidgetItem() - __qtreewidgetitem.setText(0, u"1"); - self.ctrlList.setHeaderItem(__qtreewidgetitem) - self.ctrlList.setObjectName(u"ctrlList") - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - - self.fileNameLabel = QLabel(Form) - self.fileNameLabel.setObjectName(u"fileNameLabel") - font = QFont() - font.setBold(True) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setAlignment(Qt.AlignCenter) - - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.loadBtn.setText(QCoreApplication.translate("Form", u"Load..", None)) - self.saveBtn.setText(QCoreApplication.translate("Form", u"Save", None)) - self.saveAsBtn.setText(QCoreApplication.translate("Form", u"As..", None)) - self.reloadBtn.setText(QCoreApplication.translate("Form", u"Reload Libs", None)) - self.showChartBtn.setText(QCoreApplication.translate("Form", u"Flowchart", None)) - self.fileNameLabel.setText("") - # retranslateUi - diff --git a/pyqtgraph/flowchart/FlowchartGraphicsView.py b/pyqtgraph/flowchart/FlowchartGraphicsView.py deleted file mode 100644 index 2efd313..0000000 --- a/pyqtgraph/flowchart/FlowchartGraphicsView.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from ..widgets.GraphicsView import GraphicsView -from ..GraphicsScene import GraphicsScene -from ..graphicsItems.ViewBox import ViewBox - -translate = QtCore.QCoreApplication.translate - -class FlowchartGraphicsView(GraphicsView): - - sigHoverOver = QtCore.Signal(object) - sigClicked = QtCore.Signal(object) - - def __init__(self, widget, *args): - GraphicsView.__init__(self, *args, useOpenGL=False) - self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) - self.setCentralItem(self._vb) - self.setRenderHint(QtGui.QPainter.Antialiasing, True) - - def viewBox(self): - return self._vb - - -class FlowchartViewBox(ViewBox): - - def __init__(self, widget, *args, **kwargs): - ViewBox.__init__(self, *args, **kwargs) - self.widget = widget - - def getMenu(self, ev): - ## called by ViewBox to create a new context menu - self._fc_menu = QtGui.QMenu() - self._subMenus = self.getContextMenus(ev) - for menu in self._subMenus: - self._fc_menu.addMenu(menu) - return self._fc_menu - - def getContextMenus(self, ev): - ## called by scene to add menus on to someone else's context menu - menu = self.widget.buildMenu(ev.scenePos()) - menu.setTitle(translate("Context Menu", "Add node")) - return [menu, ViewBox.getMenu(self, ev)] diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py b/pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py deleted file mode 100644 index 448a00f..0000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyqt5.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui' -# -# Created: Wed Mar 26 15:09:28 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(529, 329) - self.selInfoWidget = QtWidgets.QWidget(Form) - self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) - self.selInfoWidget.setObjectName("selInfoWidget") - self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) - self.selDescLabel.setText("") - self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.selDescLabel.setWordWrap(True) - self.selDescLabel.setObjectName("selDescLabel") - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.selNameLabel.setFont(font) - self.selNameLabel.setText("") - self.selNameLabel.setObjectName("selNameLabel") - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - self.selectedTree = DataTreeWidget(self.selInfoWidget) - self.selectedTree.setObjectName("selectedTree") - self.selectedTree.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - self.hoverText = QtWidgets.QTextEdit(Form) - self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) - self.hoverText.setObjectName("hoverText") - self.view = FlowchartGraphicsView(Form) - self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) - self.view.setObjectName("view") - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - -from ..widgets.DataTreeWidget import DataTreeWidget -from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py b/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py deleted file mode 100644 index 7296a8d..0000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py +++ /dev/null @@ -1,53 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\flowchart\FlowchartTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(529, 329) - self.selInfoWidget = QtWidgets.QWidget(Form) - self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) - self.selInfoWidget.setObjectName("selInfoWidget") - self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) - self.selDescLabel.setText("") - self.selDescLabel.setAlignment(QtCore.Qt.Alignment.AlignLeading|QtCore.Qt.Alignment.AlignLeft|QtCore.Qt.Alignment.AlignTop) - self.selDescLabel.setWordWrap(True) - self.selDescLabel.setObjectName("selDescLabel") - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) - font = QtGui.QFont() - font.setBold(True) - self.selNameLabel.setFont(font) - self.selNameLabel.setText("") - self.selNameLabel.setObjectName("selNameLabel") - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - self.selectedTree = DataTreeWidget(self.selInfoWidget) - self.selectedTree.setObjectName("selectedTree") - self.selectedTree.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - self.hoverText = QtWidgets.QTextEdit(Form) - self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) - self.hoverText.setObjectName("hoverText") - self.view = FlowchartGraphicsView(Form) - self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) - self.view.setObjectName("view") - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) -from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView -from ..widgets.DataTreeWidget import DataTreeWidget diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyside2.py b/pyqtgraph/flowchart/FlowchartTemplate_pyside2.py deleted file mode 100644 index 2bca5f8..0000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyside2.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'FlowchartTemplate.ui' -# -# Created: Sun Sep 18 19:16:03 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(529, 329) - self.selInfoWidget = QtWidgets.QWidget(Form) - self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) - self.selInfoWidget.setObjectName("selInfoWidget") - self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) - self.selDescLabel.setText("") - self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.selDescLabel.setWordWrap(True) - self.selDescLabel.setObjectName("selDescLabel") - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.selNameLabel.setFont(font) - self.selNameLabel.setText("") - self.selNameLabel.setObjectName("selNameLabel") - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - self.selectedTree = DataTreeWidget(self.selInfoWidget) - self.selectedTree.setObjectName("selectedTree") - self.selectedTree.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - self.hoverText = QtWidgets.QTextEdit(Form) - self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) - self.hoverText.setObjectName("hoverText") - self.view = FlowchartGraphicsView(Form) - self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) - self.view.setObjectName("view") - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) - -from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView -from ..widgets.DataTreeWidget import DataTreeWidget diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyside6.py b/pyqtgraph/flowchart/FlowchartTemplate_pyside6.py deleted file mode 100644 index a45b6e0..0000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyside6.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'FlowchartTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from ..widgets.DataTreeWidget import DataTreeWidget -from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(529, 329) - self.selInfoWidget = QWidget(Form) - self.selInfoWidget.setObjectName(u"selInfoWidget") - self.selInfoWidget.setGeometry(QRect(260, 10, 264, 222)) - self.gridLayout = QGridLayout(self.selInfoWidget) - self.gridLayout.setObjectName(u"gridLayout") - self.selDescLabel = QLabel(self.selInfoWidget) - self.selDescLabel.setObjectName(u"selDescLabel") - self.selDescLabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop) - self.selDescLabel.setWordWrap(True) - - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - - self.selNameLabel = QLabel(self.selInfoWidget) - self.selNameLabel.setObjectName(u"selNameLabel") - font = QFont() - font.setBold(True) - self.selNameLabel.setFont(font) - - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - - self.selectedTree = DataTreeWidget(self.selInfoWidget) - __qtreewidgetitem = QTreeWidgetItem() - __qtreewidgetitem.setText(0, u"1"); - self.selectedTree.setHeaderItem(__qtreewidgetitem) - self.selectedTree.setObjectName(u"selectedTree") - - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - - self.hoverText = QTextEdit(Form) - self.hoverText.setObjectName(u"hoverText") - self.hoverText.setGeometry(QRect(0, 240, 521, 81)) - self.view = FlowchartGraphicsView(Form) - self.view.setObjectName(u"view") - self.view.setGeometry(QRect(0, 0, 256, 192)) - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.selDescLabel.setText("") - self.selNameLabel.setText("") - # retranslateUi - diff --git a/pyqtgraph/flowchart/Node.py b/pyqtgraph/flowchart/Node.py deleted file mode 100644 index d28f89a..0000000 --- a/pyqtgraph/flowchart/Node.py +++ /dev/null @@ -1,666 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui, QtWidgets -from ..graphicsItems.GraphicsObject import GraphicsObject -from .. import functions as fn -from .Terminal import * -from collections import OrderedDict -from ..debug import * -import numpy as np -import warnings - -translate = QtCore.QCoreApplication.translate - -def strDict(d): - return dict([(str(k), v) for k, v in d.items()]) - -class Node(QtCore.QObject): - """ - Node represents the basic processing unit of a flowchart. - A Node subclass implements at least: - - 1) A list of input / ouptut terminals and their properties - 2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys. - - A flowchart thus consists of multiple instances of Node subclasses, each of which is connected - to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node. - This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself. - - Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """ - - sigOutputChanged = QtCore.Signal(object) # self - sigClosed = QtCore.Signal(object) - sigRenamed = QtCore.Signal(object, object) - sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName - sigTerminalAdded = QtCore.Signal(object, object) # self, term - sigTerminalRemoved = QtCore.Signal(object, object) # self, term - - - def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): - """ - ============== ============================================================ - **Arguments:** - name The name of this specific node instance. It can be any - string, but must be unique within a flowchart. Usually, - we simply let the flowchart decide on a name when calling - Flowchart.addNode(...) - terminals Dict-of-dicts specifying the terminals present on this Node. - Terminal specifications look like:: - - 'inputTerminalName': {'io': 'in'} - 'outputTerminalName': {'io': 'out'} - - There are a number of optional parameters for terminals: - multi, pos, renamable, removable, multiable, bypass. See - the Terminal class for more information. - allowAddInput bool; whether the user is allowed to add inputs by the - context menu. - allowAddOutput bool; whether the user is allowed to add outputs by the - context menu. - allowRemove bool; whether the user is allowed to remove this node by the - context menu. - ============== ============================================================ - - """ - QtCore.QObject.__init__(self) - self._name = name - self._bypass = False - self.bypassButton = None ## this will be set by the flowchart ctrl widget.. - self._graphicsItem = None - self.terminals = OrderedDict() - self._inputs = OrderedDict() - self._outputs = OrderedDict() - self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals - self._allowAddOutput = allowAddOutput - self._allowRemove = allowRemove - - self.exception = None - if terminals is None: - return - for name, opts in terminals.items(): - self.addTerminal(name, **opts) - - - def nextTerminalName(self, name): - """Return an unused terminal name""" - name2 = name - i = 1 - while name2 in self.terminals: - name2 = "%s.%d" % (name, i) - i += 1 - return name2 - - def addInput(self, name="Input", **args): - """Add a new input terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - This is a convenience function that just calls addTerminal(io='in', ...)""" - #print "Node.addInput called." - return self.addTerminal(name, io='in', **args) - - def addOutput(self, name="Output", **args): - """Add a new output terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - This is a convenience function that just calls addTerminal(io='out', ...)""" - return self.addTerminal(name, io='out', **args) - - def removeTerminal(self, term): - """Remove the specified terminal from this Node. May specify either the - terminal's name or the terminal itself. - - Causes sigTerminalRemoved to be emitted.""" - if isinstance(term, Terminal): - name = term.name() - else: - name = term - term = self.terminals[name] - - #print "remove", name - #term.disconnectAll() - term.close() - del self.terminals[name] - if name in self._inputs: - del self._inputs[name] - if name in self._outputs: - del self._outputs[name] - self.graphicsItem().updateTerminals() - self.sigTerminalRemoved.emit(self, term) - - - def terminalRenamed(self, term, oldName): - """Called after a terminal has been renamed - - Causes sigTerminalRenamed to be emitted.""" - newName = term.name() - for d in [self.terminals, self._inputs, self._outputs]: - if oldName not in d: - continue - d[newName] = d[oldName] - del d[oldName] - - self.graphicsItem().updateTerminals() - self.sigTerminalRenamed.emit(term, oldName) - - def addTerminal(self, name, **opts): - """Add a new terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - Causes sigTerminalAdded to be emitted.""" - name = self.nextTerminalName(name) - term = Terminal(self, name, **opts) - self.terminals[name] = term - if term.isInput(): - self._inputs[name] = term - elif term.isOutput(): - self._outputs[name] = term - self.graphicsItem().updateTerminals() - self.sigTerminalAdded.emit(self, term) - return term - - - def inputs(self): - """Return dict of all input terminals. - Warning: do not modify.""" - return self._inputs - - def outputs(self): - """Return dict of all output terminals. - Warning: do not modify.""" - return self._outputs - - def process(self, **kargs): - """Process data through this node. This method is called any time the flowchart - wants the node to process data. It will be called with one keyword argument - corresponding to each input terminal, and must return a dict mapping the name - of each output terminal to its new value. - - This method is also called with a 'display' keyword argument, which indicates - whether the node should update its display (if it implements any) while processing - this data. This is primarily used to disable expensive display operations - during batch processing. - """ - return {} - - def graphicsItem(self): - """Return the GraphicsItem for this node. Subclasses may re-implement - this method to customize their appearance in the flowchart.""" - if self._graphicsItem is None: - self._graphicsItem = NodeGraphicsItem(self) - return self._graphicsItem - - ## this is just bad planning. Causes too many bugs. - def __getattr__(self, attr): - """Return the terminal with the given name""" - warnings.warn( - "Use of note.terminalName is deprecated, use node['terminalName'] instead" - "Will be removed from 0.13.0", - DeprecationWarning, stacklevel=2 - ) - - if attr not in self.terminals: - raise AttributeError(attr) - else: - import traceback - traceback.print_stack() - print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.") - return self.terminals[attr] - - def __getitem__(self, item): - #return getattr(self, item) - """Return the terminal with the given name""" - if item not in self.terminals: - raise KeyError(item) - else: - return self.terminals[item] - - def name(self): - """Return the name of this node.""" - return self._name - - def rename(self, name): - """Rename this node. This will cause sigRenamed to be emitted.""" - oldName = self._name - self._name = name - #self.emit(QtCore.SIGNAL('renamed'), self, oldName) - self.sigRenamed.emit(self, oldName) - - def dependentNodes(self): - """Return the list of nodes which provide direct input to this node""" - nodes = set() - for t in self.inputs().values(): - nodes |= set([i.node() for i in t.inputTerminals()]) - return nodes - #return set([t.inputTerminals().node() for t in self.listInputs().values()]) - - def __repr__(self): - return "" % (self.name(), id(self)) - - def ctrlWidget(self): - """Return this Node's control widget. - - By default, Nodes have no control widget. Subclasses may reimplement this - method to provide a custom widget. This method is called by Flowcharts - when they are constructing their Node list.""" - return None - - def bypass(self, byp): - """Set whether this node should be bypassed. - - When bypassed, a Node's process() method is never called. In some cases, - data is automatically copied directly from specific input nodes to - output nodes instead (see the bypass argument to Terminal.__init__). - This is usually called when the user disables a node from the flowchart - control panel. - """ - self._bypass = byp - if self.bypassButton is not None: - self.bypassButton.setChecked(byp) - self.update() - - def isBypassed(self): - """Return True if this Node is currently bypassed.""" - return self._bypass - - def setInput(self, **args): - """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. - This is normally only used for nodes with no connected inputs.""" - changed = False - for k, v in args.items(): - term = self._inputs[k] - oldVal = term.value() - if not fn.eq(oldVal, v): - changed = True - term.setValue(v, process=False) - if changed and '_updatesHandled_' not in args: - self.update() - - def inputValues(self): - """Return a dict of all input values currently assigned to this node.""" - vals = {} - for n, t in self.inputs().items(): - vals[n] = t.value() - return vals - - def outputValues(self): - """Return a dict of all output values currently generated by this node.""" - vals = {} - for n, t in self.outputs().items(): - vals[n] = t.value() - return vals - - def connected(self, localTerm, remoteTerm): - """Called whenever one of this node's terminals is connected elsewhere.""" - pass - - def disconnected(self, localTerm, remoteTerm): - """Called whenever one of this node's terminals is disconnected from another.""" - pass - - def update(self, signal=True): - """Collect all input values, attempt to process new output values, and propagate downstream. - Subclasses should call update() whenever thir internal state has changed - (such as when the user interacts with the Node's control widget). Update - is automatically called when the inputs to the node are changed. - """ - vals = self.inputValues() - #print " inputs:", vals - try: - if self.isBypassed(): - out = self.processBypassed(vals) - else: - out = self.process(**strDict(vals)) - #print " output:", out - if out is not None: - if signal: - self.setOutput(**out) - else: - self.setOutputNoSignal(**out) - for n,t in self.inputs().items(): - t.setValueAcceptable(True) - self.clearException() - except: - #printExc( "Exception while processing %s:" % self.name()) - for n,t in self.outputs().items(): - t.setValue(None) - self.setException(sys.exc_info()) - - if signal: - #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data - self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data - - def processBypassed(self, args): - """Called when the flowchart would normally call Node.process, but this node is currently bypassed. - The default implementation looks for output terminals with a bypass connection and returns the - corresponding values. Most Node subclasses will _not_ need to reimplement this method.""" - result = {} - for term in list(self.outputs().values()): - byp = term.bypassValue() - if byp is None: - result[term.name()] = None - else: - result[term.name()] = args.get(byp, None) - return result - - def setOutput(self, **vals): - self.setOutputNoSignal(**vals) - #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data - self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data - - def setOutputNoSignal(self, **vals): - for k, v in vals.items(): - term = self.outputs()[k] - term.setValue(v) - #targets = term.connections() - #for t in targets: ## propagate downstream - #if t is term: - #continue - #t.inputChanged(term) - term.setValueAcceptable(True) - - def setException(self, exc): - self.exception = exc - self.recolor() - - def clearException(self): - self.setException(None) - - def recolor(self): - if self.exception is None: - self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) - else: - self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) - - def saveState(self): - """Return a dictionary representing the current state of this node - (excluding input / output values). This is used for saving/reloading - flowcharts. The default implementation returns this Node's position, - bypass state, and information about each of its terminals. - - Subclasses may want to extend this method, adding extra keys to the returned - dict.""" - pos = self.graphicsItem().pos() - state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} - termsEditable = self._allowAddInput | self._allowAddOutput - for term in list(self._inputs.values()) + list(self._outputs.values()): - termsEditable |= term._renamable | term._removable | term._multiable - if termsEditable: - state['terminals'] = self.saveTerminals() - return state - - def restoreState(self, state): - """Restore the state of this node from a structure previously generated - by saveState(). """ - pos = state.get('pos', (0,0)) - self.graphicsItem().setPos(*pos) - self.bypass(state.get('bypass', False)) - if 'terminals' in state: - self.restoreTerminals(state['terminals']) - - def saveTerminals(self): - terms = OrderedDict() - for n, t in self.terminals.items(): - terms[n] = (t.saveState()) - return terms - - def restoreTerminals(self, state): - for name in list(self.terminals.keys()): - if name not in state: - self.removeTerminal(name) - for name, opts in state.items(): - if name in self.terminals: - term = self[name] - term.setOpts(**opts) - continue - try: - opts = strDict(opts) - self.addTerminal(name, **opts) - except: - printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) - - - def clearTerminals(self): - for t in self.terminals.values(): - t.close() - self.terminals = OrderedDict() - self._inputs = OrderedDict() - self._outputs = OrderedDict() - - def close(self): - """Cleans up after the node--removes terminals, graphicsItem, widget""" - self.disconnectAll() - self.clearTerminals() - item = self.graphicsItem() - if item.scene() is not None: - item.scene().removeItem(item) - self._graphicsItem = None - w = self.ctrlWidget() - if w is not None: - w.setParent(None) - #self.emit(QtCore.SIGNAL('closed'), self) - self.sigClosed.emit(self) - - def disconnectAll(self): - for t in self.terminals.values(): - t.disconnectAll() - - -class TextItem(QtWidgets.QGraphicsTextItem): - def __init__(self, text, parent, on_update): - super().__init__(text, parent) - self.on_update = on_update - - def focusOutEvent(self, ev): - super().focusOutEvent(ev) - if self.on_update is not None: - self.on_update() - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - if self.on_update is not None: - self.on_update() - return - super().keyPressEvent(ev) - - -#class NodeGraphicsItem(QtGui.QGraphicsItem): -class NodeGraphicsItem(GraphicsObject): - def __init__(self, node): - #QtGui.QGraphicsItem.__init__(self) - GraphicsObject.__init__(self) - #QObjectWorkaround.__init__(self) - - #self.shadow = QtGui.QGraphicsDropShadowEffect() - #self.shadow.setOffset(5,5) - #self.shadow.setBlurRadius(10) - #self.setGraphicsEffect(self.shadow) - - self.pen = fn.mkPen(0,0,0) - self.selectPen = fn.mkPen(200,200,200,width=2) - self.brush = fn.mkBrush(200, 200, 200, 150) - self.hoverBrush = fn.mkBrush(200, 200, 200, 200) - self.selectBrush = fn.mkBrush(200, 200, 255, 200) - self.hovered = False - - self.node = node - flags = self.ItemIsMovable | self.ItemIsSelectable | self.ItemIsFocusable |self.ItemSendsGeometryChanges - #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges - - self.setFlags(flags) - self.bounds = QtCore.QRectF(0, 0, 100, 100) - self.nameItem = TextItem(self.node.name(), self, self.labelChanged) - self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) - self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) - self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) - self.updateTerminals() - #self.setZValue(10) - - self.menu = None - self.buildMenu() - - #self.node.sigTerminalRenamed.connect(self.updateActionMenu) - - #def setZValue(self, z): - #for t, item in self.terminals.values(): - #item.setZValue(z+1) - #GraphicsObject.setZValue(self, z) - - def labelChanged(self): - newName = str(self.nameItem.toPlainText()) - if newName != self.node.name(): - self.node.rename(newName) - - ### re-center the label - bounds = self.boundingRect() - self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) - - def setPen(self, *args, **kwargs): - self.pen = fn.mkPen(*args, **kwargs) - self.update() - - def setBrush(self, brush): - self.brush = brush - self.update() - - - def updateTerminals(self): - self.terminals = {} - inp = self.node.inputs() - out = self.node.outputs() - - maxNode = max(len(inp), len(out)) - titleOffset = 25 - nodeOffset = 12 - - # calculate new height - newHeight = titleOffset+maxNode*nodeOffset - - # if current height is not equal to new height, update - if not self.bounds.height() == newHeight: - self.bounds.setHeight(newHeight) - self.update() - - # Populate inputs - y = titleOffset - for i, t in inp.items(): - item = t.graphicsItem() - item.setParentItem(self) - #item.setZValue(self.zValue()+1) - item.setAnchor(0, y) - self.terminals[i] = (t, item) - y += nodeOffset - - # Populate inputs - y = titleOffset - for i, t in out.items(): - item = t.graphicsItem() - item.setParentItem(self) - item.setZValue(self.zValue()) - item.setAnchor(self.bounds.width(), y) - self.terminals[i] = (t, item) - y += nodeOffset - - #self.buildMenu() - - - def boundingRect(self): - return self.bounds.adjusted(-5, -5, 5, 5) - - def paint(self, p, *args): - - p.setPen(self.pen) - if self.isSelected(): - p.setPen(self.selectPen) - p.setBrush(self.selectBrush) - else: - p.setPen(self.pen) - if self.hovered: - p.setBrush(self.hoverBrush) - else: - p.setBrush(self.brush) - - p.drawRect(self.bounds) - - - def mousePressEvent(self, ev): - ev.ignore() - - - def mouseClickEvent(self, ev): - #print "Node.mouseClickEvent called." - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - #print " ev.button: left" - sel = self.isSelected() - #ret = QtGui.QGraphicsItem.mousePressEvent(self, ev) - self.setSelected(True) - if not sel and self.isSelected(): - #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) - #self.emit(QtCore.SIGNAL('selected')) - #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically - self.update() - #return ret - - elif ev.button() == QtCore.Qt.RightButton: - #print " ev.button: right" - ev.accept() - #pos = ev.screenPos() - self.raiseContextMenu(ev) - #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def mouseDragEvent(self, ev): - #print "Node.mouseDrag" - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) - - def hoverEvent(self, ev): - if not ev.isExit() and ev.acceptClicks(QtCore.Qt.LeftButton): - ev.acceptDrags(QtCore.Qt.LeftButton) - self.hovered = True - else: - self.hovered = False - self.update() - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: - ev.accept() - if not self.node._allowRemove: - return - self.node.close() - else: - ev.ignore() - - def itemChange(self, change, val): - if change == self.ItemPositionHasChanged: - for k, t in self.terminals.items(): - t[1].nodeMoved() - return GraphicsObject.itemChange(self, change, val) - - - def getMenu(self): - return self.menu - - def raiseContextMenu(self, ev): - menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def buildMenu(self): - self.menu = QtGui.QMenu() - self.menu.setTitle(translate("Context Menu", "Node")) - a = self.menu.addAction(translate("Context Menu","Add input"), self.addInputFromMenu) - if not self.node._allowAddInput: - a.setEnabled(False) - a = self.menu.addAction(translate("Context Menu", "Add output"), self.addOutputFromMenu) - if not self.node._allowAddOutput: - a.setEnabled(False) - a = self.menu.addAction(translate("Context Menu", "Remove node"), self.node.close) - if not self.node._allowRemove: - a.setEnabled(False) - - def addInputFromMenu(self): ## called when add input is clicked in context menu - self.node.addInput(renamable=True, removable=True, multiable=True) - - def addOutputFromMenu(self): ## called when add output is clicked in context menu - self.node.addOutput(renamable=True, removable=True, multiable=False) - diff --git a/pyqtgraph/flowchart/NodeLibrary.py b/pyqtgraph/flowchart/NodeLibrary.py deleted file mode 100644 index 3484b2b..0000000 --- a/pyqtgraph/flowchart/NodeLibrary.py +++ /dev/null @@ -1,86 +0,0 @@ -from collections import OrderedDict -from .Node import Node - -def isNodeClass(cls): - try: - if not issubclass(cls, Node): - return False - except: - return False - return hasattr(cls, 'nodeName') - - - -class NodeLibrary: - """ - A library of flowchart Node types. Custom libraries may be built to provide - each flowchart with a specific set of allowed Node types. - """ - - def __init__(self): - self.nodeList = OrderedDict() - self.nodeTree = OrderedDict() - - def addNodeType(self, nodeClass, paths, override=False): - """ - Register a new node type. If the type's name is already in use, - an exception will be raised (unless override=True). - - ============== ========================================================= - **Arguments:** - - nodeClass a subclass of Node (must have typ.nodeName) - paths list of tuples specifying the location(s) this - type will appear in the library tree. - override if True, overwrite any class having the same name - ============== ========================================================= - """ - if not isNodeClass(nodeClass): - raise Exception("Object %s is not a Node subclass" % str(nodeClass)) - - name = nodeClass.nodeName - if not override and name in self.nodeList: - raise Exception("Node type name '%s' is already registered." % name) - - self.nodeList[name] = nodeClass - for path in paths: - root = self.nodeTree - for n in path: - if n not in root: - root[n] = OrderedDict() - root = root[n] - root[name] = nodeClass - - def getNodeType(self, name): - try: - return self.nodeList[name] - except KeyError: - raise Exception("No node type called '%s'" % name) - - def getNodeTree(self): - return self.nodeTree - - def copy(self): - """ - Return a copy of this library. - """ - lib = NodeLibrary() - lib.nodeList = self.nodeList.copy() - lib.nodeTree = self.treeCopy(self.nodeTree) - return lib - - @staticmethod - def treeCopy(tree): - copy = OrderedDict() - for k,v in tree.items(): - if isNodeClass(v): - copy[k] = v - else: - copy[k] = NodeLibrary.treeCopy(v) - return copy - - def reload(self): - """ - Reload Node classes in this library. - """ - raise NotImplementedError() diff --git a/pyqtgraph/flowchart/Terminal.py b/pyqtgraph/flowchart/Terminal.py deleted file mode 100644 index b7bcc7c..0000000 --- a/pyqtgraph/flowchart/Terminal.py +++ /dev/null @@ -1,581 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui, QtWidgets -import weakref -from ..graphicsItems.GraphicsObject import GraphicsObject -from .. import functions as fn -from ..Point import Point - -translate = QtCore.QCoreApplication.translate - -class Terminal(object): - def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): - """ - Construct a new terminal. - - ============== ================================================================================= - **Arguments:** - node the node to which this terminal belongs - name string, the name of the terminal - io 'in' or 'out' - optional bool, whether the node may process without connection to this terminal - multi bool, for inputs: whether this terminal may make multiple connections - for outputs: whether this terminal creates a different value for each connection - pos [x, y], the position of the terminal within its node's boundaries - renamable (bool) Whether the terminal can be renamed by the user - removable (bool) Whether the terminal can be removed by the user - multiable (bool) Whether the user may toggle the *multi* option for this terminal - bypass (str) Name of the terminal from which this terminal's value is derived - when the Node is in bypass mode. - ============== ================================================================================= - """ - self._io = io - self._optional = optional - self._multi = multi - self._node = weakref.ref(node) - self._name = name - self._renamable = renamable - self._removable = removable - self._multiable = multiable - self._connections = {} - self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) - self._bypass = bypass - - if multi: - self._value = {} ## dictionary of terminal:value pairs. - else: - self._value = None - - self.valueOk = None - self.recolor() - - def value(self, term=None): - """Return the value this terminal provides for the connected terminal""" - if term is None: - return self._value - - if self.isMultiValue(): - return self._value.get(term, None) - else: - return self._value - - def bypassValue(self): - return self._bypass - - def setValue(self, val, process=True): - """If this is a single-value terminal, val should be a single value. - If this is a multi-value terminal, val should be a dict of terminal:value pairs""" - if not self.isMultiValue(): - if fn.eq(val, self._value): - return - self._value = val - else: - if not isinstance(self._value, dict): - self._value = {} - if val is not None: - self._value.update(val) - - self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). - if self.isInput() and process: - self.node().update() - - self.recolor() - - def setOpts(self, **opts): - self._renamable = opts.get('renamable', self._renamable) - self._removable = opts.get('removable', self._removable) - self._multiable = opts.get('multiable', self._multiable) - if 'multi' in opts: - self.setMultiValue(opts['multi']) - - def connected(self, term): - """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" - if self.isInput() and term.isOutput(): - self.inputChanged(term) - if self.isOutput() and self.isMultiValue(): - self.node().update() - self.node().connected(self, term) - - def disconnected(self, term): - """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" - if self.isMultiValue() and term in self._value: - del self._value[term] - self.node().update() - else: - if self.isInput(): - self.setValue(None) - self.node().disconnected(self, term) - - def inputChanged(self, term, process=True): - """Called whenever there is a change to the input value to this terminal. - It may often be useful to override this function.""" - if self.isMultiValue(): - self.setValue({term: term.value(self)}, process=process) - else: - self.setValue(term.value(self), process=process) - - def valueIsAcceptable(self): - """Returns True->acceptable None->unknown False->Unacceptable""" - return self.valueOk - - def setValueAcceptable(self, v=True): - self.valueOk = v - self.recolor() - - def connections(self): - return self._connections - - def node(self): - return self._node() - - def isInput(self): - return self._io == 'in' - - def isMultiValue(self): - return self._multi - - def setMultiValue(self, multi): - """Set whether this is a multi-value terminal.""" - self._multi = multi - if not multi and len(self.inputTerminals()) > 1: - self.disconnectAll() - - for term in self.inputTerminals(): - self.inputChanged(term) - - def isOutput(self): - return self._io == 'out' - - def isRenamable(self): - return self._renamable - - def isRemovable(self): - return self._removable - - def isMultiable(self): - return self._multiable - - def name(self): - return self._name - - def graphicsItem(self): - return self._graphicsItem - - def isConnected(self): - return len(self.connections()) > 0 - - def connectedTo(self, term): - return term in self.connections() - - def hasInput(self): - for t in self.connections(): - if t.isOutput(): - return True - return False - - def inputTerminals(self): - """Return the terminal(s) that give input to this one.""" - return [t for t in self.connections() if t.isOutput()] - - def dependentNodes(self): - """Return the list of nodes which receive input from this terminal.""" - return set([t.node() for t in self.connections() if t.isInput()]) - - def connectTo(self, term, connectionItem=None): - try: - if self.connectedTo(term): - raise Exception('Already connected') - if term is self: - raise Exception('Not connecting terminal to self') - if term.node() is self.node(): - raise Exception("Can't connect to terminal on same node.") - for t in [self, term]: - if t.isInput() and not t._multi and len(t.connections()) > 0: - raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) - except: - if connectionItem is not None: - connectionItem.close() - raise - - if connectionItem is None: - connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) - self.graphicsItem().getViewBox().addItem(connectionItem) - self._connections[term] = connectionItem - term._connections[self] = connectionItem - - self.recolor() - - self.connected(term) - term.connected(self) - - return connectionItem - - def disconnectFrom(self, term): - if not self.connectedTo(term): - return - item = self._connections[term] - item.close() - del self._connections[term] - del term._connections[self] - self.recolor() - term.recolor() - - self.disconnected(term) - term.disconnected(self) - - - def disconnectAll(self): - for t in list(self._connections.keys()): - self.disconnectFrom(t) - - def recolor(self, color=None, recurse=True): - if color is None: - if not self.isConnected(): ## disconnected terminals are black - color = QtGui.QColor(0,0,0) - elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals - color = QtGui.QColor(200,200,0) - elif self._value is None or fn.eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) - color = QtGui.QColor(255,255,255) - elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok - color = QtGui.QColor(200, 200, 0) - elif self.valueIsAcceptable() is True: ## terminal has good input, all ok - color = QtGui.QColor(0, 200, 0) - else: ## terminal has bad input - color = QtGui.QColor(200, 0, 0) - self.graphicsItem().setBrush(QtGui.QBrush(color)) - - if recurse: - for t in self.connections(): - t.recolor(color, recurse=False) - - def rename(self, name): - oldName = self._name - self._name = name - self.node().terminalRenamed(self, oldName) - self.graphicsItem().termRenamed(name) - - def __repr__(self): - return "" % (str(self.node().name()), str(self.name())) - - def __hash__(self): - return id(self) - - def close(self): - self.disconnectAll() - item = self.graphicsItem() - if item.scene() is not None: - item.scene().removeItem(item) - - def saveState(self): - return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} - - def __lt__(self, other): - """When the terminal is multi value, the data passed to the DatTreeWidget for each input or output, is {Terminal: value}. - To make this sortable, we provide the < operator. - """ - return self._name < other._name - - -class TextItem(QtWidgets.QGraphicsTextItem): - def __init__(self, text, parent, on_update): - super().__init__(text, parent) - self.on_update = on_update - - def focusOutEvent(self, ev): - super().focusOutEvent(ev) - if self.on_update is not None: - self.on_update() - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - if self.on_update is not None: - self.on_update() - return - super().keyPressEvent(ev) - - -class TerminalGraphicsItem(GraphicsObject): - - def __init__(self, term, parent=None): - self.term = term - GraphicsObject.__init__(self, parent) - self.brush = fn.mkBrush(0,0,0) - self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) - on_update = self.labelChanged if self.term.isRenamable() else None - self.label = TextItem(self.term.name(), self, on_update) - self.label.setScale(0.7) - self.newConnection = None - self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem - if self.term.isRenamable(): - self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) - self.setZValue(1) - self.menu = None - - def labelChanged(self): - newName = str(self.label.toPlainText()) - if newName != self.term.name(): - self.term.rename(newName) - - def termRenamed(self, name): - self.label.setPlainText(name) - - def setBrush(self, brush): - self.brush = brush - self.box.setBrush(brush) - - def disconnect(self, target): - self.term.disconnectFrom(target.term) - - def boundingRect(self): - br = self.box.mapRectToParent(self.box.boundingRect()) - lr = self.label.mapRectToParent(self.label.boundingRect()) - return br | lr - - def paint(self, p, *args): - pass - - def setAnchor(self, x, y): - pos = QtCore.QPointF(x, y) - self.anchorPos = pos - br = self.box.mapRectToParent(self.box.boundingRect()) - lr = self.label.mapRectToParent(self.label.boundingRect()) - - - if self.term.isInput(): - self.box.setPos(pos.x(), pos.y()-br.height()/2.) - self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) - else: - self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) - self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) - self.updateConnections() - - def updateConnections(self): - for t, c in self.term.connections().items(): - c.updateLine() - - def mousePressEvent(self, ev): - #ev.accept() - ev.ignore() ## necessary to allow click/drag events to process correctly - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.label.setFocus(QtCore.Qt.MouseFocusReason) - elif ev.button() == QtCore.Qt.RightButton: - ev.accept() - self.raiseContextMenu(ev) - - def raiseContextMenu(self, ev): - ## only raise menu if this terminal is removable - menu = self.getMenu() - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def getMenu(self): - if self.menu is None: - self.menu = QtGui.QMenu() - self.menu.setTitle(translate("Context Menu", "Terminal")) - remAct = QtGui.QAction(translate("Context Menu", "Remove terminal"), self.menu) - remAct.triggered.connect(self.removeSelf) - self.menu.addAction(remAct) - self.menu.remAct = remAct - if not self.term.isRemovable(): - remAct.setEnabled(False) - multiAct = QtGui.QAction(translate("Context Menu", "Multi-value"), self.menu) - multiAct.setCheckable(True) - multiAct.setChecked(self.term.isMultiValue()) - multiAct.setEnabled(self.term.isMultiable()) - - multiAct.triggered.connect(self.toggleMulti) - self.menu.addAction(multiAct) - self.menu.multiAct = multiAct - if self.term.isMultiable(): - multiAct.setEnabled = False - return self.menu - - def toggleMulti(self): - multi = self.menu.multiAct.isChecked() - self.term.setMultiValue(multi) - - def removeSelf(self): - self.term.node().removeTerminal(self.term) - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - return - - ev.accept() - if ev.isStart(): - if self.newConnection is None: - self.newConnection = ConnectionItem(self) - #self.scene().addItem(self.newConnection) - self.getViewBox().addItem(self.newConnection) - #self.newConnection.setParentItem(self.parent().parent()) - - self.newConnection.setTarget(self.mapToView(ev.pos())) - elif ev.isFinish(): - if self.newConnection is not None: - items = self.scene().items(ev.scenePos()) - gotTarget = False - for i in items: - if isinstance(i, TerminalGraphicsItem): - self.newConnection.setTarget(i) - try: - self.term.connectTo(i.term, self.newConnection) - gotTarget = True - except: - self.scene().removeItem(self.newConnection) - self.newConnection = None - raise - break - - if not gotTarget: - self.newConnection.close() - self.newConnection = None - else: - if self.newConnection is not None: - self.newConnection.setTarget(self.mapToView(ev.pos())) - - def hoverEvent(self, ev): - if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. - ev.acceptClicks(QtCore.Qt.RightButton) - self.box.setBrush(fn.mkBrush('w')) - else: - self.box.setBrush(self.brush) - self.update() - - def connectPoint(self): - ## return the connect position of this terminal in view coords - return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) - - def nodeMoved(self): - for t, item in self.term.connections().items(): - item.updateLine() - - -class ConnectionItem(GraphicsObject): - - def __init__(self, source, target=None): - GraphicsObject.__init__(self) - self.setFlags( - self.ItemIsSelectable | - self.ItemIsFocusable - ) - self.source = source - self.target = target - self.length = 0 - self.hovered = False - self.path = None - self.shapePath = None - self.style = { - 'shape': 'line', - 'color': (100, 100, 250), - 'width': 1.0, - 'hoverColor': (150, 150, 250), - 'hoverWidth': 1.0, - 'selectedColor': (200, 200, 0), - 'selectedWidth': 3.0, - } - self.source.getViewBox().addItem(self) - self.updateLine() - self.setZValue(0) - - def close(self): - if self.scene() is not None: - self.scene().removeItem(self) - - def setTarget(self, target): - self.target = target - self.updateLine() - - def setStyle(self, **kwds): - self.style.update(kwds) - if 'shape' in kwds: - self.updateLine() - else: - self.update() - - def updateLine(self): - start = Point(self.source.connectPoint()) - if isinstance(self.target, TerminalGraphicsItem): - stop = Point(self.target.connectPoint()) - elif isinstance(self.target, QtCore.QPointF): - stop = Point(self.target) - else: - return - self.prepareGeometryChange() - - self.path = self.generatePath(start, stop) - self.shapePath = None - self.update() - - def generatePath(self, start, stop): - path = QtGui.QPainterPath() - path.moveTo(start) - if self.style['shape'] == 'line': - path.lineTo(stop) - elif self.style['shape'] == 'cubic': - path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) - else: - raise Exception('Invalid shape "%s"; options are "line" or "cubic"' % self.style['shape']) - return path - - def keyPressEvent(self, ev): - if not self.isSelected(): - ev.ignore() - return - - if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: - self.source.disconnect(self.target) - ev.accept() - else: - ev.ignore() - - def mousePressEvent(self, ev): - ev.ignore() - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - sel = self.isSelected() - self.setSelected(True) - self.setFocus() - if not sel and self.isSelected(): - self.update() - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): - self.hovered = True - else: - self.hovered = False - self.update() - - def boundingRect(self): - return self.shape().boundingRect() - - def viewRangeChanged(self): - self.shapePath = None - self.prepareGeometryChange() - - def shape(self): - if self.shapePath is None: - if self.path is None: - return QtGui.QPainterPath() - stroker = QtGui.QPainterPathStroker() - px = self.pixelWidth() - stroker.setWidth(px*8) - self.shapePath = stroker.createStroke(self.path) - return self.shapePath - - def paint(self, p, *args): - if self.isSelected(): - p.setPen(fn.mkPen(self.style['selectedColor'], width=self.style['selectedWidth'])) - else: - if self.hovered: - p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) - else: - p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) - - p.drawPath(self.path) diff --git a/pyqtgraph/flowchart/__init__.py b/pyqtgraph/flowchart/__init__.py deleted file mode 100644 index 46e04db..0000000 --- a/pyqtgraph/flowchart/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -from .Flowchart import * - -from .library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/pyqtgraph/flowchart/__pycache__/Flowchart.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/Flowchart.cpython-36.pyc deleted file mode 100644 index 2b770cc..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/Flowchart.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index bf561e6..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index 23699bb..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside2.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index bdd0069..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside6.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index b80d2a9..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartCtrlTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartGraphicsView.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartGraphicsView.cpython-36.pyc deleted file mode 100644 index 5466b76..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartGraphicsView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index 9cc03e7..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index fdc961c..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside2.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index 3938c7e..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside6.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index 564c37f..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/FlowchartTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/Node.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/Node.cpython-36.pyc deleted file mode 100644 index 0c48f87..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/Node.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/NodeLibrary.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/NodeLibrary.cpython-36.pyc deleted file mode 100644 index ed881d0..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/NodeLibrary.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/Terminal.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/Terminal.cpython-36.pyc deleted file mode 100644 index 5488756..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/Terminal.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/flowchart/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 37fa11f..0000000 Binary files a/pyqtgraph/flowchart/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py deleted file mode 100644 index 6edd3a8..0000000 --- a/pyqtgraph/flowchart/library/Data.py +++ /dev/null @@ -1,485 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node -from ...Qt import QtGui, QtCore, QtWidgets -import numpy as np -import sys -from .common import * -from ...SRTTransform import SRTTransform -from ...Point import Point -from ...widgets.TreeWidget import TreeWidget -from ...graphicsItems.LinearRegionItem import LinearRegionItem - -from . import functions - -class ColumnSelectNode(Node): - """Select named columns from a record array or MetaArray.""" - nodeName = "ColumnSelect" - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in'}}) - self.columns = set() - self.columnList = QtGui.QListWidget() - self.axis = 0 - self.columnList.itemChanged.connect(self.itemChanged) - - def process(self, In, display=True): - if display: - self.updateList(In) - - out = {} - if hasattr(In, 'implements') and In.implements('MetaArray'): - for c in self.columns: - out[c] = In[self.axis:c] - elif isinstance(In, np.ndarray) and In.dtype.fields is not None: - for c in self.columns: - out[c] = In[c] - else: - self.In.setValueAcceptable(False) - raise Exception("Input must be MetaArray or ndarray with named fields") - - return out - - def ctrlWidget(self): - return self.columnList - - def updateList(self, data): - if hasattr(data, 'implements') and data.implements('MetaArray'): - cols = data.listColumns() - for ax in cols: ## find first axis with columns - if len(cols[ax]) > 0: - self.axis = ax - cols = set(cols[ax]) - break - else: - cols = list(data.dtype.fields.keys()) - - rem = set() - for c in self.columns: - if c not in cols: - self.removeTerminal(c) - rem.add(c) - self.columns -= rem - - self.columnList.blockSignals(True) - self.columnList.clear() - for c in cols: - item = QtGui.QListWidgetItem(c) - item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsUserCheckable) - if c in self.columns: - item.setCheckState(QtCore.Qt.Checked) - else: - item.setCheckState(QtCore.Qt.Unchecked) - self.columnList.addItem(item) - self.columnList.blockSignals(False) - - - def itemChanged(self, item): - col = str(item.text()) - if item.checkState() == QtCore.Qt.Checked: - if col not in self.columns: - self.columns.add(col) - self.addOutput(col) - else: - if col in self.columns: - self.columns.remove(col) - self.removeTerminal(col) - self.update() - - def saveState(self): - state = Node.saveState(self) - state['columns'] = list(self.columns) - return state - - def restoreState(self, state): - Node.restoreState(self, state) - self.columns = set(state.get('columns', [])) - for c in self.columns: - self.addOutput(c) - - - -class RegionSelectNode(CtrlNode): - """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" - nodeName = "RegionSelect" - uiTemplate = [ - ('start', 'spin', {'value': 0, 'step': 0.1}), - ('stop', 'spin', {'value': 0.1, 'step': 0.1}), - ('display', 'check', {'value': True}), - ('movable', 'check', {'value': True}), - ] - - def __init__(self, name): - self.items = {} - CtrlNode.__init__(self, name, terminals={ - 'data': {'io': 'in'}, - 'selected': {'io': 'out'}, - 'region': {'io': 'out'}, - 'widget': {'io': 'out', 'multi': True} - }) - self.ctrls['display'].toggled.connect(self.displayToggled) - self.ctrls['movable'].toggled.connect(self.movableToggled) - - def displayToggled(self, b): - for item in self.items.values(): - item.setVisible(b) - - def movableToggled(self, b): - for item in self.items.values(): - item.setMovable(b) - - - def process(self, data=None, display=True): - #print "process.." - s = self.stateGroup.state() - region = [s['start'], s['stop']] - - if display: - conn = self['widget'].connections() - for c in conn: - plot = c.node().getPlot() - if plot is None: - continue - if c in self.items: - item = self.items[c] - item.setRegion(region) - #print " set rgn:", c, region - #item.setXVals(events) - else: - item = LinearRegionItem(values=region) - self.items[c] = item - #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) - item.sigRegionChanged.connect(self.rgnChanged) - item.setVisible(s['display']) - item.setMovable(s['movable']) - #print " new rgn:", c, region - #self.items[c].setYRange([0., 0.2], relative=True) - - if self['selected'].isConnected(): - if data is None: - sliced = None - elif (hasattr(data, 'implements') and data.implements('MetaArray')): - sliced = data[0:s['start']:s['stop']] - else: - mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) - sliced = data[mask] - else: - sliced = None - - return {'selected': sliced, 'widget': self.items, 'region': region} - - - def rgnChanged(self, item): - region = item.getRegion() - self.stateGroup.setState({'start': region[0], 'stop': region[1]}) - self.update() - - -class TextEdit(QtWidgets.QTextEdit): - def __init__(self, on_update): - super().__init__() - self.on_update = on_update - self.lastText = None - - def focusOutEvent(self, ev): - text = str(self.toPlainText()) - if text != self.lastText: - self.lastText = text - self.on_update() - super().focusOutEvent(ev) - - -class EvalNode(Node): - """Return the output of a string evaluated/executed by the python interpreter. - The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. - For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. - For a script, the text will be executed as the body of a function.""" - nodeName = 'PythonEval' - - def __init__(self, name): - Node.__init__(self, name, - terminals = { - 'input': {'io': 'in', 'renamable': True, 'multiable': True}, - 'output': {'io': 'out', 'renamable': True, 'multiable': True}, - }, - allowAddInput=True, allowAddOutput=True) - - self.ui = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - self.text = TextEdit(self.update) - self.text.setTabStopWidth(30) - self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") - self.layout.addWidget(self.text, 1, 0, 1, 2) - self.ui.setLayout(self.layout) - - def ctrlWidget(self): - return self.ui - - def setCode(self, code): - # unindent code; this allows nicer inline code specification when - # calling this method. - ind = [] - lines = code.split('\n') - for line in lines: - stripped = line.lstrip() - if len(stripped) > 0: - ind.append(len(line) - len(stripped)) - if len(ind) > 0: - ind = min(ind) - code = '\n'.join([line[ind:] for line in lines]) - - self.text.clear() - self.text.insertPlainText(code) - - def code(self): - return self.text.toPlainText() - - def process(self, display=True, **args): - l = locals() - l.update(args) - ## try eval first, then exec - try: - text = str(self.text.toPlainText()).replace('\n', ' ') - output = eval(text, globals(), l) - except SyntaxError: - fn = "def fn(**args):\n" - run = "\noutput=fn(**args)\n" - text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run - if sys.version_info.major == 2: - exec(text) - elif sys.version_info.major == 3: - ldict = locals() - exec(text, globals(), ldict) - output = ldict['output'] - except: - print("Error processing node: %s" % self.name()) - raise - return output - - def saveState(self): - state = Node.saveState(self) - state['text'] = str(self.text.toPlainText()) - #state['terminals'] = self.saveTerminals() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - self.setCode(state['text']) - self.restoreTerminals(state['terminals']) - self.update() - - -class ColumnJoinNode(Node): - """Concatenates record arrays and/or adds new columns""" - nodeName = 'ColumnJoin' - - def __init__(self, name): - Node.__init__(self, name, terminals = { - 'output': {'io': 'out'}, - }) - - #self.items = [] - - self.ui = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - self.ui.setLayout(self.layout) - - self.tree = TreeWidget() - self.addInBtn = QtGui.QPushButton('+ Input') - self.remInBtn = QtGui.QPushButton('- Input') - - self.layout.addWidget(self.tree, 0, 0, 1, 2) - self.layout.addWidget(self.addInBtn, 1, 0) - self.layout.addWidget(self.remInBtn, 1, 1) - - self.addInBtn.clicked.connect(self.addInput) - self.remInBtn.clicked.connect(self.remInput) - self.tree.sigItemMoved.connect(self.update) - - def ctrlWidget(self): - return self.ui - - def addInput(self): - #print "ColumnJoinNode.addInput called." - term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True) - #print "Node.addInput returned. term:", term - item = QtGui.QTreeWidgetItem([term.name()]) - item.term = term - term.joinItem = item - #self.items.append((term, item)) - self.tree.addTopLevelItem(item) - - def remInput(self): - sel = self.tree.currentItem() - term = sel.term - term.joinItem = None - sel.term = None - self.tree.removeTopLevelItem(sel) - self.removeTerminal(term) - self.update() - - def process(self, display=True, **args): - order = self.order() - vals = [] - for name in order: - if name not in args: - continue - val = args[name] - if isinstance(val, np.ndarray) and len(val.dtype) > 0: - vals.append(val) - else: - vals.append((name, None, val)) - return {'output': functions.concatenateColumns(vals)} - - def order(self): - return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] - - def saveState(self): - state = Node.saveState(self) - state['order'] = self.order() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - inputs = self.inputs() - - ## Node.restoreState should have created all of the terminals we need - ## However: to maintain support for some older flowchart files, we need - ## to manually add any terminals that were not taken care of. - for name in [n for n in state['order'] if n not in inputs]: - Node.addInput(self, name, renamable=True, removable=True, multiable=True) - inputs = self.inputs() - - order = [name for name in state['order'] if name in inputs] - for name in inputs: - if name not in order: - order.append(name) - - self.tree.clear() - for name in order: - term = self[name] - item = QtGui.QTreeWidgetItem([name]) - item.term = term - term.joinItem = item - #self.items.append((term, item)) - self.tree.addTopLevelItem(item) - - def terminalRenamed(self, term, oldName): - Node.terminalRenamed(self, term, oldName) - item = term.joinItem - item.setText(0, term.name()) - self.update() - - -class Mean(CtrlNode): - """Calculate the mean of an array across an axis. - """ - nodeName = 'Mean' - uiTemplate = [ - ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = None if s['axis'] == -1 else s['axis'] - return data.mean(axis=ax) - - -class Max(CtrlNode): - """Calculate the maximum of an array across an axis. - """ - nodeName = 'Max' - uiTemplate = [ - ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = None if s['axis'] == -1 else s['axis'] - return data.max(axis=ax) - - -class Min(CtrlNode): - """Calculate the minimum of an array across an axis. - """ - nodeName = 'Min' - uiTemplate = [ - ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = None if s['axis'] == -1 else s['axis'] - return data.min(axis=ax) - - -class Stdev(CtrlNode): - """Calculate the standard deviation of an array across an axis. - """ - nodeName = 'Stdev' - uiTemplate = [ - ('axis', 'intSpin', {'value': -0, 'min': -1, 'max': 1000000}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = None if s['axis'] == -1 else s['axis'] - return data.std(axis=ax) - - -class Index(CtrlNode): - """Select an index from an array axis. - """ - nodeName = 'Index' - uiTemplate = [ - ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), - ('index', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = s['axis'] - ind = s['index'] - if ax == 0: - # allow support for non-ndarray sequence types - return data[ind] - else: - return data.take(ind, axis=ax) - - -class Slice(CtrlNode): - """Select a slice from an array axis. - """ - nodeName = 'Slice' - uiTemplate = [ - ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1e6}), - ('start', 'intSpin', {'value': 0, 'min': -1e6, 'max': 1e6}), - ('stop', 'intSpin', {'value': -1, 'min': -1e6, 'max': 1e6}), - ('step', 'intSpin', {'value': 1, 'min': -1e6, 'max': 1e6}), - ] - - def processData(self, data): - s = self.stateGroup.state() - ax = s['axis'] - start = s['start'] - stop = s['stop'] - step = s['step'] - if ax == 0: - # allow support for non-ndarray sequence types - return data[start:stop:step] - else: - sl = [slice(None) for i in range(data.ndim)] - sl[ax] = slice(start, stop, step) - return data[sl] - - -class AsType(CtrlNode): - """Convert an array to a different dtype. - """ - nodeName = 'AsType' - uiTemplate = [ - ('dtype', 'combo', {'values': ['float', 'int', 'float32', 'float64', 'float128', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'], 'index': 0}), - ] - - def processData(self, data): - s = self.stateGroup.state() - return data.astype(s['dtype']) - diff --git a/pyqtgraph/flowchart/library/Display.py b/pyqtgraph/flowchart/library/Display.py deleted file mode 100644 index 642e649..0000000 --- a/pyqtgraph/flowchart/library/Display.py +++ /dev/null @@ -1,312 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node -import weakref -from ...Qt import QtCore, QtGui -from ...graphicsItems.ScatterPlotItem import ScatterPlotItem -from ...graphicsItems.PlotCurveItem import PlotCurveItem -from ... import PlotDataItem, ComboBox - -from .common import * -import numpy as np - -class PlotWidgetNode(Node): - """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" - nodeName = 'PlotWidget' - sigPlotChanged = QtCore.Signal(object) - - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) - self.plot = None # currently selected plot - self.plots = {} # list of available plots user may select from - self.ui = None - self.items = {} - - def disconnected(self, localTerm, remoteTerm): - if localTerm is self['In'] and remoteTerm in self.items: - self.plot.removeItem(self.items[remoteTerm]) - del self.items[remoteTerm] - - def setPlot(self, plot): - #print "======set plot" - if plot == self.plot: - return - - # clear data from previous plot - if self.plot is not None: - for vid in list(self.items.keys()): - self.plot.removeItem(self.items[vid]) - del self.items[vid] - - self.plot = plot - self.updateUi() - self.update() - self.sigPlotChanged.emit(self) - - def getPlot(self): - return self.plot - - def process(self, In, display=True): - if display and self.plot is not None: - items = set() - # Add all new input items to selected plot - for name, vals in In.items(): - if vals is None: - continue - if type(vals) is not list: - vals = [vals] - - for val in vals: - vid = id(val) - if vid in self.items and self.items[vid].scene() is self.plot.scene(): - # Item is already added to the correct scene - # possible bug: what if two plots occupy the same scene? (should - # rarely be a problem because items are removed from a plot before - # switching). - items.add(vid) - else: - # Add the item to the plot, or generate a new item if needed. - if isinstance(val, QtGui.QGraphicsItem): - self.plot.addItem(val) - item = val - else: - item = self.plot.plot(val) - self.items[vid] = item - items.add(vid) - - # Any left-over items that did not appear in the input must be removed - for vid in list(self.items.keys()): - if vid not in items: - self.plot.removeItem(self.items[vid]) - del self.items[vid] - - def processBypassed(self, args): - if self.plot is None: - return - for item in list(self.items.values()): - self.plot.removeItem(item) - self.items = {} - - def ctrlWidget(self): - if self.ui is None: - self.ui = ComboBox() - self.ui.currentIndexChanged.connect(self.plotSelected) - self.updateUi() - return self.ui - - def plotSelected(self, index): - self.setPlot(self.ui.value()) - - def setPlotList(self, plots): - """ - Specify the set of plots (PlotWidget or PlotItem) that the user may - select from. - - *plots* must be a dictionary of {name: plot} pairs. - """ - self.plots = plots - self.updateUi() - - def updateUi(self): - # sets list and automatically preserves previous selection - self.ui.setItems(self.plots) - try: - self.ui.setValue(self.plot) - except ValueError: - pass - - -class CanvasNode(Node): - """Connection to a Canvas widget.""" - nodeName = 'CanvasWidget' - - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) - self.canvas = None - self.items = {} - - def disconnected(self, localTerm, remoteTerm): - if localTerm is self.In and remoteTerm in self.items: - self.canvas.removeItem(self.items[remoteTerm]) - del self.items[remoteTerm] - - def setCanvas(self, canvas): - self.canvas = canvas - - def getCanvas(self): - return self.canvas - - def process(self, In, display=True): - if display: - items = set() - for name, vals in In.items(): - if vals is None: - continue - if type(vals) is not list: - vals = [vals] - - for val in vals: - vid = id(val) - if vid in self.items: - items.add(vid) - else: - self.canvas.addItem(val) - item = val - self.items[vid] = item - items.add(vid) - for vid in list(self.items.keys()): - if vid not in items: - #print "remove", self.items[vid] - self.canvas.removeItem(self.items[vid]) - del self.items[vid] - - -class PlotCurve(CtrlNode): - """Generates a plot curve from x/y data""" - nodeName = 'PlotCurve' - uiTemplate = [ - ('color', 'color'), - ] - - def __init__(self, name): - CtrlNode.__init__(self, name, terminals={ - 'x': {'io': 'in'}, - 'y': {'io': 'in'}, - 'plot': {'io': 'out'} - }) - self.item = PlotDataItem() - - def process(self, x, y, display=True): - #print "scatterplot process" - if not display: - return {'plot': None} - - self.item.setData(x, y, pen=self.ctrls['color'].color()) - return {'plot': self.item} - - - - -class ScatterPlot(CtrlNode): - """Generates a scatter plot from a record array or nested dicts""" - nodeName = 'ScatterPlot' - uiTemplate = [ - ('x', 'combo', {'values': [], 'index': 0}), - ('y', 'combo', {'values': [], 'index': 0}), - ('sizeEnabled', 'check', {'value': False}), - ('size', 'combo', {'values': [], 'index': 0}), - ('absoluteSize', 'check', {'value': False}), - ('colorEnabled', 'check', {'value': False}), - ('color', 'colormap', {}), - ('borderEnabled', 'check', {'value': False}), - ('border', 'colormap', {}), - ] - - def __init__(self, name): - CtrlNode.__init__(self, name, terminals={ - 'input': {'io': 'in'}, - 'plot': {'io': 'out'} - }) - self.item = ScatterPlotItem() - self.keys = [] - - #self.ui = QtGui.QWidget() - #self.layout = QtGui.QGridLayout() - #self.ui.setLayout(self.layout) - - #self.xCombo = QtGui.QComboBox() - #self.yCombo = QtGui.QComboBox() - - - - def process(self, input, display=True): - #print "scatterplot process" - if not display: - return {'plot': None} - - self.updateKeys(input[0]) - - x = str(self.ctrls['x'].currentText()) - y = str(self.ctrls['y'].currentText()) - size = str(self.ctrls['size'].currentText()) - pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) - points = [] - for i in input: - pt = {'pos': (i[x], i[y])} - if self.ctrls['sizeEnabled'].isChecked(): - pt['size'] = i[size] - if self.ctrls['borderEnabled'].isChecked(): - pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) - else: - pt['pen'] = pen - if self.ctrls['colorEnabled'].isChecked(): - pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) - points.append(pt) - self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) - - self.item.setPoints(points) - - return {'plot': self.item} - - - - def updateKeys(self, data): - if isinstance(data, dict): - keys = list(data.keys()) - elif isinstance(data, list) or isinstance(data, tuple): - keys = data - elif isinstance(data, np.ndarray) or isinstance(data, np.void): - keys = data.dtype.names - else: - print("Unknown data type:", type(data), data) - return - - for c in self.ctrls.values(): - c.blockSignals(True) - for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: - cur = str(c.currentText()) - c.clear() - for k in keys: - c.addItem(k) - if k == cur: - c.setCurrentIndex(c.count()-1) - for c in [self.ctrls['color'], self.ctrls['border']]: - c.setArgList(keys) - for c in self.ctrls.values(): - c.blockSignals(False) - - self.keys = keys - - - def saveState(self): - state = CtrlNode.saveState(self) - return {'keys': self.keys, 'ctrls': state} - - def restoreState(self, state): - self.updateKeys(state['keys']) - CtrlNode.restoreState(self, state['ctrls']) - -#class ImageItem(Node): - #"""Creates an ImageItem for display in a canvas from a file handle.""" - #nodeName = 'Image' - - #def __init__(self, name): - #Node.__init__(self, name, terminals={ - #'file': {'io': 'in'}, - #'image': {'io': 'out'} - #}) - #self.imageItem = graphicsItems.ImageItem() - #self.handle = None - - #def process(self, file, display=True): - #if not display: - #return {'image': None} - - #if file != self.handle: - #self.handle = file - #data = file.read() - #self.imageItem.updateImage(data) - - #pos = file. - - - diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py deleted file mode 100644 index 9a7fa40..0000000 --- a/pyqtgraph/flowchart/library/Filters.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from ...Qt import QtCore, QtGui -from ..Node import Node -from . import functions -from ... import functions as pgfn -from .common import * -from ...python2_3 import xrange -from ... import PolyLineROI -from ... import Point -from ... import metaarray as metaarray - - -class Downsample(CtrlNode): - """Downsample by averaging samples together.""" - nodeName = 'Downsample' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - def processData(self, data): - return functions.downsample(data, self.ctrls['n'].value(), axis=0) - - -class Subsample(CtrlNode): - """Downsample by selecting every Nth sample.""" - nodeName = 'Subsample' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - def processData(self, data): - return data[::self.ctrls['n'].value()] - - -class Bessel(CtrlNode): - """Bessel filter. Input data must have time values.""" - nodeName = 'BesselFilter' - uiTemplate = [ - ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - if s['band'] == 'lowpass': - mode = 'low' - else: - mode = 'high' - return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) - - -class Butterworth(CtrlNode): - """Butterworth filter""" - nodeName = 'ButterworthFilter' - uiTemplate = [ - ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - if s['band'] == 'lowpass': - mode = 'low' - else: - mode = 'high' - ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) - return ret - - -class ButterworthNotch(CtrlNode): - """Butterworth notch filter""" - nodeName = 'ButterworthNotchFilter' - uiTemplate = [ - ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - - low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop']) - high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop']) - return low + high - - -class Mean(CtrlNode): - """Filters data by taking the mean of a sliding window""" - nodeName = 'MeanFilter' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - n = self.ctrls['n'].value() - return functions.rollingSum(data, n) / n - - -class Median(CtrlNode): - """Filters data by taking the median of a sliding window""" - nodeName = 'MedianFilter' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - try: - import scipy.ndimage - except ImportError: - raise Exception("MedianFilter node requires the package scipy.ndimage.") - return scipy.ndimage.median_filter(data, self.ctrls['n'].value()) - -class Mode(CtrlNode): - """Filters data by taking the mode (histogram-based) of a sliding window""" - nodeName = 'ModeFilter' - uiTemplate = [ - ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), - ] - - @metaArrayWrapper - def processData(self, data): - return functions.modeFilter(data, self.ctrls['window'].value()) - - -class Denoise(CtrlNode): - """Removes anomalous spikes from data, replacing with nearby values""" - nodeName = 'DenoiseFilter' - uiTemplate = [ - ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), - ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) - ] - - def processData(self, data): - #print "DENOISE" - s = self.stateGroup.state() - return functions.denoise(data, **s) - - -class Gaussian(CtrlNode): - """Gaussian smoothing filter.""" - nodeName = 'GaussianFilter' - uiTemplate = [ - ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - sigma = self.ctrls['sigma'].value() - try: - import scipy.ndimage - return scipy.ndimage.gaussian_filter(data, sigma) - except ImportError: - return pgfn.gaussianFilter(data, sigma) - - -class Derivative(CtrlNode): - """Returns the pointwise derivative of the input""" - nodeName = 'DerivativeFilter' - - def processData(self, data): - if hasattr(data, 'implements') and data.implements('MetaArray'): - info = data.infoCopy() - if 'values' in info[0]: - info[0]['values'] = info[0]['values'][:-1] - return metaarray.MetaArray(data[1:] - data[:-1], info=info) - else: - return data[1:] - data[:-1] - - -class Integral(CtrlNode): - """Returns the pointwise integral of the input""" - nodeName = 'IntegralFilter' - - @metaArrayWrapper - def processData(self, data): - data[1:] += data[:-1] - return data - - -class Detrend(CtrlNode): - """Removes linear trend from the data""" - nodeName = 'DetrendFilter' - - @metaArrayWrapper - def processData(self, data): - try: - from scipy.signal import detrend - except ImportError: - raise Exception("DetrendFilter node requires the package scipy.signal.") - return detrend(data) - -class RemoveBaseline(PlottingCtrlNode): - """Remove an arbitrary, graphically defined baseline from the data.""" - nodeName = 'RemoveBaseline' - - def __init__(self, name): - ## define inputs and outputs (one output needs to be a plot) - PlottingCtrlNode.__init__(self, name) - self.line = PolyLineROI([[0,0],[1,0]]) - self.line.sigRegionChanged.connect(self.changed) - - ## create a PolyLineROI, add it to a plot -- actually, I think we want to do this after the node is connected to a plot (look at EventDetection.ThresholdEvents node for ideas), and possible after there is data. We will need to update the end positions of the line each time the input data changes - #self.line = None ## will become a PolyLineROI - - def connectToPlot(self, node): - """Define what happens when the node is connected to a plot""" - - if node.plot is None: - return - node.getPlot().addItem(self.line) - - def disconnectFromPlot(self, plot): - """Define what happens when the node is disconnected from a plot""" - plot.removeItem(self.line) - - def processData(self, data): - ## get array of baseline (from PolyLineROI) - h0 = self.line.getHandles()[0] - h1 = self.line.getHandles()[-1] - - timeVals = data.xvals(0) - h0.setPos(timeVals[0], h0.pos()[1]) - h1.setPos(timeVals[-1], h1.pos()[1]) - - pts = self.line.listPoints() ## lists line handles in same coordinates as data - pts, indices = self.adjustXPositions(pts, timeVals) ## maxe sure x positions match x positions of data points - - ## construct an array that represents the baseline - arr = np.zeros(len(data), dtype=float) - n = 1 - arr[0] = pts[0].y() - for i in range(len(pts)-1): - x1 = pts[i].x() - x2 = pts[i+1].x() - y1 = pts[i].y() - y2 = pts[i+1].y() - m = (y2-y1)/(x2-x1) - b = y1 - - times = timeVals[(timeVals > x1)*(timeVals <= x2)] - arr[n:n+len(times)] = (m*(times-times[0]))+b - n += len(times) - - return data - arr ## subract baseline from data - - def adjustXPositions(self, pts, data): - """Return a list of Point() where the x position is set to the nearest x value in *data* for each point in *pts*.""" - points = [] - timeIndices = [] - for p in pts: - x = np.argwhere(abs(data - p.x()) == abs(data - p.x()).min()) - points.append(Point(data[x], p.y())) - timeIndices.append(x) - - return points, timeIndices - - - -class AdaptiveDetrend(CtrlNode): - """Removes baseline from data, ignoring anomalous events""" - nodeName = 'AdaptiveDetrend' - uiTemplate = [ - ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) - ] - - def processData(self, data): - return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) - -class HistogramDetrend(CtrlNode): - """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" - nodeName = 'HistogramDetrend' - uiTemplate = [ - ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), - ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}), - ('offsetOnly', 'check', {'checked': False}), - ] - - def processData(self, data): - s = self.stateGroup.state() - #ws = self.ctrls['windowSize'].value() - #bn = self.ctrls['numBins'].value() - #offset = self.ctrls['offsetOnly'].checked() - return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly']) - - - -class RemovePeriodic(CtrlNode): - nodeName = 'RemovePeriodic' - uiTemplate = [ - #('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), - #('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) - ('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}), - ('harmonics', 'intSpin', {'value': 30, 'min': 0}), - ('samples', 'intSpin', {'value': 1, 'min': 1}), - ] - - def processData(self, data): - times = data.xvals('Time') - dt = times[1]-times[0] - - data1 = data.asarray() - ft = np.fft.fft(data1) - - ## determine frequencies in fft data - df = 1.0 / (len(data1) * dt) - freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft)) - - ## flatten spikes at f0 and harmonics - f0 = self.ctrls['f0'].value() - for i in xrange(1, self.ctrls['harmonics'].value()+2): - f = f0 * i # target frequency - - ## determine index range to check for this frequency - ind1 = int(np.floor(f / df)) - ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1) - if ind1 > len(ft)/2.: - break - mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 - for j in range(ind1, ind2+1): - phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. - re = mag * np.cos(phase) - im = mag * np.sin(phase) - ft[j] = re + im*1j - ft[len(ft)-j] = re - im*1j - - data2 = np.fft.ifft(ft).real - - ma = metaarray.MetaArray(data2, info=data.infoCopy()) - return ma - - - diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py deleted file mode 100644 index d1483c1..0000000 --- a/pyqtgraph/flowchart/library/Operators.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node -from .common import CtrlNode - - -class UniOpNode(Node): - """Generic node for performing any operation like Out = In.fn()""" - def __init__(self, name, fn): - self.fn = fn - Node.__init__(self, name, terminals={ - 'In': {'io': 'in'}, - 'Out': {'io': 'out', 'bypass': 'In'} - }) - - def process(self, **args): - return {'Out': getattr(args['In'], self.fn)()} - -class BinOpNode(CtrlNode): - """Generic node for performing any operation like A.fn(B)""" - - _dtypes = [ - 'float64', 'float32', 'float16', - 'int64', 'int32', 'int16', 'int8', - 'uint64', 'uint32', 'uint16', 'uint8' - ] - - uiTemplate = [ - ('outputType', 'combo', {'values': ['no change', 'input A', 'input B'] + _dtypes , 'index': 0}) - ] - - def __init__(self, name, fn): - self.fn = fn - CtrlNode.__init__(self, name, terminals={ - 'A': {'io': 'in'}, - 'B': {'io': 'in'}, - 'Out': {'io': 'out', 'bypass': 'A'} - }) - - def process(self, **args): - if isinstance(self.fn, tuple): - for name in self.fn: - try: - fn = getattr(args['A'], name) - break - except AttributeError: - pass - else: - fn = getattr(args['A'], self.fn) - out = fn(args['B']) - if out is NotImplemented: - raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) - - # Coerce dtype if requested - typ = self.stateGroup.state()['outputType'] - if typ == 'no change': - pass - elif typ == 'input A': - out = out.astype(args['A'].dtype) - elif typ == 'input B': - out = out.astype(args['B'].dtype) - else: - out = out.astype(typ) - - #print " ", fn, out - return {'Out': out} - - -class AbsNode(UniOpNode): - """Returns abs(Inp). Does not check input types.""" - nodeName = 'Abs' - def __init__(self, name): - UniOpNode.__init__(self, name, '__abs__') - -class AddNode(BinOpNode): - """Returns A + B. Does not check input types.""" - nodeName = 'Add' - def __init__(self, name): - BinOpNode.__init__(self, name, '__add__') - -class SubtractNode(BinOpNode): - """Returns A - B. Does not check input types.""" - nodeName = 'Subtract' - def __init__(self, name): - BinOpNode.__init__(self, name, '__sub__') - -class MultiplyNode(BinOpNode): - """Returns A * B. Does not check input types.""" - nodeName = 'Multiply' - def __init__(self, name): - BinOpNode.__init__(self, name, '__mul__') - -class DivideNode(BinOpNode): - """Returns A / B. Does not check input types.""" - nodeName = 'Divide' - def __init__(self, name): - # try truediv first, followed by div - BinOpNode.__init__(self, name, ('__truediv__', '__div__')) - -class FloorDivideNode(BinOpNode): - """Returns A // B. Does not check input types.""" - nodeName = 'FloorDivide' - def __init__(self, name): - BinOpNode.__init__(self, name, '__floordiv__') - - diff --git a/pyqtgraph/flowchart/library/__init__.py b/pyqtgraph/flowchart/library/__init__.py deleted file mode 100644 index f7fa87a..0000000 --- a/pyqtgraph/flowchart/library/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import OrderedDict -import os, types -from ...debug import printExc -from ..NodeLibrary import NodeLibrary, isNodeClass -from ... import reload as reload - - -# Build default library -LIBRARY = NodeLibrary() - -# For backward compatibility, expose the default library's properties here: -NODE_LIST = LIBRARY.nodeList -NODE_TREE = LIBRARY.nodeTree -registerNodeType = LIBRARY.addNodeType -getNodeTree = LIBRARY.getNodeTree -getNodeType = LIBRARY.getNodeType - -# Add all nodes to the default library -from . import Data, Display, Filters, Operators -for mod in [Data, Display, Filters, Operators]: - nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))] - for node in nodes: - LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)]) - - - - diff --git a/pyqtgraph/flowchart/library/__pycache__/Data.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/Data.cpython-36.pyc deleted file mode 100644 index 88354cf..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/Data.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/Display.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/Display.cpython-36.pyc deleted file mode 100644 index c02ab6a..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/Display.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/Filters.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/Filters.cpython-36.pyc deleted file mode 100644 index a300cdc..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/Filters.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/Operators.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/Operators.cpython-36.pyc deleted file mode 100644 index 8c69c82..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/Operators.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 14b18fa..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/common.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/common.cpython-36.pyc deleted file mode 100644 index b60b756..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/common.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/__pycache__/functions.cpython-36.pyc b/pyqtgraph/flowchart/library/__pycache__/functions.cpython-36.pyc deleted file mode 100644 index 42e5fe9..0000000 Binary files a/pyqtgraph/flowchart/library/__pycache__/functions.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/flowchart/library/common.py b/pyqtgraph/flowchart/library/common.py deleted file mode 100644 index 1f5613c..0000000 --- a/pyqtgraph/flowchart/library/common.py +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding: utf-8 -*- -from ...Qt import QtCore, QtGui -from ...widgets.SpinBox import SpinBox -#from ...SignalProxy import SignalProxy -from ...WidgetGroup import WidgetGroup -#from ColorMapper import ColorMapper -from ..Node import Node -import numpy as np -from ...widgets.ColorButton import ColorButton -try: - import metaarray - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - - -def generateUi(opts): - """Convenience function for generating common UI types""" - widget = QtGui.QWidget() - l = QtGui.QFormLayout() - l.setSpacing(0) - widget.setLayout(l) - ctrls = {} - row = 0 - for opt in opts: - if len(opt) == 2: - k, t = opt - o = {} - elif len(opt) == 3: - k, t, o = opt - else: - raise Exception("Widget specification must be (name, type) or (name, type, {opts})") - - ## clean out these options so they don't get sent to SpinBox - hidden = o.pop('hidden', False) - tip = o.pop('tip', None) - - if t == 'intSpin': - w = QtGui.QSpinBox() - if 'max' in o: - w.setMaximum(o['max']) - if 'min' in o: - w.setMinimum(o['min']) - if 'value' in o: - w.setValue(o['value']) - elif t == 'doubleSpin': - w = QtGui.QDoubleSpinBox() - if 'max' in o: - w.setMaximum(o['max']) - if 'min' in o: - w.setMinimum(o['min']) - if 'value' in o: - w.setValue(o['value']) - elif t == 'spin': - w = SpinBox() - w.setOpts(**o) - elif t == 'check': - w = QtGui.QCheckBox() - if 'checked' in o: - w.setChecked(o['checked']) - elif t == 'combo': - w = QtGui.QComboBox() - for i in o['values']: - w.addItem(i) - #elif t == 'colormap': - #w = ColorMapper() - elif t == 'color': - w = ColorButton() - else: - raise Exception("Unknown widget type '%s'" % str(t)) - - if tip is not None: - w.setToolTip(tip) - w.setObjectName(k) - l.addRow(k, w) - if hidden: - w.hide() - label = l.labelForField(w) - label.hide() - - ctrls[k] = w - w.rowNum = row - row += 1 - group = WidgetGroup(widget) - return widget, group, ctrls - - -class CtrlNode(Node): - """Abstract class for nodes with auto-generated control UI""" - - sigStateChanged = QtCore.Signal(object) - - def __init__(self, name, ui=None, terminals=None): - if terminals is None: - terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} - Node.__init__(self, name=name, terminals=terminals) - - if ui is None: - if hasattr(self, 'uiTemplate'): - ui = self.uiTemplate - else: - ui = [] - - self.ui, self.stateGroup, self.ctrls = generateUi(ui) - self.stateGroup.sigChanged.connect(self.changed) - - def ctrlWidget(self): - return self.ui - - def changed(self): - self.update() - self.sigStateChanged.emit(self) - - def process(self, In, display=True): - out = self.processData(In) - return {'Out': out} - - def saveState(self): - state = Node.saveState(self) - state['ctrl'] = self.stateGroup.state() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - if self.stateGroup is not None: - self.stateGroup.setState(state.get('ctrl', {})) - - def hideRow(self, name): - w = self.ctrls[name] - l = self.ui.layout().labelForField(w) - w.hide() - l.hide() - - def showRow(self, name): - w = self.ctrls[name] - l = self.ui.layout().labelForField(w) - w.show() - l.show() - - -class PlottingCtrlNode(CtrlNode): - """Abstract class for CtrlNodes that can connect to plots.""" - - def __init__(self, name, ui=None, terminals=None): - #print "PlottingCtrlNode.__init__ called." - CtrlNode.__init__(self, name, ui=ui, terminals=terminals) - self.plotTerminal = self.addOutput('plot', optional=True) - - def connected(self, term, remote): - CtrlNode.connected(self, term, remote) - if term is not self.plotTerminal: - return - node = remote.node() - node.sigPlotChanged.connect(self.connectToPlot) - self.connectToPlot(node) - - def disconnected(self, term, remote): - CtrlNode.disconnected(self, term, remote) - if term is not self.plotTerminal: - return - remote.node().sigPlotChanged.disconnect(self.connectToPlot) - self.disconnectFromPlot(remote.node().getPlot()) - - def connectToPlot(self, node): - """Define what happens when the node is connected to a plot""" - raise Exception("Must be re-implemented in subclass") - - def disconnectFromPlot(self, plot): - """Define what happens when the node is disconnected from a plot""" - raise Exception("Must be re-implemented in subclass") - - def process(self, In, display=True): - out = CtrlNode.process(self, In, display) - out['plot'] = None - return out - - -def metaArrayWrapper(fn): - def newFn(self, data, *args, **kargs): - if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - d1 = fn(self, data.view(np.ndarray), *args, **kargs) - info = data.infoCopy() - if d1.shape != data.shape: - for i in range(data.ndim): - if 'values' in info[i]: - info[i]['values'] = info[i]['values'][:d1.shape[i]] - return metaarray.MetaArray(d1, info=info) - else: - return fn(self, data, *args, **kargs) - return newFn - diff --git a/pyqtgraph/flowchart/library/functions.py b/pyqtgraph/flowchart/library/functions.py deleted file mode 100644 index cb7fb41..0000000 --- a/pyqtgraph/flowchart/library/functions.py +++ /dev/null @@ -1,357 +0,0 @@ -import numpy as np -from ...metaarray import MetaArray -from ...python2_3 import basestring, xrange - - -def downsample(data, n, axis=0, xvals='subsample'): - """Downsample by averaging points together across axis. - If multiple axes are specified, runs once per axis. - If a metaArray is given, then the axis values can be either subsampled - or downsampled to match. - """ - ma = None - if (hasattr(data, 'implements') and data.implements('MetaArray')): - ma = data - data = data.view(np.ndarray) - - - if hasattr(axis, '__len__'): - if not hasattr(n, '__len__'): - n = [n]*len(axis) - for i in range(len(axis)): - data = downsample(data, n[i], axis[i]) - return data - - nPts = int(data.shape[axis] / n) - s = list(data.shape) - s[axis] = nPts - s.insert(axis+1, n) - sl = [slice(None)] * data.ndim - sl[axis] = slice(0, nPts*n) - d1 = data[tuple(sl)] - #print d1.shape, s - d1.shape = tuple(s) - d2 = d1.mean(axis+1) - - if ma is None: - return d2 - else: - info = ma.infoCopy() - if 'values' in info[axis]: - if xvals == 'subsample': - info[axis]['values'] = info[axis]['values'][::n][:nPts] - elif xvals == 'downsample': - info[axis]['values'] = downsample(info[axis]['values'], n) - return MetaArray(d2, info=info) - - -def applyFilter(data, b, a, padding=100, bidir=True): - """Apply a linear filter with coefficients a, b. Optionally pad the data before filtering - and/or run the filter in both directions.""" - try: - import scipy.signal - except ImportError: - raise Exception("applyFilter() requires the package scipy.signal.") - - d1 = data.view(np.ndarray) - - if padding > 0: - d1 = np.hstack([d1[:padding], d1, d1[-padding:]]) - - if bidir: - d1 = scipy.signal.lfilter(b, a, scipy.signal.lfilter(b, a, d1)[::-1])[::-1] - else: - d1 = scipy.signal.lfilter(b, a, d1) - - if padding > 0: - d1 = d1[padding:-padding] - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d1, info=data.infoCopy()) - else: - return d1 - -def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True): - """return data passed through bessel filter""" - try: - import scipy.signal - except ImportError: - raise Exception("besselFilter() requires the package scipy.signal.") - - if dt is None: - try: - tvals = data.xvals('Time') - dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) - except: - dt = 1.0 - - b,a = scipy.signal.bessel(order, cutoff * dt, btype=btype) - - return applyFilter(data, b, a, bidir=bidir) - #base = data.mean() - #d1 = scipy.signal.lfilter(b, a, data.view(ndarray)-base) + base - #if (hasattr(data, 'implements') and data.implements('MetaArray')): - #return MetaArray(d1, info=data.infoCopy()) - #return d1 - -def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True): - """return data passed through bessel filter""" - try: - import scipy.signal - except ImportError: - raise Exception("butterworthFilter() requires the package scipy.signal.") - - if dt is None: - try: - tvals = data.xvals('Time') - dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) - except: - dt = 1.0 - - if wStop is None: - wStop = wPass * 2.0 - ord, Wn = scipy.signal.buttord(wPass*dt*2., wStop*dt*2., gPass, gStop) - #print "butterworth ord %f Wn %f c %f sc %f" % (ord, Wn, cutoff, stopCutoff) - b,a = scipy.signal.butter(ord, Wn, btype=btype) - - return applyFilter(data, b, a, bidir=bidir) - - -def rollingSum(data, n): - d1 = data.copy() - d1[1:] += d1[:-1] # integrate - d2 = np.empty(len(d1) - n + 1, dtype=data.dtype) - d2[0] = d1[n-1] # copy first point - d2[1:] = d1[n:] - d1[:-n] # subtract - return d2 - - -def mode(data, bins=None): - """Returns location max value from histogram.""" - if bins is None: - bins = int(len(data)/10.) - if bins < 2: - bins = 2 - y, x = np.histogram(data, bins=bins) - ind = np.argmax(y) - mode = 0.5 * (x[ind] + x[ind+1]) - return mode - -def modeFilter(data, window=500, step=None, bins=None): - """Filter based on histogram-based mode function""" - d1 = data.view(np.ndarray) - vals = [] - l2 = int(window/2.) - if step is None: - step = l2 - i = 0 - while True: - if i > len(data)-step: - break - vals.append(mode(d1[i:i+window], bins)) - i += step - - chunks = [np.linspace(vals[0], vals[0], l2)] - for i in range(len(vals)-1): - chunks.append(np.linspace(vals[i], vals[i+1], step)) - remain = len(data) - step*(len(vals)-1) - l2 - chunks.append(np.linspace(vals[-1], vals[-1], remain)) - d2 = np.hstack(chunks) - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d2, info=data.infoCopy()) - return d2 - -def denoise(data, radius=2, threshold=4): - """Very simple noise removal function. Compares a point to surrounding points, - replaces with nearby values if the difference is too large.""" - - - r2 = radius * 2 - d1 = data.view(np.ndarray) - d2 = d1[radius:] - d1[:-radius] #a derivative - #d3 = data[r2:] - data[:-r2] - #d4 = d2 - d3 - stdev = d2.std() - #print "denoise: stdev of derivative:", stdev - mask1 = d2 > stdev*threshold #where derivative is large and positive - mask2 = d2 < -stdev*threshold #where derivative is large and negative - maskpos = mask1[:-radius] * mask2[radius:] #both need to be true - maskneg = mask1[radius:] * mask2[:-radius] - mask = maskpos + maskneg - d5 = np.where(mask, d1[:-r2], d1[radius:-radius]) #where both are true replace the value with the value from 2 points before - d6 = np.empty(d1.shape, dtype=d1.dtype) #add points back to the ends - d6[radius:-radius] = d5 - d6[:radius] = d1[:radius] - d6[-radius:] = d1[-radius:] - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d6, info=data.infoCopy()) - return d6 - -def adaptiveDetrend(data, x=None, threshold=3.0): - """Return the signal with baseline removed. Discards outliers from baseline measurement.""" - try: - import scipy.signal - except ImportError: - raise Exception("adaptiveDetrend() requires the package scipy.signal.") - - if x is None: - x = data.xvals(0) - - d = data.view(np.ndarray) - - d2 = scipy.signal.detrend(d) - - stdev = d2.std() - mask = abs(d2) < stdev*threshold - #d3 = where(mask, 0, d2) - #d4 = d2 - lowPass(d3, cutoffs[1], dt=dt) - - lr = scipy.stats.linregress(x[mask], d[mask]) - base = lr[1] + lr[0]*x - d4 = d - base - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d4, info=data.infoCopy()) - return d4 - - -def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False): - """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers. - If offsetOnly is True, then only the offset from the beginning of the trace is subtracted. - """ - - d1 = data.view(np.ndarray) - d2 = [d1[:window], d1[-window:]] - v = [0, 0] - for i in [0, 1]: - d3 = d2[i] - stdev = d3.std() - mask = abs(d3-np.median(d3)) < stdev*threshold - d4 = d3[mask] - y, x = np.histogram(d4, bins=bins) - ind = np.argmax(y) - v[i] = 0.5 * (x[ind] + x[ind+1]) - - if offsetOnly: - d3 = data.view(np.ndarray) - v[0] - else: - base = np.linspace(v[0], v[1], len(data)) - d3 = data.view(np.ndarray) - base - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d3, info=data.infoCopy()) - return d3 - -def concatenateColumns(data): - """Returns a single record array with columns taken from the elements in data. - data should be a list of elements, which can be either record arrays or tuples (name, type, data) - """ - - ## first determine dtype - dtype = [] - names = set() - maxLen = 0 - for element in data: - if isinstance(element, np.ndarray): - ## use existing columns - for i in range(len(element.dtype)): - name = element.dtype.names[i] - dtype.append((name, element.dtype[i])) - maxLen = max(maxLen, len(element)) - else: - name, type, d = element - if type is None: - type = suggestDType(d) - dtype.append((name, type)) - if isinstance(d, list) or isinstance(d, np.ndarray): - maxLen = max(maxLen, len(d)) - if name in names: - raise Exception('Name "%s" repeated' % name) - names.add(name) - - - - ## create empty array - out = np.empty(maxLen, dtype) - - ## fill columns - for element in data: - if isinstance(element, np.ndarray): - for i in range(len(element.dtype)): - name = element.dtype.names[i] - try: - out[name] = element[name] - except: - print("Column:", name) - print("Input shape:", element.shape, element.dtype) - print("Output shape:", out.shape, out.dtype) - raise - else: - name, type, d = element - out[name] = d - - return out - -def suggestDType(x): - """Return a suitable dtype for x""" - if isinstance(x, list) or isinstance(x, tuple): - if len(x) == 0: - raise Exception('can not determine dtype for empty list') - x = x[0] - - if hasattr(x, 'dtype'): - return x.dtype - elif isinstance(x, float): - return float - elif isinstance(x, int): - return int - #elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead. - #return ' len(ft)/2.: - break - mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 - for j in range(ind1, ind2+1): - phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. - re = mag * np.cos(phase) - im = mag * np.sin(phase) - ft[j] = re + im*1j - ft[len(ft)-j] = re - im*1j - - data2 = np.fft.ifft(ft).real - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return metaarray.MetaArray(data2, info=data.infoCopy()) - else: - return data2 - - - \ No newline at end of file diff --git a/pyqtgraph/frozenSupport.py b/pyqtgraph/frozenSupport.py deleted file mode 100644 index c42a12e..0000000 --- a/pyqtgraph/frozenSupport.py +++ /dev/null @@ -1,52 +0,0 @@ -## Definitions helpful in frozen environments (eg py2exe) -import os, sys, zipfile - -def listdir(path): - """Replacement for os.listdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.listdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.listdir(path) - - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - results = set() - for name in contents: - # components in zip archive paths are always separated by forward slash - if name.startswith(archivePath) and len(name) > len(archivePath): - name = name[len(archivePath):].split('/')[0] - results.add(name) - return list(results) - -def isdir(path): - """Replacement for os.path.isdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.path.isdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.path.isdir(path) - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end - for c in contents: - if c.startswith(archivePath): - return True - return False - - -def splitZip(path): - """Splits a path containing a zip file into (zipfile, subpath). - If there is no zip file, returns (path, None)""" - components = os.path.normpath(path).split(os.sep) - for index, component in enumerate(components): - if component.endswith('.zip'): - zipPath = os.sep.join(components[0:index+1]) - archivePath = ''.join([x+'/' for x in components[index+1:]]) - return (zipPath, archivePath) - else: - return (path, None) - - \ No newline at end of file diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py deleted file mode 100644 index bd86356..0000000 --- a/pyqtgraph/functions.py +++ /dev/null @@ -1,2668 +0,0 @@ -# -*- coding: utf-8 -*- -""" -functions.py - Miscellaneous functions with no other home -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from __future__ import division - -import ctypes -import decimal -import re -import struct -import sys -import warnings - -import numpy as np -from .util.cupy_helper import getCupy - -from . import debug, reload -from .Qt import QtGui, QtCore, QT_LIB, QtVersion -from . import Qt -from .metaarray import MetaArray -from collections import OrderedDict -from .python2_3 import asUnicode, basestring - -Colors = { - 'b': QtGui.QColor(0,0,255,255), - 'g': QtGui.QColor(0,255,0,255), - 'r': QtGui.QColor(255,0,0,255), - 'c': QtGui.QColor(0,255,255,255), - 'm': QtGui.QColor(255,0,255,255), - 'y': QtGui.QColor(255,255,0,255), - 'k': QtGui.QColor(0,0,0,255), - 'w': QtGui.QColor(255,255,255,255), - 'd': QtGui.QColor(150,150,150,255), - 'l': QtGui.QColor(200,200,200,255), - 's': QtGui.QColor(100,100,150,255), -} - -SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') -SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' -SI_PREFIX_EXPONENTS = dict([(SI_PREFIXES[i], (i-8)*3) for i in range(len(SI_PREFIXES))]) -SI_PREFIX_EXPONENTS['u'] = -6 - -FLOAT_REGEX = re.compile(r'(?P[+-]?((((\d+(\.\d*)?)|(\d*\.\d+))([eE][+-]?\d+)?)|((?i:nan)|(inf))))\s*((?P[u' + SI_PREFIXES + r']?)(?P\w.*))?$') -INT_REGEX = re.compile(r'(?P[+-]?\d+)\s*(?P[u' + SI_PREFIXES + r']?)(?P.*)$') - - -def siScale(x, minVal=1e-25, allowUnicode=True): - """ - Return the recommended scale factor and SI prefix string for x. - - Example:: - - siScale(0.0001) # returns (1e6, 'μ') - # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits - """ - - if isinstance(x, decimal.Decimal): - x = float(x) - - try: - if np.isnan(x) or np.isinf(x): - return(1, '') - except: - print(x, type(x)) - raise - if abs(x) < minVal: - m = 0 - x = 0 - else: - m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) - - if m == 0: - pref = '' - elif m < -8 or m > 8: - pref = 'e%d' % (m*3) - else: - if allowUnicode: - pref = SI_PREFIXES[m+8] - else: - pref = SI_PREFIXES_ASCII[m+8] - m1 = -3*m - p = 10.**m1 - - return (p, pref) - - -def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): - """ - Return the number x formatted in engineering notation with SI prefix. - - Example:: - siFormat(0.0001, suffix='V') # returns "100 μV" - """ - - if space is True: - space = ' ' - if space is False: - space = '' - - - (p, pref) = siScale(x, minVal, allowUnicode) - if not (len(pref) > 0 and pref[0] == 'e'): - pref = space + pref - - if error is None: - fmt = "%." + str(precision) + "g%s%s" - return fmt % (x*p, pref, suffix) - else: - if allowUnicode: - plusminus = space + asUnicode("±") + space - else: - plusminus = " +/- " - fmt = "%." + str(precision) + "g%s%s%s%s" - return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) - - -def siParse(s, regex=FLOAT_REGEX, suffix=None): - """Convert a value written in SI notation to a tuple (number, si_prefix, suffix). - - Example:: - - siParse('100 µV") # returns ('100', 'µ', 'V') - - Note that in the above example, the µ symbol is the "micro sign" (UTF-8 - 0xC2B5), as opposed to the Greek letter mu (UTF-8 0xCEBC). - - Parameters - ---------- - s : str - The string to parse. - regex : re.Pattern, optional - Compiled regular expression object for parsing. The default is a - general-purpose regex for parsing floating point expressions, - potentially containing an SI prefix and a suffix. - suffix : str, optional - Suffix to check for in ``s``. The default (None) indicates there may or - may not be a suffix contained in the string and it is returned if - found. An empty string ``""`` is handled differently: if the string - contains a suffix, it is discarded. This enables interpreting - characters following the numerical value as an SI prefix. - """ - s = asUnicode(s) - s = s.strip() - if suffix is not None and len(suffix) > 0: - if s[-len(suffix):] != suffix: - raise ValueError("String '%s' does not have the expected suffix '%s'" % (s, suffix)) - s = s[:-len(suffix)] + 'X' # add a fake suffix so the regex still picks up the si prefix - - # special case: discard any extra characters if suffix is explicitly empty - if suffix == "": - s += 'X' - - m = regex.match(s) - if m is None: - raise ValueError('Cannot parse number "%s"' % s) - - try: - sip = m.group('siPrefix') - except IndexError: - sip = '' - - if suffix is None: - try: - suf = m.group('suffix') - except IndexError: - suf = '' - else: - suf = suffix - - return m.group('number'), '' if sip is None else sip, '' if suf is None else suf - - -def siEval(s, typ=float, regex=FLOAT_REGEX, suffix=None): - """ - Convert a value written in SI notation to its equivalent prefixless value. - - Example:: - - siEval("100 μV") # returns 0.0001 - """ - val, siprefix, suffix = siParse(s, regex, suffix=suffix) - v = typ(val) - return siApply(v, siprefix) - - -def siApply(val, siprefix): - """ - """ - n = SI_PREFIX_EXPONENTS[siprefix] if siprefix != '' else 0 - if n > 0: - return val * 10**n - elif n < 0: - # this case makes it possible to use Decimal objects here - return val / 10**-n - else: - return val - - -class Color(QtGui.QColor): - def __init__(self, *args): - QtGui.QColor.__init__(self, mkColor(*args)) - - def glColor(self): - """Return (r,g,b,a) normalized for use in opengl""" - return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.) - - def __getitem__(self, ind): - return (self.red, self.green, self.blue, self.alpha)[ind]() - - -def mkColor(*args): - """ - Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: - - ================ ================================================ - 'c' one of: r, g, b, c, m, y, k, w - R, G, B, [A] integers 0-255 - (R, G, B, [A]) tuple of integers 0-255 - float greyscale, 0.0-1.0 - int see :func:`intColor() ` - (int, hues) see :func:`intColor() ` - "RGB" hexadecimal strings; may begin with '#' - "RGBA" - "RRGGBB" - "RRGGBBAA" - QColor QColor instance; makes a copy. - ================ ================================================ - """ - err = 'Not sure how to make a color from "%s"' % str(args) - if len(args) == 1: - if isinstance(args[0], basestring): - c = args[0] - if c[0] == '#': - c = c[1:] - if len(c) == 1: - try: - return Colors[c] - except KeyError: - raise ValueError('No color named "%s"' % c) - if len(c) == 3: - r = int(c[0]*2, 16) - g = int(c[1]*2, 16) - b = int(c[2]*2, 16) - a = 255 - elif len(c) == 4: - r = int(c[0]*2, 16) - g = int(c[1]*2, 16) - b = int(c[2]*2, 16) - a = int(c[3]*2, 16) - elif len(c) == 6: - r = int(c[0:2], 16) - g = int(c[2:4], 16) - b = int(c[4:6], 16) - a = 255 - elif len(c) == 8: - r = int(c[0:2], 16) - g = int(c[2:4], 16) - b = int(c[4:6], 16) - a = int(c[6:8], 16) - elif isinstance(args[0], QtGui.QColor): - return QtGui.QColor(args[0]) - elif np.issubdtype(type(args[0]), np.floating): - r = g = b = int(args[0] * 255) - a = 255 - elif hasattr(args[0], '__len__'): - if len(args[0]) == 3: - (r, g, b) = args[0] - a = 255 - elif len(args[0]) == 4: - (r, g, b, a) = args[0] - elif len(args[0]) == 2: - return intColor(*args[0]) - else: - raise TypeError(err) - elif np.issubdtype(type(args[0]), np.integer): - return intColor(args[0]) - else: - raise TypeError(err) - elif len(args) == 3: - (r, g, b) = args - a = 255 - elif len(args) == 4: - (r, g, b, a) = args - else: - raise TypeError(err) - - args = [r,g,b,a] - args = [0 if np.isnan(a) or np.isinf(a) else a for a in args] - args = list(map(int, args)) - return QtGui.QColor(*args) - - -def mkBrush(*args, **kwds): - """ - | Convenience function for constructing Brush. - | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() ` - | Calling mkBrush(None) returns an invisible brush. - """ - if 'color' in kwds: - color = kwds['color'] - elif len(args) == 1: - arg = args[0] - if arg is None: - return QtGui.QBrush(QtCore.Qt.NoBrush) - elif isinstance(arg, QtGui.QBrush): - return QtGui.QBrush(arg) - else: - color = arg - elif len(args) > 1: - color = args - return QtGui.QBrush(mkColor(color)) - - -def mkPen(*args, **kargs): - """ - Convenience function for constructing QPen. - - Examples:: - - mkPen(color) - mkPen(color, width=2) - mkPen(cosmetic=False, width=4.5, color='r') - mkPen({'color': "FF0", width: 2}) - mkPen(None) # (no pen) - - In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ - - color = kargs.get('color', None) - width = kargs.get('width', 1) - style = kargs.get('style', None) - dash = kargs.get('dash', None) - cosmetic = kargs.get('cosmetic', True) - hsv = kargs.get('hsv', None) - - if len(args) == 1: - arg = args[0] - if isinstance(arg, dict): - return mkPen(**arg) - if isinstance(arg, QtGui.QPen): - return QtGui.QPen(arg) ## return a copy of this pen - elif arg is None: - style = QtCore.Qt.NoPen - else: - color = arg - if len(args) > 1: - color = args - - if color is None: - color = mkColor('l') - if hsv is not None: - color = hsvColor(*hsv) - else: - color = mkColor(color) - - pen = QtGui.QPen(QtGui.QBrush(color), width) - pen.setCosmetic(cosmetic) - if style is not None: - pen.setStyle(style) - if dash is not None: - pen.setDashPattern(dash) - return pen - - -def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): - """Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)""" - c = QtGui.QColor() - c.setHsvF(hue, sat, val, alpha) - return c - - -def colorTuple(c): - """Return a tuple (R,G,B,A) from a QColor""" - return (c.red(), c.green(), c.blue(), c.alpha()) - - -def colorStr(c): - """Generate a hex string code from a QColor""" - return ('%02x'*4) % colorTuple(c) - - -def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255): - """ - Creates a QColor from a single index. Useful for stepping through a predefined list of colors. - - The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be - - Colors are chosen by cycling across hues while varying the value (brightness). - By default, this selects from a list of 9 hues.""" - hues = int(hues) - values = int(values) - ind = int(index) % (hues * values) - indh = ind % hues - indv = ind // hues - if values > 1: - v = minValue + indv * ((maxValue-minValue) // (values-1)) - else: - v = maxValue - h = minHue + (indh * (maxHue-minHue)) // hues - - c = QtGui.QColor() - c.setHsv(h, sat, v) - c.setAlpha(alpha) - return c - - -def glColor(*args, **kargs): - """ - Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 - Accepts same arguments as :func:`mkColor `. - """ - c = mkColor(*args, **kargs) - return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.) - - - -def makeArrowPath(headLen=20, headWidth=None, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0): - """ - Construct a path outlining an arrow with the given dimensions. - The arrow points in the -x direction with tip positioned at 0,0. - If *headWidth* is supplied, it overrides *tipAngle* (in degrees). - If *tailLen* is None, no tail will be drawn. - """ - if headWidth is None: - headWidth = headLen * np.tan(tipAngle * 0.5 * np.pi/180.) - path = QtGui.QPainterPath() - path.moveTo(0,0) - path.lineTo(headLen, -headWidth) - if tailLen is None: - innerY = headLen - headWidth * np.tan(baseAngle*np.pi/180.) - path.lineTo(innerY, 0) - else: - tailWidth *= 0.5 - innerY = headLen - (headWidth-tailWidth) * np.tan(baseAngle*np.pi/180.) - path.lineTo(innerY, -tailWidth) - path.lineTo(headLen + tailLen, -tailWidth) - path.lineTo(headLen + tailLen, tailWidth) - path.lineTo(innerY, tailWidth) - path.lineTo(headLen, headWidth) - path.lineTo(0,0) - return path - - -def eq(a, b): - """The great missing equivalence function: Guaranteed evaluation to a single bool value. - - This function has some important differences from the == operator: - - 1. Returns True if a IS b, even if a==b still evaluates to False. - 2. While a is b will catch the case with np.nan values, special handling is done for distinct - float('nan') instances using np.isnan. - 3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur - (AtrtibuteError, ValueError). - 4. When comparing arrays, returns False if the array shapes are not the same. - 5. When comparing arrays of the same shape, returns True only if all elements are equal (whereas - the == operator would return a boolean array). - 6. Collections (dict, list, etc.) must have the same type to be considered equal. One - consequence is that comparing a dict to an OrderedDict will always return False. - """ - if a is b: - return True - - # The above catches np.nan, but not float('nan') - if isinstance(a, float) and isinstance(b, float): - if np.isnan(a) and np.isnan(b): - return True - - # Avoid comparing large arrays against scalars; this is expensive and we know it should return False. - aIsArr = isinstance(a, (np.ndarray, MetaArray)) - bIsArr = isinstance(b, (np.ndarray, MetaArray)) - if (aIsArr or bIsArr) and type(a) != type(b): - return False - - # If both inputs are arrays, we can speeed up comparison if shapes / dtypes don't match - # NOTE: arrays of dissimilar type should be considered unequal even if they are numerically - # equal because they may behave differently when computed on. - if aIsArr and bIsArr and (a.shape != b.shape or a.dtype != b.dtype): - return False - - # Recursively handle common containers - if isinstance(a, dict) and isinstance(b, dict): - if type(a) != type(b) or len(a) != len(b): - return False - if set(a.keys()) != set(b.keys()): - return False - for k, v in a.items(): - if not eq(v, b[k]): - return False - if isinstance(a, OrderedDict) or sys.version_info >= (3, 7): - for a_item, b_item in zip(a.items(), b.items()): - if not eq(a_item, b_item): - return False - return True - if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): - if type(a) != type(b) or len(a) != len(b): - return False - for v1,v2 in zip(a, b): - if not eq(v1, v2): - return False - return True - - # Test for equivalence. - # If the test raises a recognized exception, then return Falase - try: - try: - # Sometimes running catch_warnings(module=np) generates AttributeError ??? - catcher = warnings.catch_warnings(module=np) # ignore numpy futurewarning (numpy v. 1.10) - catcher.__enter__() - except Exception: - catcher = None - e = a==b - except (ValueError, AttributeError): - return False - except: - print('failed to evaluate equivalence for:') - print(" a:", str(type(a)), str(a)) - print(" b:", str(type(b)), str(b)) - raise - finally: - if catcher is not None: - catcher.__exit__(None, None, None) - - t = type(e) - if t is bool: - return e - elif t is np.bool_: - return bool(e) - elif isinstance(e, np.ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')): - try: ## disaster: if a is an empty array and b is not, then e.all() is True - if a.shape != b.shape: - return False - except: - return False - if (hasattr(e, 'implements') and e.implements('MetaArray')): - return e.asarray().all() - else: - return e.all() - else: - raise Exception("== operator returned type %s" % str(type(e))) - - -def affineSliceCoords(shape, origin, vectors, axes): - """Return the array of coordinates used to sample data arrays in affineSlice(). - """ - # sanity check - if len(shape) != len(vectors): - raise Exception("shape and vectors must have same length.") - if len(origin) != len(axes): - raise Exception("origin and axes must have same length.") - for v in vectors: - if len(v) != len(axes): - raise Exception("each vector must be same length as axes.") - - shape = list(map(np.ceil, shape)) - - ## make sure vectors are arrays - if not isinstance(vectors, np.ndarray): - vectors = np.array(vectors) - if not isinstance(origin, np.ndarray): - origin = np.array(origin) - origin.shape = (len(axes),) + (1,)*len(shape) - - ## Build array of sample locations. - grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes - x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic - x += origin - - return x - - -def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): - """ - Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays - such as MRI images for viewing as 1D or 2D data. - - The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is - possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger - datasets. The original data is interpolated onto a new array of coordinates using either interpolateArray if order<2 - or scipy.ndimage.map_coordinates otherwise. - - For a graphical interface to this function, see :func:`ROI.getArrayRegion ` - - ============== ==================================================================================================== - **Arguments:** - *data* (ndarray) the original dataset - *shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape)) - *origin* the location in the original dataset that will become the origin of the sliced data. - *vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same - length as *axes*. If the vectors are not unit length, the result will be scaled relative to the - original data. If the vectors are not orthogonal, the result will be sheared relative to the - original data. - *axes* The axes in the original dataset which correspond to the slice *vectors* - *order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates - for more information. - *returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select - values from the original dataset. - *All extra keyword arguments are passed to scipy.ndimage.map_coordinates.* - -------------------------------------------------------------------------------------------------------------------- - ============== ==================================================================================================== - - Note the following must be true: - - | len(shape) == len(vectors) - | len(origin) == len(axes) == len(vectors[i]) - - Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes - - * data = array with dims (time, x, y, z) = (100, 40, 40, 40) - * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) - * The origin of the slice will be at (x,y,z) = (40, 0, 0) - * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) - - The call for this example would look like:: - - affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) - - """ - x = affineSliceCoords(shape, origin, vectors, axes) - - ## transpose data so slice axes come first - trAx = list(range(data.ndim)) - for ax in axes: - trAx.remove(ax) - tr1 = tuple(axes) + tuple(trAx) - data = data.transpose(tr1) - #print "tr1:", tr1 - ## dims are now [(slice axes), (other axes)] - - if order > 1: - try: - import scipy.ndimage - except ImportError: - raise ImportError("Interpolating with order > 1 requires the scipy.ndimage module, but it could not be imported.") - - # iterate manually over unused axes since map_coordinates won't do it for us - extraShape = data.shape[len(axes):] - output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) - for inds in np.ndindex(*extraShape): - ind = (Ellipsis,) + inds - output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) - else: - # map_coordinates expects the indexes as the first axis, whereas - # interpolateArray expects indexes at the last axis. - tr = tuple(range(1, x.ndim)) + (0,) - output = interpolateArray(data, x.transpose(tr), order=order) - - tr = list(range(output.ndim)) - trb = [] - for i in range(min(axes)): - ind = tr1.index(i) + (len(shape)-len(axes)) - tr.remove(ind) - trb.append(ind) - tr2 = tuple(trb+tr) - - ## Untranspose array before returning - output = output.transpose(tr2) - if returnCoords: - return (output, x) - else: - return output - - -def interweaveArrays(*args): - """ - Parameters - ---------- - - args : numpy.ndarray - series of 1D numpy arrays of the same length and dtype - - Returns - ------- - numpy.ndarray - A numpy array with all the input numpy arrays interwoven - - Examples - -------- - - >>> result = interweaveArrays(numpy.ndarray([0, 2, 4]), numpy.ndarray([1, 3, 5])) - >>> result - array([0, 1, 2, 3, 4, 5]) - """ - - size = sum(x.size for x in args) - result = np.empty((size,), dtype=args[0].dtype) - n = len(args) - for index, array in enumerate(args): - result[index::n] = array - return result - - -def interpolateArray(data, x, default=0.0, order=1): - """ - N-dimensional interpolation similar to scipy.ndimage.map_coordinates. - - This function returns linearly-interpolated values sampled from a regular - grid of data. It differs from `ndimage.map_coordinates` by allowing broadcasting - within the input array. - - ============== =========================================================================================== - **Arguments:** - *data* Array of any shape containing the values to be interpolated. - *x* Array with (shape[-1] <= data.ndim) containing the locations within *data* to interpolate. - (note: the axes for this argument are transposed relative to the same argument for - `ndimage.map_coordinates`). - *default* Value to return for locations in *x* that are outside the bounds of *data*. - *order* Order of interpolation: 0=nearest, 1=linear. - ============== =========================================================================================== - - Returns array of shape (x.shape[:-1] + data.shape[x.shape[-1]:]) - - For example, assume we have the following 2D image data:: - - >>> data = np.array([[1, 2, 4 ], - [10, 20, 40 ], - [100, 200, 400]]) - - To compute a single interpolated point from this data:: - - >>> x = np.array([(0.5, 0.5)]) - >>> interpolateArray(data, x) - array([ 8.25]) - - To compute a 1D list of interpolated locations:: - - >>> x = np.array([(0.5, 0.5), - (1.0, 1.0), - (1.0, 2.0), - (1.5, 0.0)]) - >>> interpolateArray(data, x) - array([ 8.25, 20. , 40. , 55. ]) - - To compute a 2D array of interpolated locations:: - - >>> x = np.array([[(0.5, 0.5), (1.0, 2.0)], - [(1.0, 1.0), (1.5, 0.0)]]) - >>> interpolateArray(data, x) - array([[ 8.25, 40. ], - [ 20. , 55. ]]) - - ..and so on. The *x* argument may have any shape as long as - ```x.shape[-1] <= data.ndim```. In the case that - ```x.shape[-1] < data.ndim```, then the remaining axes are simply - broadcasted as usual. For example, we can interpolate one location - from an entire row of the data:: - - >>> x = np.array([[0.5]]) - >>> interpolateArray(data, x) - array([[ 5.5, 11. , 22. ]]) - - This is useful for interpolating from arrays of colors, vertexes, etc. - """ - if order not in (0, 1): - raise ValueError("interpolateArray requires order=0 or 1 (got %s)" % order) - - prof = debug.Profiler() - - nd = data.ndim - md = x.shape[-1] - if md > nd: - raise TypeError("x.shape[-1] must be less than or equal to data.ndim") - - totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes - if order == 0: - xinds = np.round(x).astype(int) # NOTE: for 0.5 this rounds to the nearest *even* number - for ax in range(md): - mask = (xinds[...,ax] >= 0) & (xinds[...,ax] <= data.shape[ax]-1) - xinds[...,ax][~mask] = 0 - # keep track of points that need to be set to default - totalMask &= mask - result = data[tuple([xinds[...,i] for i in range(xinds.shape[-1])])] - - elif order == 1: - # First we generate arrays of indexes that are needed to - # extract the data surrounding each point - fields = np.mgrid[(slice(0,order+1),) * md] - xmin = np.floor(x).astype(int) - xmax = xmin + 1 - indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]]) - fieldInds = [] - for ax in range(md): - mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1) - # keep track of points that need to be set to default - totalMask &= mask - - # ..and keep track of indexes that are out of bounds - # (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out - # of bounds, but the interpolation will work anyway) - mask &= (xmax[...,ax] < data.shape[ax]) - axisIndex = indexes[...,ax][fields[ax]] - axisIndex[axisIndex < 0] = 0 - axisIndex[axisIndex >= data.shape[ax]] = 0 - fieldInds.append(axisIndex) - prof() - - # Get data values surrounding each requested point - fieldData = data[tuple(fieldInds)] - prof() - - ## Interpolate - s = np.empty((md,) + fieldData.shape, dtype=float) - dx = x - xmin - # reshape fields for arithmetic against dx - for ax in range(md): - f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1)) - sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax]) - sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim)) - s[ax] = sax - s = np.product(s, axis=0) - result = fieldData * s - for i in range(md): - result = result.sum(axis=0) - - prof() - - if totalMask.ndim > 0: - result[~totalMask] = default - else: - if totalMask is False: - result[:] = default - - prof() - return result - - -def subArray(data, offset, shape, stride): - """ - Unpack a sub-array from *data* using the specified offset, shape, and stride. - - Note that *stride* is specified in array elements, not bytes. - For example, we have a 2x3 array packed in a 1D array as follows:: - - data = [_, _, 00, 01, 02, _, 10, 11, 12, _] - - Then we can unpack the sub-array with this call:: - - subArray(data, offset=2, shape=(2, 3), stride=(4, 1)) - - ..which returns:: - - [[00, 01, 02], - [10, 11, 12]] - - This function operates only on the first axis of *data*. So changing - the input in the example above to have shape (10, 7) would cause the - output to have shape (2, 3, 7). - """ - data = np.ascontiguousarray(data)[offset:] - shape = tuple(shape) - extraShape = data.shape[1:] - - strides = list(data.strides[::-1]) - itemsize = strides[-1] - for s in stride[1::-1]: - strides.append(itemsize * s) - strides = tuple(strides[::-1]) - - return np.ndarray(buffer=data, shape=shape+extraShape, strides=strides, dtype=data.dtype) - - -def transformToArray(tr): - """ - Given a QTransform, return a 3x3 numpy array. - Given a QMatrix4x4, return a 4x4 numpy array. - - Example: map an array of x,y coordinates through a transform:: - - ## coordinates to map are (1,5), (2,6), (3,7), and (4,8) - coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work - - ## Make an example transform - tr = QtGui.QTransform() - tr.translate(3,4) - tr.scale(2, 0.1) - - ## convert to array - m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation - - ## map coordinates through transform - mapped = np.dot(m, coords) - """ - #return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]]) - ## The order of elements given by the method names m11..m33 is misleading-- - ## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in - ## a transformation matrix. However, with QTransform these values appear at m31 and m32. - ## So the correct interpretation is transposed: - if isinstance(tr, QtGui.QTransform): - return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]]) - elif isinstance(tr, QtGui.QMatrix4x4): - return np.array(tr.copyDataTo()).reshape(4,4) - else: - raise Exception("Transform argument must be either QTransform or QMatrix4x4.") - -def transformCoordinates(tr, coords, transpose=False): - """ - Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4. - The shape of coords must be (2,...) or (3,...) - The mapping will _ignore_ any perspective transformations. - - For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication. - Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To - allow this, use transpose=True. - - """ - - if transpose: - ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates. - coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1))) - - nd = coords.shape[0] - if isinstance(tr, np.ndarray): - m = tr - else: - m = transformToArray(tr) - m = m[:m.shape[0]-1] # remove perspective - - ## If coords are 3D and tr is 2D, assume no change for Z axis - if m.shape == (2,3) and nd == 3: - m2 = np.zeros((3,4)) - m2[:2, :2] = m[:2,:2] - m2[:2, 3] = m[:2,2] - m2[2,2] = 1 - m = m2 - - ## if coords are 2D and tr is 3D, ignore Z axis - if m.shape == (3,4) and nd == 2: - m2 = np.empty((2,3)) - m2[:,:2] = m[:2,:2] - m2[:,2] = m[:2,3] - m = m2 - - ## reshape tr and coords to prepare for multiplication - m = m.reshape(m.shape + (1,)*(coords.ndim-1)) - coords = coords[np.newaxis, ...] - - # separate scale/rotate and translation - translate = m[:,-1] - m = m[:, :-1] - - ## map coordinates and return - mapped = (m*coords).sum(axis=1) ## apply scale/rotate - mapped += translate - - if transpose: - ## move first axis to end. - mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,)) - return mapped - - - - -def solve3DTransform(points1, points2): - """ - Find a 3D transformation matrix that maps points1 onto points2. - Points must be specified as either lists of 4 Vectors or - (4, 3) arrays. - """ - import numpy.linalg - pts = [] - for inp in (points1, points2): - if isinstance(inp, np.ndarray): - A = np.empty((4,4), dtype=float) - A[:,:3] = inp[:,:3] - A[:,3] = 1.0 - else: - A = np.array([[inp[i].x(), inp[i].y(), inp[i].z(), 1] for i in range(4)]) - pts.append(A) - - ## solve 3 sets of linear equations to determine transformation matrix elements - matrix = np.zeros((4,4)) - for i in range(3): - ## solve Ax = B; x is one row of the desired transformation matrix - matrix[i] = numpy.linalg.solve(pts[0], pts[1][:,i]) - - return matrix - -def solveBilinearTransform(points1, points2): - """ - Find a bilinear transformation matrix (2x4) that maps points1 onto points2. - Points must be specified as a list of 4 Vector, Point, QPointF, etc. - - To use this matrix to map a point [x,y]:: - - mapped = np.dot(matrix, [x*y, x, y, 1]) - """ - import numpy.linalg - ## A is 4 rows (points) x 4 columns (xy, x, y, 1) - ## B is 4 rows (points) x 2 columns (x, y) - A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)]) - B = np.array([[points2[i].x(), points2[i].y()] for i in range(4)]) - - ## solve 2 sets of linear equations to determine transformation matrix elements - matrix = np.zeros((2,4)) - for i in range(2): - matrix[i] = numpy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix - - return matrix - - -def clip_array(arr, vmin, vmax, out=None): - # replacement for np.clip due to regression in - # performance since numpy 1.17 - # https://github.com/numpy/numpy/issues/14281 - - if vmin is None and vmax is None: - # let np.clip handle the error - return np.clip(arr, vmin, vmax, out=out) - - if vmin is None: - return np.core.umath.minimum(arr, vmax, out=out) - elif vmax is None: - return np.core.umath.maximum(arr, vmin, out=out) - elif sys.platform == 'win32': - # Windows umath.clip is slower than umath.maximum(umath.minimum) - if out is None: - out = np.empty_like(arr) - out = np.core.umath.minimum(arr, vmax, out=out) - return np.core.umath.maximum(out, vmin, out=out) - else: - return np.core.umath.clip(arr, vmin, vmax, out=out) - - -def rescaleData(data, scale, offset, dtype=None, clip=None): - """Return data rescaled and optionally cast to a new dtype. - - The scaling operation is:: - - data => (data-offset) * scale - """ - if dtype is None: - dtype = data.dtype - else: - dtype = np.dtype(dtype) - - if dtype.kind in 'ui': - lim = np.iinfo(dtype) - if clip is None: - # don't let rescale cause integer overflow - clip = lim.min, lim.max - clip = max(clip[0], lim.min), min(clip[1], lim.max) - - if np.can_cast(data, np.float32): - work_dtype = np.float32 - else: - work_dtype = np.float64 - d2 = data.astype(work_dtype, copy=True) - d2 -= offset - d2 *= scale - - # Clip before converting dtype to avoid overflow - if clip is not None: - clip_array(d2, clip[0], clip[1], out=d2) - - # don't copy if no change in dtype - data = d2.astype(dtype, copy=False) - return data - - -def applyLookupTable(data, lut): - """ - Uses values in *data* as indexes to select values from *lut*. - The returned data has shape data.shape + lut.shape[1:] - - Note: color gradient lookup tables can be generated using GradientWidget. - - Parameters - ---------- - data : ndarray - lut : ndarray - Either cupy or numpy arrays are accepted, though this function has only - consistently behaved correctly on windows with cuda toolkit version >= 11.1. - """ - if data.dtype.kind not in ('i', 'u'): - data = data.astype(int) - - cp = getCupy() - if cp and cp.get_array_module(data) == cp: - # cupy.take only supports "wrap" mode - return cp.take(lut, cp.clip(data, 0, lut.shape[0] - 1), axis=0) - else: - return np.take(lut, data, axis=0, mode='clip') - - -def makeRGBA(*args, **kwds): - """Equivalent to makeARGB(..., useRGBA=True)""" - kwds['useRGBA'] = True - return makeARGB(*args, **kwds) - - -def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False, output=None): - """ - Convert an array of values into an ARGB array suitable for building QImages, - OpenGL textures, etc. - - Returns the ARGB array (unsigned byte) and a boolean indicating whether - there is alpha channel data. This is a two stage process: - - 1) Rescale the data based on the values in the *levels* argument (min, max). - 2) Determine the final output by passing the rescaled values through a - lookup table. - - Both stages are optional. - - ============== ================================================================================== - **Arguments:** - data numpy array of int/float types. If - levels List [min, max]; optionally rescale data before converting through the - lookup table. The data is rescaled such that min->0 and max->*scale*:: - - rescaled = (clip(data, min, max) - min) * (*scale* / (max - min)) - - It is also possible to use a 2D (N,2) array of values for levels. In this case, - it is assumed that each pair of min,max values in the levels array should be - applied to a different subset of the input data (for example, the input data may - already have RGB values and the levels are used to independently scale each - channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. - scale The maximum value to which data will be rescaled before being passed through the - lookup table (or returned if there is no lookup table). By default this will - be set to the length of the lookup table, or 255 if no lookup table is provided. - lut Optional lookup table (array with dtype=ubyte). - Values in data will be converted to color by indexing directly from lut. - The output data shape will be input.shape + lut.shape[1:]. - Lookup tables can be built using ColorMap or GradientWidget. - useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). - The default is False, which returns in ARGB order for use with QImage - (Note that 'ARGB' is a term used by the Qt documentation; the *actual* order - is BGRA). - ============== ================================================================================== - """ - cp = getCupy() - xp = cp.get_array_module(data) if cp else np - profile = debug.Profiler() - if data.ndim not in (2, 3): - raise TypeError("data must be 2D or 3D") - if data.ndim == 3 and data.shape[2] > 4: - raise TypeError("data.shape[2] must be <= 4") - - if lut is not None and not isinstance(lut, xp.ndarray): - lut = xp.array(lut) - - if levels is None: - # automatically decide levels based on data dtype - if data.dtype.kind == 'u': - levels = xp.array([0, 2**(data.itemsize*8)-1]) - elif data.dtype.kind == 'i': - s = 2**(data.itemsize*8 - 1) - levels = xp.array([-s, s-1]) - elif data.dtype.kind == 'b': - levels = xp.array([0,1]) - else: - raise Exception('levels argument is required for float input types') - if not isinstance(levels, xp.ndarray): - levels = xp.array(levels) - levels = levels.astype(xp.float64) - if levels.ndim == 1: - if levels.shape[0] != 2: - raise Exception('levels argument must have length 2') - elif levels.ndim == 2: - if lut is not None and lut.ndim > 1: - raise Exception('Cannot make ARGB data when both levels and lut have ndim > 2') - if levels.shape != (data.shape[-1], 2): - raise Exception('levels must have shape (data.shape[-1], 2)') - else: - raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape)) - - profile('check inputs') - - # Decide on maximum scaled value - if scale is None: - if lut is not None: - scale = lut.shape[0] - else: - scale = 255. - - # Decide on the dtype we want after scaling - if lut is None: - dtype = xp.ubyte - else: - dtype = xp.min_scalar_type(lut.shape[0]-1) - - # awkward, but fastest numpy native nan evaluation - nanMask = None - if data.dtype.kind == 'f' and xp.isnan(data.min()): - nanMask = xp.isnan(data) - if data.ndim > 2: - nanMask = xp.any(nanMask, axis=-1) - # Apply levels if given - if levels is not None: - if isinstance(levels, xp.ndarray) and levels.ndim == 2: - # we are going to rescale each channel independently - if levels.shape[0] != data.shape[-1]: - raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") - newData = xp.empty(data.shape, dtype=int) - for i in range(data.shape[-1]): - minVal, maxVal = levels[i] - if minVal == maxVal: - maxVal = xp.nextafter(maxVal, 2*maxVal) - rng = maxVal-minVal - rng = 1 if rng == 0 else rng - newData[...,i] = rescaleData(data[...,i], scale / rng, minVal, dtype=dtype) - data = newData - else: - # Apply level scaling unless it would have no effect on the data - minVal, maxVal = levels - if minVal != 0 or maxVal != scale: - if minVal == maxVal: - maxVal = xp.nextafter(maxVal, 2*maxVal) - rng = maxVal-minVal - rng = 1 if rng == 0 else rng - data = rescaleData(data, scale/rng, minVal, dtype=dtype) - - profile('apply levels') - - # apply LUT if given - if lut is not None: - data = applyLookupTable(data, lut) - else: - if data.dtype != xp.ubyte: - data = xp.clip(data, 0, 255).astype(xp.ubyte) - - profile('apply lut') - - # this will be the final image array - if output is None: - imgData = xp.empty(data.shape[:2]+(4,), dtype=xp.ubyte) - else: - imgData = output - - profile('allocate') - - # decide channel order - if useRGBA: - order = [0,1,2,3] # array comes out RGBA - else: - order = [2,1,0,3] # for some reason, the colors line up as BGR in the final image. - - # copy data into image array - if data.ndim == 2: - # This is tempting: - # imgData[..., :3] = data[..., xp.newaxis] - # ..but it turns out this is faster: - for i in range(3): - imgData[..., i] = data - elif data.shape[2] == 1: - for i in range(3): - imgData[..., i] = data[..., 0] - else: - for i in range(0, data.shape[2]): - imgData[..., i] = data[..., order[i]] - - profile('reorder channels') - - # add opaque alpha channel if needed - if data.ndim == 2 or data.shape[2] == 3: - alpha = False - imgData[..., 3] = 255 - else: - alpha = True - - # apply nan mask through alpha channel - if nanMask is not None: - alpha = True - # Workaround for https://github.com/cupy/cupy/issues/4693 - if xp == cp: - imgData[nanMask, :, 3] = 0 - else: - imgData[nanMask, 3] = 0 - - profile('alpha channel') - return imgData, alpha - - -def makeQImage(imgData, alpha=None, copy=True, transpose=True): - """ - Turn an ARGB array into QImage. - By default, the data is copied; changes to the array will not - be reflected in the image. The image will be given a 'data' attribute - pointing to the array which shares its data to prevent python - freeing that memory while the image is in use. - - ============== =================================================================== - **Arguments:** - imgData Array of data to convert. Must have shape (height, width), - (height, width, 3), or (height, width, 4). If transpose is - True, then the first two axes are swapped. The array dtype - must be ubyte. For 2D arrays, the value is interpreted as - greyscale. For 3D arrays, the order of values in the 3rd - axis must be (b, g, r, a). - alpha If the input array is 3D and *alpha* is True, the QImage - returned will have format ARGB32. If False, - the format will be RGB32. By default, _alpha_ is True if - array.shape[2] == 4. - copy If True, the data is copied before converting to QImage. - If False, the new QImage points directly to the data in the array. - Note that the array must be contiguous for this to work - (see numpy.ascontiguousarray). - transpose If True (the default), the array x/y axes are transposed before - creating the image. Note that Qt expects the axes to be in - (height, width) order whereas pyqtgraph usually prefers the - opposite. - ============== =================================================================== - """ - ## create QImage from buffer - profile = debug.Profiler() - - copied = False - if imgData.ndim == 2: - imgFormat = QtGui.QImage.Format_Grayscale8 - elif imgData.ndim == 3: - # If we didn't explicitly specify alpha, check the array shape. - if alpha is None: - alpha = (imgData.shape[2] == 4) - - if imgData.shape[2] == 3: # need to make alpha channel (even if alpha==False; QImage requires 32 bpp) - if copy is True: - d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype) - d2[:,:,:3] = imgData - d2[:,:,3] = 255 - imgData = d2 - copied = True - else: - raise Exception('Array has only 3 channels; cannot make QImage without copying.') - - profile("add alpha channel") - - if alpha: - imgFormat = QtGui.QImage.Format_ARGB32 - else: - imgFormat = QtGui.QImage.Format_RGB32 - else: - raise TypeError("Image array must have ndim = 2 or 3.") - - if transpose: - imgData = imgData.transpose((1, 0, 2)) # QImage expects row-major order - - if not imgData.flags['C_CONTIGUOUS']: - if copy is False: - extra = ' (try setting transpose=False)' if transpose else '' - raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra) - imgData = np.ascontiguousarray(imgData) - copied = True - - profile("ascontiguousarray") - - if copy is True and copied is False: - imgData = imgData.copy() - - profile("copy") - - # C++ QImage has two kind of constructors - # - QImage(const uchar*, ...) - # - QImage(uchar*, ...) - # If the const constructor is used, subsequently calling any non-const method - # will trigger the COW mechanism, i.e. a copy is made under the hood. - - if QT_LIB.startswith('PyQt'): - if QtCore.PYQT_VERSION == 0x60000: - # PyQt5 -> const - # PyQt6 >= 6.0.1 -> const - # PyQt6 == 6.0.0 -> non-const - img_ptr = Qt.sip.voidptr(imgData) - else: - # PyQt5 -> non-const - # PyQt6 >= 6.0.1 -> non-const - img_ptr = int(Qt.sip.voidptr(imgData)) # or imgData.ctypes.data - else: - # bindings that support ndarray - # PyQt5 -> const - # PyQt6 >= 6.0.1 -> const - # PySide2 -> non-const - # PySide6 -> non-const - img_ptr = imgData - - img = QtGui.QImage(img_ptr, imgData.shape[1], imgData.shape[0], imgFormat) - - img.data = imgData - return img - -def imageToArray(img, copy=False, transpose=True): - """ - Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied. - By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if - the QImage is collected before the array, there may be trouble). - The array will have shape (width, height, (b,g,r,a)). - """ - fmt = img.format() - img_ptr = img.bits() - - if QT_LIB.startswith('PyQt'): - # sizeInBytes() was introduced in Qt 5.10 - # however PyQt5 5.12 will fail with: - # "TypeError: QImage.sizeInBytes() is a private method" - # note that sizeInBytes() works fine with: - # PyQt5 5.15, PySide2 5.12, PySide2 5.15 - try: - # 64-bits size - nbytes = img.sizeInBytes() - except (TypeError, AttributeError): - # 32-bits size - nbytes = img.byteCount() - img_ptr.setsize(nbytes) - - arr = np.frombuffer(img_ptr, dtype=np.ubyte) - arr = arr.reshape(img.height(), img.width(), 4) - if fmt == img.Format_RGB32: - arr[...,3] = 255 - - if copy: - arr = arr.copy() - - if transpose: - return arr.transpose((1,0,2)) - else: - return arr - -def colorToAlpha(data, color): - """ - Given an RGBA image in *data*, convert *color* to be transparent. - *data* must be an array (w, h, 3 or 4) of ubyte values and *color* must be - an array (3) of ubyte values. - This is particularly useful for use with images that have a black or white background. - - Algorithm is taken from Gimp's color-to-alpha function in plug-ins/common/colortoalpha.c - Credit: - /* - * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14 - * with algorithm by clahey - */ - - """ - data = data.astype(float) - if data.shape[-1] == 3: ## add alpha channel if needed - d2 = np.empty(data.shape[:2]+(4,), dtype=data.dtype) - d2[...,:3] = data - d2[...,3] = 255 - data = d2 - - color = color.astype(float) - alpha = np.zeros(data.shape[:2]+(3,), dtype=float) - output = data.copy() - - for i in [0,1,2]: - d = data[...,i] - c = color[i] - mask = d > c - alpha[...,i][mask] = (d[mask] - c) / (255. - c) - imask = d < c - alpha[...,i][imask] = (c - d[imask]) / c - - output[...,3] = alpha.max(axis=2) * 255. - - mask = output[...,3] >= 1.0 ## avoid zero division while processing alpha channel - correction = 255. / output[...,3][mask] ## increase value to compensate for decreased alpha - for i in [0,1,2]: - output[...,i][mask] = ((output[...,i][mask]-color[i]) * correction) + color[i] - output[...,3][mask] *= data[...,3][mask] / 255. ## combine computed and previous alpha values - - #raise Exception() - return np.clip(output, 0, 255).astype(np.ubyte) - -def gaussianFilter(data, sigma): - """ - Drop-in replacement for scipy.ndimage.gaussian_filter. - - (note: results are only approximately equal to the output of - gaussian_filter) - """ - cp = getCupy() - xp = cp.get_array_module(data) if cp else np - if xp.isscalar(sigma): - sigma = (sigma,) * data.ndim - - baseline = data.mean() - filtered = data - baseline - for ax in range(data.ndim): - s = sigma[ax] - if s == 0: - continue - - # generate 1D gaussian kernel - ksize = int(s * 6) - x = xp.arange(-ksize, ksize) - kernel = xp.exp(-x**2 / (2*s**2)) - kshape = [1,] * data.ndim - kshape[ax] = len(kernel) - kernel = kernel.reshape(kshape) - - # convolve as product of FFTs - shape = data.shape[ax] + ksize - scale = 1.0 / (abs(s) * (2*xp.pi)**0.5) - filtered = scale * xp.fft.irfft(xp.fft.rfft(filtered, shape, axis=ax) * - xp.fft.rfft(kernel, shape, axis=ax), - axis=ax) - - # clip off extra data - sl = [slice(None)] * data.ndim - sl[ax] = slice(filtered.shape[ax]-data.shape[ax],None,None) - filtered = filtered[tuple(sl)] - return filtered + baseline - - -def downsample(data, n, axis=0, xvals='subsample'): - """Downsample by averaging points together across axis. - If multiple axes are specified, runs once per axis. - If a metaArray is given, then the axis values can be either subsampled - or downsampled to match. - """ - ma = None - if (hasattr(data, 'implements') and data.implements('MetaArray')): - ma = data - data = data.view(np.ndarray) - - - if hasattr(axis, '__len__'): - if not hasattr(n, '__len__'): - n = [n]*len(axis) - for i in range(len(axis)): - data = downsample(data, n[i], axis[i]) - return data - - if n <= 1: - return data - nPts = int(data.shape[axis] / n) - s = list(data.shape) - s[axis] = nPts - s.insert(axis+1, n) - sl = [slice(None)] * data.ndim - sl[axis] = slice(0, nPts*n) - d1 = data[tuple(sl)] - #print d1.shape, s - d1.shape = tuple(s) - d2 = d1.mean(axis+1) - - if ma is None: - return d2 - else: - info = ma.infoCopy() - if 'values' in info[axis]: - if xvals == 'subsample': - info[axis]['values'] = info[axis]['values'][::n][:nPts] - elif xvals == 'downsample': - info[axis]['values'] = downsample(info[axis]['values'], n) - return MetaArray(d2, info=info) - - -def arrayToQPath(x, y, connect='all'): - """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. - The *connect* argument may be 'all', indicating that each point should be - connected to the next; 'pairs', indicating that each pair of points - should be connected, or an array of int32 values (0 or 1) indicating - connections. - """ - - ## Create all vertices in path. The method used below creates a binary format so that all - ## vertices can be read in at once. This binary format may change in future versions of Qt, - ## so the original (slower) method is left here for emergencies: - #path.moveTo(x[0], y[0]) - #if connect == 'all': - #for i in range(1, y.shape[0]): - #path.lineTo(x[i], y[i]) - #elif connect == 'pairs': - #for i in range(1, y.shape[0]): - #if i%2 == 0: - #path.lineTo(x[i], y[i]) - #else: - #path.moveTo(x[i], y[i]) - #elif isinstance(connect, np.ndarray): - #for i in range(1, y.shape[0]): - #if connect[i] == 1: - #path.lineTo(x[i], y[i]) - #else: - #path.moveTo(x[i], y[i]) - #else: - #raise Exception('connect argument must be "all", "pairs", or array') - - ## Speed this up using >> operator - ## Format is: - ## numVerts(i4) - ## 0(i4) x(f8) y(f8) <-- 0 means this vertex does not connect - ## 1(i4) x(f8) y(f8) <-- 1 means this vertex connects to the previous vertex - ## ... - ## cStart(i4) fillRule(i4) - ## - ## see: https://github.com/qt/qtbase/blob/dev/src/gui/painting/qpainterpath.cpp - - ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') - - path = QtGui.QPainterPath() - - n = x.shape[0] - - # create empty array, pad with extra space on either end - arr = np.empty(n+2, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')]) - - # write first two integers - byteview = arr.view(dtype=np.ubyte) - byteview[:16] = 0 - byteview.data[16:20] = struct.pack('>i', n) - - # Fill array with vertex values - arr[1:-1]['x'] = x - arr[1:-1]['y'] = y - - # inf/nans completely prevent the plot from being displayed starting on - # Qt version 5.12.3; these must now be manually cleaned out. - isfinite = None - qtver = [int(x) for x in QtVersion.split('.')] - if qtver >= [5, 12, 3]: - isfinite = np.isfinite(x) & np.isfinite(y) - if not np.all(isfinite): - # credit: Divakar https://stackoverflow.com/a/41191127/643629 - mask = ~isfinite - idx = np.arange(len(x)) - idx[mask] = -1 - np.maximum.accumulate(idx, out=idx) - first = np.searchsorted(idx, 0) - if first < len(x): - # Replace all non-finite entries from beginning of arr with the first finite one - idx[:first] = first - arr[1:-1] = arr[1:-1][idx] - - # decide which points are connected by lines - if eq(connect, 'all'): - arr[1:-1]['c'] = 1 - elif eq(connect, 'pairs'): - arr[1:-1]['c'][::2] = 0 - arr[1:-1]['c'][1::2] = 1 # connect every 2nd point to every 1st one - elif eq(connect, 'finite'): - # Let's call a point with either x or y being nan is an invalid point. - # A point will anyway not connect to an invalid point regardless of the - # 'c' value of the invalid point. Therefore, we should set 'c' to 0 for - # the next point of an invalid point. - if isfinite is None: - isfinite = np.isfinite(x) & np.isfinite(y) - arr[2:]['c'] = isfinite - elif isinstance(connect, np.ndarray): - arr[2:-1]['c'] = connect[:-1] - else: - raise Exception('connect argument must be "all", "pairs", "finite", or array') - - arr[1]['c'] = 0 # the first vertex has no previous vertex to connect - - byteview.data[-20:-16] = struct.pack('>i', 0) # cStart - byteview.data[-16:-12] = struct.pack('>i', 0) # fillRule (Qt.OddEvenFill) - - # create datastream object and stream into path - - ## Avoiding this method because QByteArray(str) leaks memory in PySide - #buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here - - path.strn = byteview.data[16:-12] # make sure data doesn't run away - try: - buf = QtCore.QByteArray.fromRawData(path.strn) - except TypeError: - buf = QtCore.QByteArray(bytes(path.strn)) - except AttributeError: - # PyQt6 raises AttributeError - buf = QtCore.QByteArray(path.strn, path.strn.nbytes) - - ds = QtCore.QDataStream(buf) - ds >> path - - return path - -#def isosurface(data, level): - #""" - #Generate isosurface from volumetric data using marching tetrahedra algorithm. - #See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/) - - #*data* 3D numpy array of scalar values - #*level* The level at which to generate an isosurface - #""" - - #facets = [] - - ### mark everything below the isosurface level - #mask = data < level - - #### make eight sub-fields - #fields = np.empty((2,2,2), dtype=object) - #slices = [slice(0,-1), slice(1,None)] - #for i in [0,1]: - #for j in [0,1]: - #for k in [0,1]: - #fields[i,j,k] = mask[slices[i], slices[j], slices[k]] - - - - ### split each cell into 6 tetrahedra - ### these all have the same 'orienation'; points 1,2,3 circle - ### clockwise around point 0 - #tetrahedra = [ - #[(0,1,0), (1,1,1), (0,1,1), (1,0,1)], - #[(0,1,0), (0,1,1), (0,0,1), (1,0,1)], - #[(0,1,0), (0,0,1), (0,0,0), (1,0,1)], - #[(0,1,0), (0,0,0), (1,0,0), (1,0,1)], - #[(0,1,0), (1,0,0), (1,1,0), (1,0,1)], - #[(0,1,0), (1,1,0), (1,1,1), (1,0,1)] - #] - - ### each tetrahedron will be assigned an index - ### which determines how to generate its facets. - ### this structure is: - ### facets[index][facet1, facet2, ...] - ### where each facet is triangular and its points are each - ### interpolated between two points on the tetrahedron - ### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)] - ### facet points always circle clockwise if you are looking - ### at them from below the isosurface. - #indexFacets = [ - #[], ## all above - #[[(0,1), (0,2), (0,3)]], # 0 below - #[[(1,0), (1,3), (1,2)]], # 1 below - #[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below - #[[(2,0), (2,1), (2,3)]], # 2 below - #[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below - #[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below - #[[(3,0), (3,1), (3,2)]], # 3 above - #[[(3,0), (3,2), (3,1)]], # 3 below - #[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below - #[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below - #[[(2,0), (2,3), (2,1)]], # 0,1,3 below - #[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below - #[[(1,0), (1,2), (1,3)]], # 0,2,3 below - #[[(0,1), (0,3), (0,2)]], # 1,2,3 below - #[] ## all below - #] - - #for tet in tetrahedra: - - ### get the 4 fields for this tetrahedron - #tetFields = [fields[c] for c in tet] - - ### generate an index for each grid cell - #index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8 - - ### add facets - #for i in xrange(index.shape[0]): # data x-axis - #for j in xrange(index.shape[1]): # data y-axis - #for k in xrange(index.shape[2]): # data z-axis - #for f in indexFacets[index[i,j,k]]: # faces to generate for this tet - #pts = [] - #for l in [0,1,2]: # points in this face - #p1 = tet[f[l][0]] # tet corner 1 - #p2 = tet[f[l][1]] # tet corner 2 - #pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners - #facets.append(pts) - - #return facets - - -def isocurve(data, level, connected=False, extendToEdge=False, path=False): - """ - Generate isocurve from 2D data using marching squares algorithm. - - ============== ========================================================= - **Arguments:** - data 2D numpy array of scalar values - level The level at which to generate an isosurface - connected If False, return a single long list of point pairs - If True, return multiple long lists of connected point - locations. (This is slower but better for drawing - continuous lines) - extendToEdge If True, extend the curves to reach the exact edges of - the data. - path if True, return a QPainterPath rather than a list of - vertex coordinates. This forces connected=True. - ============== ========================================================= - - This function is SLOW; plenty of room for optimization here. - """ - - if path is True: - connected = True - - if extendToEdge: - d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype) - d2[1:-1, 1:-1] = data - d2[0, 1:-1] = data[0] - d2[-1, 1:-1] = data[-1] - d2[1:-1, 0] = data[:, 0] - d2[1:-1, -1] = data[:, -1] - d2[0,0] = d2[0,1] - d2[0,-1] = d2[1,-1] - d2[-1,0] = d2[-1,1] - d2[-1,-1] = d2[-1,-2] - data = d2 - - sideTable = [ - [], - [0,1], - [1,2], - [0,2], - [0,3], - [1,3], - [0,1,2,3], - [2,3], - [2,3], - [0,1,2,3], - [1,3], - [0,3], - [0,2], - [1,2], - [0,1], - [] - ] - - edgeKey=[ - [(0,1), (0,0)], - [(0,0), (1,0)], - [(1,0), (1,1)], - [(1,1), (0,1)] - ] - - - lines = [] - - ## mark everything below the isosurface level - mask = data < level - - ### make four sub-fields and compute indexes for grid cells - index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) - fields = np.empty((2,2), dtype=object) - slices = [slice(0,-1), slice(1,None)] - for i in [0,1]: - for j in [0,1]: - fields[i,j] = mask[slices[i], slices[j]] - #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme - vertIndex = i+2*j - #print i,j,k," : ", fields[i,j,k], 2**vertIndex - np.add(index, fields[i,j] * 2**vertIndex, out=index, casting='unsafe') - #print index - #print index - - ## add lines - for i in range(index.shape[0]): # data x-axis - for j in range(index.shape[1]): # data y-axis - sides = sideTable[index[i,j]] - for l in range(0, len(sides), 2): ## faces for this grid cell - edges = sides[l:l+2] - pts = [] - for m in [0,1]: # points in this face - p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge - p2 = edgeKey[edges[m]][1] - v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 - v2 = data[i+p2[0], j+p2[1]] - f = (level-v1) / (v2-v1) - fi = 1.0 - f - p = ( ## interpolate between corners - p1[0]*fi + p2[0]*f + i + 0.5, - p1[1]*fi + p2[1]*f + j + 0.5 - ) - if extendToEdge: - ## check bounds - p = ( - min(data.shape[0]-2, max(0, p[0]-1)), - min(data.shape[1]-2, max(0, p[1]-1)), - ) - if connected: - gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2 - pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments) - else: - pts.append(p) - - lines.append(pts) - - if not connected: - return lines - - ## turn disjoint list of segments into continuous lines - - #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]] - #lines = [[(float(a), a), (float(b), b)] for a,b in lines] - points = {} ## maps each point to its connections - for a,b in lines: - if a[1] not in points: - points[a[1]] = [] - points[a[1]].append([a,b]) - if b[1] not in points: - points[b[1]] = [] - points[b[1]].append([b,a]) - - ## rearrange into chains - for k in list(points.keys()): - try: - chains = points[k] - except KeyError: ## already used this point elsewhere - continue - #print "===========", k - for chain in chains: - #print " chain:", chain - x = None - while True: - if x == chain[-1][1]: - break ## nothing left to do on this chain - - x = chain[-1][1] - if x == k: - break ## chain has looped; we're done and can ignore the opposite chain - y = chain[-2][1] - connects = points[x] - for conn in connects[:]: - if conn[1][1] != y: - #print " ext:", conn - chain.extend(conn[1:]) - #print " del:", x - del points[x] - if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction - chains.pop() - break - - - ## extract point locations - lines = [] - for chain in points.values(): - if len(chain) == 2: - chain = chain[1][1:][::-1] + chain[0] # join together ends of chain - else: - chain = chain[0] - lines.append([p[0] for p in chain]) - - if not path: - return lines ## a list of pairs of points - - path = QtGui.QPainterPath() - for line in lines: - path.moveTo(*line[0]) - for p in line[1:]: - path.lineTo(*p) - - return path - - -def traceImage(image, values, smooth=0.5): - """ - Convert an image to a set of QPainterPath curves. - One curve will be generated for each item in *values*; each curve outlines the area - of the image that is closer to its value than to any others. - - If image is RGB or RGBA, then the shape of values should be (nvals, 3/4) - The parameter *smooth* is expressed in pixels. - """ - try: - import scipy.ndimage as ndi - except ImportError: - raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.") - - if values.ndim == 2: - values = values.T - values = values[np.newaxis, np.newaxis, ...].astype(float) - image = image[..., np.newaxis].astype(float) - diff = np.abs(image-values) - if values.ndim == 4: - diff = diff.sum(axis=2) - - labels = np.argmin(diff, axis=2) - - paths = [] - for i in range(diff.shape[-1]): - d = (labels==i).astype(float) - d = gaussianFilter(d, (smooth, smooth)) - lines = isocurve(d, 0.5, connected=True, extendToEdge=True) - path = QtGui.QPainterPath() - for line in lines: - path.moveTo(*line[0]) - for p in line[1:]: - path.lineTo(*p) - - paths.append(path) - return paths - - - -IsosurfaceDataCache = None -def isosurface(data, level): - """ - Generate isosurface from volumetric data using marching cubes algorithm. - See Paul Bourke, "Polygonising a Scalar Field" - (http://paulbourke.net/geometry/polygonise/) - - *data* 3D numpy array of scalar values. Must be contiguous. - *level* The level at which to generate an isosurface - - Returns an array of vertex coordinates (Nv, 3) and an array of - per-face vertex indexes (Nf, 3) - """ - ## For improvement, see: - ## - ## Efficient implementation of Marching Cubes' cases with topological guarantees. - ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares. - ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003) - - ## Precompute lookup tables on the first run - global IsosurfaceDataCache - if IsosurfaceDataCache is None: - ## map from grid cell index to edge index. - ## grid cell index tells us which corners are below the isosurface, - ## edge index tells us which edges are cut by the isosurface. - ## (Data stolen from Bourk; see above.) - edgeTable = np.array([ - 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, - 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, - 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, - 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, - 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, - 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, - 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, - 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, - 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, - 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, - 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, - 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, - 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, - 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, - 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , - 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, - 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, - 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, - 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, - 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, - 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, - 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, - 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, - 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, - 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, - 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, - 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, - 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, - 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, - 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, - 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, - 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 - ], dtype=np.uint16) - - ## Table of triangles to use for filling each grid cell. - ## Each set of three integers tells us which three edges to - ## draw a triangle between. - ## (Data stolen from Bourk; see above.) - triTable = [ - [], - [0, 8, 3], - [0, 1, 9], - [1, 8, 3, 9, 8, 1], - [1, 2, 10], - [0, 8, 3, 1, 2, 10], - [9, 2, 10, 0, 2, 9], - [2, 8, 3, 2, 10, 8, 10, 9, 8], - [3, 11, 2], - [0, 11, 2, 8, 11, 0], - [1, 9, 0, 2, 3, 11], - [1, 11, 2, 1, 9, 11, 9, 8, 11], - [3, 10, 1, 11, 10, 3], - [0, 10, 1, 0, 8, 10, 8, 11, 10], - [3, 9, 0, 3, 11, 9, 11, 10, 9], - [9, 8, 10, 10, 8, 11], - [4, 7, 8], - [4, 3, 0, 7, 3, 4], - [0, 1, 9, 8, 4, 7], - [4, 1, 9, 4, 7, 1, 7, 3, 1], - [1, 2, 10, 8, 4, 7], - [3, 4, 7, 3, 0, 4, 1, 2, 10], - [9, 2, 10, 9, 0, 2, 8, 4, 7], - [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], - [8, 4, 7, 3, 11, 2], - [11, 4, 7, 11, 2, 4, 2, 0, 4], - [9, 0, 1, 8, 4, 7, 2, 3, 11], - [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], - [3, 10, 1, 3, 11, 10, 7, 8, 4], - [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], - [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], - [4, 7, 11, 4, 11, 9, 9, 11, 10], - [9, 5, 4], - [9, 5, 4, 0, 8, 3], - [0, 5, 4, 1, 5, 0], - [8, 5, 4, 8, 3, 5, 3, 1, 5], - [1, 2, 10, 9, 5, 4], - [3, 0, 8, 1, 2, 10, 4, 9, 5], - [5, 2, 10, 5, 4, 2, 4, 0, 2], - [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], - [9, 5, 4, 2, 3, 11], - [0, 11, 2, 0, 8, 11, 4, 9, 5], - [0, 5, 4, 0, 1, 5, 2, 3, 11], - [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], - [10, 3, 11, 10, 1, 3, 9, 5, 4], - [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], - [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], - [5, 4, 8, 5, 8, 10, 10, 8, 11], - [9, 7, 8, 5, 7, 9], - [9, 3, 0, 9, 5, 3, 5, 7, 3], - [0, 7, 8, 0, 1, 7, 1, 5, 7], - [1, 5, 3, 3, 5, 7], - [9, 7, 8, 9, 5, 7, 10, 1, 2], - [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], - [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], - [2, 10, 5, 2, 5, 3, 3, 5, 7], - [7, 9, 5, 7, 8, 9, 3, 11, 2], - [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], - [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], - [11, 2, 1, 11, 1, 7, 7, 1, 5], - [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], - [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], - [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], - [11, 10, 5, 7, 11, 5], - [10, 6, 5], - [0, 8, 3, 5, 10, 6], - [9, 0, 1, 5, 10, 6], - [1, 8, 3, 1, 9, 8, 5, 10, 6], - [1, 6, 5, 2, 6, 1], - [1, 6, 5, 1, 2, 6, 3, 0, 8], - [9, 6, 5, 9, 0, 6, 0, 2, 6], - [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], - [2, 3, 11, 10, 6, 5], - [11, 0, 8, 11, 2, 0, 10, 6, 5], - [0, 1, 9, 2, 3, 11, 5, 10, 6], - [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], - [6, 3, 11, 6, 5, 3, 5, 1, 3], - [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], - [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], - [6, 5, 9, 6, 9, 11, 11, 9, 8], - [5, 10, 6, 4, 7, 8], - [4, 3, 0, 4, 7, 3, 6, 5, 10], - [1, 9, 0, 5, 10, 6, 8, 4, 7], - [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], - [6, 1, 2, 6, 5, 1, 4, 7, 8], - [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], - [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], - [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], - [3, 11, 2, 7, 8, 4, 10, 6, 5], - [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], - [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], - [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], - [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], - [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], - [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], - [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], - [10, 4, 9, 6, 4, 10], - [4, 10, 6, 4, 9, 10, 0, 8, 3], - [10, 0, 1, 10, 6, 0, 6, 4, 0], - [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], - [1, 4, 9, 1, 2, 4, 2, 6, 4], - [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], - [0, 2, 4, 4, 2, 6], - [8, 3, 2, 8, 2, 4, 4, 2, 6], - [10, 4, 9, 10, 6, 4, 11, 2, 3], - [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], - [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], - [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], - [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], - [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], - [3, 11, 6, 3, 6, 0, 0, 6, 4], - [6, 4, 8, 11, 6, 8], - [7, 10, 6, 7, 8, 10, 8, 9, 10], - [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], - [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], - [10, 6, 7, 10, 7, 1, 1, 7, 3], - [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], - [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], - [7, 8, 0, 7, 0, 6, 6, 0, 2], - [7, 3, 2, 6, 7, 2], - [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], - [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], - [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], - [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], - [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], - [0, 9, 1, 11, 6, 7], - [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], - [7, 11, 6], - [7, 6, 11], - [3, 0, 8, 11, 7, 6], - [0, 1, 9, 11, 7, 6], - [8, 1, 9, 8, 3, 1, 11, 7, 6], - [10, 1, 2, 6, 11, 7], - [1, 2, 10, 3, 0, 8, 6, 11, 7], - [2, 9, 0, 2, 10, 9, 6, 11, 7], - [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], - [7, 2, 3, 6, 2, 7], - [7, 0, 8, 7, 6, 0, 6, 2, 0], - [2, 7, 6, 2, 3, 7, 0, 1, 9], - [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], - [10, 7, 6, 10, 1, 7, 1, 3, 7], - [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], - [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], - [7, 6, 10, 7, 10, 8, 8, 10, 9], - [6, 8, 4, 11, 8, 6], - [3, 6, 11, 3, 0, 6, 0, 4, 6], - [8, 6, 11, 8, 4, 6, 9, 0, 1], - [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], - [6, 8, 4, 6, 11, 8, 2, 10, 1], - [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], - [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], - [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], - [8, 2, 3, 8, 4, 2, 4, 6, 2], - [0, 4, 2, 4, 6, 2], - [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], - [1, 9, 4, 1, 4, 2, 2, 4, 6], - [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], - [10, 1, 0, 10, 0, 6, 6, 0, 4], - [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], - [10, 9, 4, 6, 10, 4], - [4, 9, 5, 7, 6, 11], - [0, 8, 3, 4, 9, 5, 11, 7, 6], - [5, 0, 1, 5, 4, 0, 7, 6, 11], - [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], - [9, 5, 4, 10, 1, 2, 7, 6, 11], - [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], - [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], - [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], - [7, 2, 3, 7, 6, 2, 5, 4, 9], - [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], - [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], - [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], - [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], - [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], - [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], - [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], - [6, 9, 5, 6, 11, 9, 11, 8, 9], - [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], - [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], - [6, 11, 3, 6, 3, 5, 5, 3, 1], - [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], - [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], - [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], - [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], - [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], - [9, 5, 6, 9, 6, 0, 0, 6, 2], - [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], - [1, 5, 6, 2, 1, 6], - [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], - [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], - [0, 3, 8, 5, 6, 10], - [10, 5, 6], - [11, 5, 10, 7, 5, 11], - [11, 5, 10, 11, 7, 5, 8, 3, 0], - [5, 11, 7, 5, 10, 11, 1, 9, 0], - [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], - [11, 1, 2, 11, 7, 1, 7, 5, 1], - [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], - [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], - [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], - [2, 5, 10, 2, 3, 5, 3, 7, 5], - [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], - [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], - [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], - [1, 3, 5, 3, 7, 5], - [0, 8, 7, 0, 7, 1, 1, 7, 5], - [9, 0, 3, 9, 3, 5, 5, 3, 7], - [9, 8, 7, 5, 9, 7], - [5, 8, 4, 5, 10, 8, 10, 11, 8], - [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], - [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], - [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], - [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], - [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], - [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], - [9, 4, 5, 2, 11, 3], - [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], - [5, 10, 2, 5, 2, 4, 4, 2, 0], - [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], - [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], - [8, 4, 5, 8, 5, 3, 3, 5, 1], - [0, 4, 5, 1, 0, 5], - [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], - [9, 4, 5], - [4, 11, 7, 4, 9, 11, 9, 10, 11], - [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], - [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], - [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], - [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], - [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], - [11, 7, 4, 11, 4, 2, 2, 4, 0], - [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], - [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], - [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], - [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], - [1, 10, 2, 8, 7, 4], - [4, 9, 1, 4, 1, 7, 7, 1, 3], - [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], - [4, 0, 3, 7, 4, 3], - [4, 8, 7], - [9, 10, 8, 10, 11, 8], - [3, 0, 9, 3, 9, 11, 11, 9, 10], - [0, 1, 10, 0, 10, 8, 8, 10, 11], - [3, 1, 10, 11, 3, 10], - [1, 2, 11, 1, 11, 9, 9, 11, 8], - [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], - [0, 2, 11, 8, 0, 11], - [3, 2, 11], - [2, 3, 8, 2, 8, 10, 10, 8, 9], - [9, 10, 2, 0, 9, 2], - [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], - [1, 10, 2], - [1, 3, 8, 9, 1, 8], - [0, 9, 1], - [0, 3, 8], - [] - ] - edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2) - [0, 0, 0, 0], - [1, 0, 0, 1], - [0, 1, 0, 0], - [0, 0, 0, 1], - [0, 0, 1, 0], - [1, 0, 1, 1], - [0, 1, 1, 0], - [0, 0, 1, 1], - [0, 0, 0, 2], - [1, 0, 0, 2], - [1, 1, 0, 2], - [0, 1, 0, 2], - #[9, 9, 9, 9] ## fake - ], dtype=np.uint16) # don't use ubyte here! This value gets added to cell index later; will need the extra precision. - nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte) - faceShiftTables = [None] - for i in range(1,6): - ## compute lookup table of index: vertexes mapping - faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte) - faceTableInds = np.argwhere(nTableFaces == i) - faceTableI[faceTableInds[:,0]] = np.array([triTable[j[0]] for j in faceTableInds]) - faceTableI = faceTableI.reshape((len(triTable), i, 3)) - faceShiftTables.append(edgeShifts[faceTableI]) - - ## Let's try something different: - #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup) - #for i,f in enumerate(triTable): - #f = np.array(f + [12] * (15-len(f))).reshape(5,3) - #faceTable[i] = edgeShifts[f] - - - IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces) - else: - faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache - - # We use strides below, which means we need contiguous array input. - # Ideally we can fix this just by removing the dependency on strides. - if not data.flags['C_CONTIGUOUS']: - raise TypeError("isosurface input data must be c-contiguous.") - - ## mark everything below the isosurface level - mask = data < level - - ### make eight sub-fields and compute indexes for grid cells - index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) - fields = np.empty((2,2,2), dtype=object) - slices = [slice(0,-1), slice(1,None)] - for i in [0,1]: - for j in [0,1]: - for k in [0,1]: - fields[i,j,k] = mask[slices[i], slices[j], slices[k]] - vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme - np.add(index, fields[i,j,k] * 2**vertIndex, out=index, casting='unsafe') - - ### Generate table of edges that have been cut - cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) - edges = edgeTable[index] - for i, shift in enumerate(edgeShifts[:12]): - slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)] - cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i - - ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions - m = cutEdges > 0 - vertexInds = np.argwhere(m) ## argwhere is slow! - vertexes = vertexInds[:,:3].astype(np.float32) - dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2]) - - ## re-use the cutEdges array as a lookup table for vertex IDs - cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0]) - - for i in [0,1,2]: - vim = vertexInds[:,3] == i - vi = vertexInds[vim, :3] - viFlat = (vi * (np.array(data.strides[:3]) // data.itemsize)[np.newaxis,:]).sum(axis=1) - v1 = dataFlat[viFlat] - v2 = dataFlat[viFlat + data.strides[i]//data.itemsize] - vertexes[vim,i] += (level-v1) / (v2-v1) - - ### compute the set of vertex indexes for each face. - - ## This works, but runs a bit slower. - #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face - #cellInds = index[cells[:,0], cells[:,1], cells[:,2]] - #verts = faceTable[cellInds] - #mask = verts[...,0,0] != 9 - #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges - #verts = verts[mask] - #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. - - - ## To allow this to be vectorized efficiently, we count the number of faces in each - ## grid cell and handle each group of cells with the same number together. - ## determine how many faces to assign to each grid cell - nFaces = nTableFaces[index] - totFaces = nFaces.sum() - faces = np.empty((totFaces, 3), dtype=np.uint32) - ptr = 0 - #import debug - #p = debug.Profiler() - - ## this helps speed up an indexing operation later on - cs = np.array(cutEdges.strides)//cutEdges.itemsize - cutEdges = cutEdges.flatten() - - ## this, strangely, does not seem to help. - #ins = np.array(index.strides)/index.itemsize - #index = index.flatten() - - for i in range(1,6): - ### expensive: - #profiler() - cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) - #profiler() - if cells.shape[0] == 0: - continue - cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round - #profiler() - - ### expensive: - verts = faceShiftTables[i][cellInds] - #profiler() - np.add(verts[...,:3], cells[:,np.newaxis,np.newaxis,:], out=verts[...,:3], casting='unsafe') ## we now have indexes into cutEdges - verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) - #profiler() - - ### expensive: - verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) - vertInds = cutEdges[verts] - #profiler() - nv = vertInds.shape[0] - #profiler() - faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) - #profiler() - ptr += nv - - return vertexes, faces - - -def _pinv_fallback(tr): - arr = np.array([tr.m11(), tr.m12(), tr.m13(), - tr.m21(), tr.m22(), tr.m23(), - tr.m31(), tr.m32(), tr.m33()]) - arr.shape = (3, 3) - pinv = np.linalg.pinv(arr) - return QtGui.QTransform(*pinv.ravel().tolist()) - - -def invertQTransform(tr): - """Return a QTransform that is the inverse of *tr*. - A pseudo-inverse is returned if tr is not invertible. - - Note that this function is preferred over QTransform.inverted() due to - bugs in that method. (specifically, Qt has floating-point precision issues - when determining whether a matrix is invertible) - """ - try: - det = tr.determinant() - detr = 1.0 / det # let singular matrices raise ZeroDivisionError - inv = tr.adjoint() - inv *= detr - return inv - except ZeroDivisionError: - return _pinv_fallback(tr) - - -def pseudoScatter(data, spacing=None, shuffle=True, bidir=False, method='exact'): - """Return an array of position values needed to make beeswarm or column scatter plots. - - Used for examining the distribution of values in an array. - - Given an array of x-values, construct an array of y-values such that an x,y scatter-plot - will not have overlapping points (it will look similar to a histogram). - """ - if method == 'exact': - return _pseudoScatterExact(data, spacing=spacing, shuffle=shuffle, bidir=bidir) - elif method == 'histogram': - return _pseudoScatterHistogram(data, spacing=spacing, shuffle=shuffle, bidir=bidir) - - -def _pseudoScatterHistogram(data, spacing=None, shuffle=True, bidir=False): - """Works by binning points into a histogram and spreading them out to fill the bin. - - Faster method, but can produce blocky results. - """ - inds = np.arange(len(data)) - if shuffle: - np.random.shuffle(inds) - - data = data[inds] - - if spacing is None: - spacing = 2.*np.std(data)/len(data)**0.5 - - yvals = np.empty(len(data)) - - dmin = data.min() - dmax = data.max() - nbins = int((dmax-dmin) / spacing) + 1 - bins = np.linspace(dmin, dmax, nbins) - dx = bins[1] - bins[0] - dbins = ((data - bins[0]) / dx).astype(int) - binCounts = {} - - for i,j in enumerate(dbins): - c = binCounts.get(j, -1) + 1 - binCounts[j] = c - yvals[i] = c - - if bidir is True: - for i in range(nbins): - yvals[dbins==i] -= binCounts.get(i, 0) * 0.5 - - return yvals[np.argsort(inds)] ## un-shuffle values before returning - - -def _pseudoScatterExact(data, spacing=None, shuffle=True, bidir=False): - """Works by stacking points up one at a time, searching for the lowest position available at each point. - - This method produces nice, smooth results but can be prohibitively slow for large datasets. - """ - inds = np.arange(len(data)) - if shuffle: - np.random.shuffle(inds) - - data = data[inds] - - if spacing is None: - spacing = 2.*np.std(data)/len(data)**0.5 - s2 = spacing**2 - - yvals = np.empty(len(data)) - if len(data) == 0: - return yvals - yvals[0] = 0 - for i in range(1,len(data)): - x = data[i] # current x value to be placed - x0 = data[:i] # all x values already placed - y0 = yvals[:i] # all y values already placed - y = 0 - - dx = (x0-x)**2 # x-distance to each previous point - xmask = dx < s2 # exclude anything too far away - - if xmask.sum() > 0: - if bidir: - dirs = [-1, 1] - else: - dirs = [1] - yopts = [] - for direction in dirs: - y = 0 - dx2 = dx[xmask] - dy = (s2 - dx2)**0.5 - limits = np.empty((2,len(dy))) # ranges of y-values to exclude - limits[0] = y0[xmask] - dy - limits[1] = y0[xmask] + dy - while True: - # ignore anything below this y-value - if direction > 0: - mask = limits[1] >= y - else: - mask = limits[0] <= y - - limits2 = limits[:,mask] - - # are we inside an excluded region? - mask = (limits2[0] < y) & (limits2[1] > y) - if mask.sum() == 0: - break - - if direction > 0: - y = limits2[:,mask].max() - else: - y = limits2[:,mask].min() - yopts.append(y) - if bidir: - y = yopts[0] if -yopts[0] < yopts[1] else yopts[1] - else: - y = yopts[0] - yvals[i] = y - - return yvals[np.argsort(inds)] ## un-shuffle values before returning - - - -def toposort(deps, nodes=None, seen=None, stack=None, depth=0): - """Topological sort. Arguments are: - deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" - nodes optional, specifies list of starting nodes (these should be the nodes - which are not depended on by any other nodes). Other candidate starting - nodes will be ignored. - - Example:: - - # Sort the following graph: - # - # B ──┬─────> C <── D - # │ │ - # E <─┴─> A <─┘ - # - deps = {'a': ['b', 'c'], 'c': ['b', 'd'], 'e': ['b']} - toposort(deps) - => ['b', 'd', 'c', 'a', 'e'] - """ - # fill in empty dep lists - deps = deps.copy() - for k,v in list(deps.items()): - for k in v: - if k not in deps: - deps[k] = [] - - if nodes is None: - ## run through deps to find nodes that are not depended upon - rem = set() - for dep in deps.values(): - rem |= set(dep) - nodes = set(deps.keys()) - rem - if seen is None: - seen = set() - stack = [] - sorted = [] - for n in nodes: - if n in stack: - raise Exception("Cyclic dependency detected", stack + [n]) - if n in seen: - continue - seen.add(n) - sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) - sorted.append(n) - return sorted - - -def disconnect(signal, slot): - """Disconnect a Qt signal from a slot. - - This method augments Qt's Signal.disconnect(): - - * Return bool indicating whether disconnection was successful, rather than - raising an exception - * Attempt to disconnect prior versions of the slot when using pg.reload - """ - while True: - try: - signal.disconnect(slot) - return True - except (TypeError, RuntimeError): - slot = reload.getPreviousVersion(slot) - if slot is None: - return False - - -class SignalBlock(object): - """Class used to temporarily block a Qt signal connection:: - - with SignalBlock(signal, slot): - # do something that emits a signal; it will - # not be delivered to slot - """ - def __init__(self, signal, slot): - self.signal = signal - self.slot = slot - - def __enter__(self): - self.reconnect = disconnect(self.signal, self.slot) - return self - - def __exit__(self, *args): - if self.reconnect: - self.signal.connect(self.slot) diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py deleted file mode 100644 index 77b6c44..0000000 --- a/pyqtgraph/graphicsItems/ArrowItem.py +++ /dev/null @@ -1,145 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import functions as fn -import numpy as np -__all__ = ['ArrowItem'] - -class ArrowItem(QtGui.QGraphicsPathItem): - """ - For displaying scale-invariant arrows. - For arrows pointing to a location on a curve, see CurveArrow - - """ - - - def __init__(self, parent=None, **opts): - """ - Arrows can be initialized with any keyword arguments accepted by - the setStyle() method. - """ - self.opts = {} - QtGui.QGraphicsPathItem.__init__(self, parent) - - if 'size' in opts: - opts['headLen'] = opts['size'] - if 'width' in opts: - opts['headWidth'] = opts['width'] - pos = opts.pop('pos', (0, 0)) - - defaultOpts = { - 'pxMode': True, - 'angle': -150, ## If the angle is 0, the arrow points left - 'headLen': 20, - 'headWidth': None, - 'tipAngle': 25, - 'baseAngle': 0, - 'tailLen': None, - 'tailWidth': 3, - 'pen': (200,200,200), - 'brush': (50,50,200), - } - defaultOpts.update(opts) - - self.setStyle(**defaultOpts) - - # for backward compatibility - self.setPos(*pos) - - def setStyle(self, **opts): - """ - Changes the appearance of the arrow. - All arguments are optional: - - ====================== ================================================= - **Keyword Arguments:** - angle Orientation of the arrow in degrees. Default is - 0; arrow pointing to the left. - headLen Length of the arrow head, from tip to base. - default=20 - headWidth Width of the arrow head at its base. If - headWidth is specified, it overrides tipAngle. - tipAngle Angle of the tip of the arrow in degrees. Smaller - values make a 'sharper' arrow. default=25 - baseAngle Angle of the base of the arrow head. Default is - 0, which means that the base of the arrow head - is perpendicular to the arrow tail. - tailLen Length of the arrow tail, measured from the base - of the arrow head to the end of the tail. If - this value is None, no tail will be drawn. - default=None - tailWidth Width of the tail. default=3 - pen The pen used to draw the outline of the arrow. - brush The brush used to fill the arrow. - pxMode If True, then the arrow is drawn as a fixed size - regardless of the scale of its parents (including - the ViewBox zoom level). - ====================== ================================================= - """ - arrowOpts = ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth', 'headWidth'] - allowedOpts = ['angle', 'pen', 'brush', 'pxMode'] + arrowOpts - needUpdate = False - for k,v in opts.items(): - if k not in allowedOpts: - raise KeyError('Invalid arrow style option "%s"' % k) - if self.opts.get(k) != v: - needUpdate = True - self.opts[k] = v - - if not needUpdate: - return - - opt = dict([(k,self.opts[k]) for k in arrowOpts if k in self.opts]) - tr = QtGui.QTransform() - tr.rotate(self.opts['angle']) - self.path = tr.map(fn.makeArrowPath(**opt)) - - self.setPath(self.path) - - self.setPen(fn.mkPen(self.opts['pen'])) - self.setBrush(fn.mkBrush(self.opts['brush'])) - - if self.opts['pxMode']: - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - else: - self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - super().paint(p, *args) - - #p.setPen(fn.mkPen('r')) - #p.setBrush(fn.mkBrush(None)) - #p.drawRect(self.boundingRect()) - - def shape(self): - #if not self.opts['pxMode']: - #return QtGui.QGraphicsPathItem.shape(self) - return self.path - - ## dataBounds and pixelPadding methods are provided to ensure ViewBox can - ## properly auto-range - def dataBounds(self, ax, frac, orthoRange=None): - pw = 0 - pen = self.pen() - if not pen.isCosmetic(): - pw = pen.width() * 0.7072 - if self.opts['pxMode']: - return [0,0] - else: - br = self.boundingRect() - if ax == 0: - return [br.left()-pw, br.right()+pw] - else: - return [br.top()-pw, br.bottom()+pw] - - def pixelPadding(self): - pad = 0 - if self.opts['pxMode']: - br = self.boundingRect() - pad += (br.width()**2 + br.height()**2) ** 0.5 - pen = self.pen() - if pen.isCosmetic(): - pad += max(1, pen.width()) * 0.7072 - return pad - - - diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py deleted file mode 100644 index 37490d9..0000000 --- a/pyqtgraph/graphicsItems/AxisItem.py +++ /dev/null @@ -1,1211 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore, QT_LIB -from ..python2_3 import asUnicode -import numpy as np -from ..Point import Point -from .. import debug as debug -import sys -import weakref -from .. import functions as fn -from .. import getConfigOption -from .GraphicsWidget import GraphicsWidget -import warnings - -__all__ = ['AxisItem'] -class AxisItem(GraphicsWidget): - """ - GraphicsItem showing a single plot axis with ticks, values, and label. - Can be configured to fit on any side of a plot, - Can automatically synchronize its displayed scale with ViewBox items. - Ticks can be extended to draw a grid. - If maxTickLength is negative, ticks point into the plot. - """ - - def __init__(self, orientation, pen=None, textPen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True, text='', units='', unitPrefix='', **args): - """ - =============== =============================================================== - **Arguments:** - orientation one of 'left', 'right', 'top', or 'bottom' - maxTickLength (px) maximum length of ticks to draw. Negative values draw - into the plot, positive values draw outward. - linkView (ViewBox) causes the range of values displayed in the axis - to be linked to the visible range of a ViewBox. - showValues (bool) Whether to display values adjacent to ticks - pen (QPen) Pen used when drawing ticks. - textPen (QPen) Pen used when drawing tick labels. - text The text (excluding units) to display on the label for this - axis. - units The units for this axis. Units should generally be given - without any scaling prefix (eg, 'V' instead of 'mV'). The - scaling prefix will be automatically prepended based on the - range of data displayed. - args All extra keyword arguments become CSS style options for - the tag which will surround the axis label and units. - =============== =============================================================== - """ - - GraphicsWidget.__init__(self, parent) - self.label = QtGui.QGraphicsTextItem(self) - self.picture = None - self.orientation = orientation - if orientation not in ['left', 'right', 'top', 'bottom']: - raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") - if orientation in ['left', 'right']: - self.label.setRotation(-90) - - self.style = { - 'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis - 'tickTextWidth': 30, ## space reserved for tick text - 'tickTextHeight': 18, - 'autoExpandTextSpace': True, ## automatically expand text space if needed - 'autoReduceTextSpace': True, - 'tickFont': None, - 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick - 'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally. - (0, 0.8), ## never fill more than 80% of the axis - (2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis - (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis - (6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis - ], - 'showValues': showValues, - 'tickLength': maxTickLength, - 'maxTickLevel': 2, - 'maxTextLevel': 2, - 'tickAlpha': None, ## If not none, use this alpha for all ticks. - } - - self.textWidth = 30 ## Keeps track of maximum width / height of tick text - self.textHeight = 18 - - # If the user specifies a width / height, remember that setting - # indefinitely. - self.fixedWidth = None - self.fixedHeight = None - - self.labelText = text - self.labelUnits = units - self.labelUnitPrefix = unitPrefix - self.labelStyle = args - self.logMode = False - - self._tickLevels = None ## used to override the automatic ticking system with explicit ticks - self._tickSpacing = None # used to override default tickSpacing method - self.scale = 1.0 - self.autoSIPrefix = True - self.autoSIPrefixScale = 1.0 - - self.showLabel(False) - - self.setRange(0, 1) - - if pen is None: - self.setPen() - else: - self.setPen(pen) - - if textPen is None: - self.setTextPen() - else: - self.setTextPen(pen) - - self._linkedView = None - if linkView is not None: - self.linkToView(linkView) - - self.grid = False - #self.setCacheMode(self.DeviceCoordinateCache) - - def setStyle(self, **kwds): - """ - Set various style options. - - =================== ======================================================= - Keyword Arguments: - tickLength (int) The maximum length of ticks in pixels. - Positive values point toward the text; negative - values point away. - tickTextOffset (int) reserved spacing between text and axis in px - tickTextWidth (int) Horizontal space reserved for tick text in px - tickTextHeight (int) Vertical space reserved for tick text in px - autoExpandTextSpace (bool) Automatically expand text space if the tick - strings become too long. - autoReduceTextSpace (bool) Automatically shrink the axis if necessary - tickFont (QFont or None) Determines the font used for tick - values. Use None for the default font. - stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis - line is drawn only as far as the last tick. - Otherwise, the line is drawn to the edge of the - AxisItem boundary. - textFillLimits (list of (tick #, % fill) tuples). This structure - determines how the AxisItem decides how many ticks - should have text appear next to them. Each tuple in - the list specifies what fraction of the axis length - may be occupied by text, given the number of ticks - that already have text displayed. For example:: - - [(0, 0.8), # Never fill more than 80% of the axis - (2, 0.6), # If we already have 2 ticks with text, - # fill no more than 60% of the axis - (4, 0.4), # If we already have 4 ticks with text, - # fill no more than 40% of the axis - (6, 0.2)] # If we already have 6 ticks with text, - # fill no more than 20% of the axis - - showValues (bool) indicates whether text is displayed adjacent - to ticks. - tickAlpha (float or int or None) If None, pyqtgraph will draw the - ticks with the alpha it deems appropriate. Otherwise, - the alpha will be fixed at the value passed. With int, - accepted values are [0..255]. With vaule of type - float, accepted values are from [0..1]. - =================== ======================================================= - - Added in version 0.9.9 - """ - for kwd,value in kwds.items(): - if kwd not in self.style: - raise NameError("%s is not a valid style argument." % kwd) - - if kwd in ('tickLength', 'tickTextOffset', 'tickTextWidth', 'tickTextHeight'): - if not isinstance(value, int): - raise ValueError("Argument '%s' must be int" % kwd) - - if kwd == 'tickTextOffset': - if self.orientation in ('left', 'right'): - self.style['tickTextOffset'][0] = value - else: - self.style['tickTextOffset'][1] = value - elif kwd == 'stopAxisAtTick': - try: - assert len(value) == 2 and isinstance(value[0], bool) and isinstance(value[1], bool) - except: - raise ValueError("Argument 'stopAxisAtTick' must have type (bool, bool)") - self.style[kwd] = value - else: - self.style[kwd] = value - - self.picture = None - self._adjustSize() - self.update() - - def close(self): - self.scene().removeItem(self.label) - self.label = None - self.scene().removeItem(self) - - def setGrid(self, grid): - """Set the alpha value (0-255) for the grid, or False to disable. - - When grid lines are enabled, the axis tick lines are extended to cover - the extent of the linked ViewBox, if any. - """ - self.grid = grid - self.picture = None - self.prepareGeometryChange() - self.update() - - def setLogMode(self, log): - """ - If *log* is True, then ticks are displayed on a logarithmic scale and values - are adjusted accordingly. (This is usually accessed by changing the log mode - of a :func:`PlotItem `) - """ - self.logMode = log - self.picture = None - self.update() - - def setTickFont(self, font): - """ - (QFont or None) Determines the font used for tick values. - Use None for the default font. - """ - self.style['tickFont'] = font - self.picture = None - self.prepareGeometryChange() - ## Need to re-allocate space depending on font size? - - self.update() - - def resizeEvent(self, ev=None): - #s = self.size() - - ## Set the position of the label - nudge = 5 - br = self.label.boundingRect() - p = QtCore.QPointF(0, 0) - if self.orientation == 'left': - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(-nudge) - elif self.orientation == 'right': - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(int(self.size().width()-br.height()+nudge)) - elif self.orientation == 'top': - p.setY(-nudge) - p.setX(int(self.size().width()/2. - br.width()/2.)) - elif self.orientation == 'bottom': - p.setX(int(self.size().width()/2. - br.width()/2.)) - p.setY(int(self.size().height()-br.height()+nudge)) - self.label.setPos(p) - self.picture = None - - def showLabel(self, show=True): - """Show/hide the label text for this axis.""" - #self.drawLabel = show - self.label.setVisible(show) - if self.orientation in ['left', 'right']: - self._updateWidth() - else: - self._updateHeight() - if self.autoSIPrefix: - self.updateAutoSIPrefix() - - def setLabel(self, text=None, units=None, unitPrefix=None, **args): - """Set the text displayed adjacent to the axis. - - ============== ============================================================= - **Arguments:** - text The text (excluding units) to display on the label for this - axis. - units The units for this axis. Units should generally be given - without any scaling prefix (eg, 'V' instead of 'mV'). The - scaling prefix will be automatically prepended based on the - range of data displayed. - args All extra keyword arguments become CSS style options for - the tag which will surround the axis label and units. - ============== ============================================================= - - The final text generated for the label will look like:: - - {text} (prefix{units}) - - Each extra keyword argument will become a CSS option in the above template. - For example, you can set the font size and color of the label:: - - labelStyle = {'color': '#FFF', 'font-size': '14pt'} - axis.setLabel('label text', units='V', **labelStyle) - - """ - # `None` input is kept for backward compatibility! - self.labelText = text or "" - self.labelUnits = units or "" - self.labelUnitPrefix = unitPrefix or "" - if len(args) > 0: - self.labelStyle = args - # Account empty string and `None` for units and text - visible = True if (text or units) else False - self.showLabel(visible) - self._updateLabel() - - def _updateLabel(self): - """Internal method to update the label according to the text""" - self.label.setHtml(self.labelString()) - self._adjustSize() - self.picture = None - self.update() - - def labelString(self): - if self.labelUnits == '': - if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: - units = '' - else: - units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale) - else: - #print repr(self.labelUnitPrefix), repr(self.labelUnits) - units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) - - s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units)) - - style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) - - return asUnicode("%s") % (style, asUnicode(s)) - - def _updateMaxTextSize(self, x): - ## Informs that the maximum tick size orthogonal to the axis has - ## changed; we use this to decide whether the item needs to be resized - ## to accomodate. - if self.orientation in ['left', 'right']: - if self.style["autoReduceTextSpace"]: - if x > self.textWidth or x < self.textWidth - 10: - self.textWidth = x - else: - mx = max(self.textWidth, x) - if mx > self.textWidth or mx < self.textWidth - 10: - self.textWidth = mx - if self.style['autoExpandTextSpace']: - self._updateWidth() - - else: - if self.style['autoReduceTextSpace']: - if x > self.textHeight or x < self.textHeight - 10: - self.textHeight = x - else: - mx = max(self.textHeight, x) - if mx > self.textHeight or mx < self.textHeight - 10: - self.textHeight = mx - if self.style['autoExpandTextSpace']: - self._updateHeight() - - def _adjustSize(self): - if self.orientation in ['left', 'right']: - self._updateWidth() - else: - self._updateHeight() - - def setHeight(self, h=None): - """Set the height of this axis reserved for ticks and tick labels. - The height of the axis label is automatically added. - - If *height* is None, then the value will be determined automatically - based on the size of the tick text.""" - self.fixedHeight = h - self._updateHeight() - - def _updateHeight(self): - if not self.isVisible(): - h = 0 - else: - if self.fixedHeight is None: - if not self.style['showValues']: - h = 0 - elif self.style['autoExpandTextSpace']: - h = self.textHeight - else: - h = self.style['tickTextHeight'] - h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0 - h += max(0, self.style['tickLength']) - if self.label.isVisible(): - h += self.label.boundingRect().height() * 0.8 - else: - h = self.fixedHeight - - self.setMaximumHeight(h) - self.setMinimumHeight(h) - self.picture = None - - def setWidth(self, w=None): - """Set the width of this axis reserved for ticks and tick labels. - The width of the axis label is automatically added. - - If *width* is None, then the value will be determined automatically - based on the size of the tick text.""" - self.fixedWidth = w - self._updateWidth() - - def _updateWidth(self): - if not self.isVisible(): - w = 0 - else: - if self.fixedWidth is None: - if not self.style['showValues']: - w = 0 - elif self.style['autoExpandTextSpace']: - w = self.textWidth - else: - w = self.style['tickTextWidth'] - w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0 - w += max(0, self.style['tickLength']) - if self.label.isVisible(): - w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate - else: - w = self.fixedWidth - - self.setMaximumWidth(w) - self.setMinimumWidth(w) - self.picture = None - - def pen(self): - if self._pen is None: - return fn.mkPen(getConfigOption('foreground')) - return fn.mkPen(self._pen) - - def setPen(self, *args, **kwargs): - """ - Set the pen used for drawing text, axes, ticks, and grid lines. - If no arguments are given, the default foreground color will be used - (see :func:`setConfigOption `). - """ - self.picture = None - if args or kwargs: - self._pen = fn.mkPen(*args, **kwargs) - else: - self._pen = fn.mkPen(getConfigOption('foreground')) - self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6] - self._updateLabel() - - def textPen(self): - if self._textPen is None: - return fn.mkPen(getConfigOption('foreground')) - return fn.mkPen(self._textPen) - - def setTextPen(self, *args, **kwargs): - """ - Set the pen used for drawing text. - If no arguments are given, the default foreground color will be used. - """ - self.picture = None - if args or kwargs: - self._textPen = fn.mkPen(*args, **kwargs) - else: - self._textPen = fn.mkPen(getConfigOption('foreground')) - self.labelStyle['color'] = '#' + fn.colorStr(self._textPen.color())[:6] - self._updateLabel() - - def setScale(self, scale=None): - """ - Set the value scaling for this axis. - - Setting this value causes the axis to draw ticks and tick labels as if - the view coordinate system were scaled. By default, the axis scaling is - 1.0. - """ - # Deprecated usage, kept for backward compatibility - if scale is None: - warnings.warn( - 'AxisItem.setScale(None) is deprecated, will be removed in 0.13.0' - 'instead use AxisItem.enableAutoSIPrefix(bool) to enable/disable' - 'SI prefix scaling', - DeprecationWarning, stacklevel=2 - ) - scale = 1.0 - self.enableAutoSIPrefix(True) - - if scale != self.scale: - self.scale = scale - self._updateLabel() - - def enableAutoSIPrefix(self, enable=True): - """ - Enable (or disable) automatic SI prefix scaling on this axis. - - When enabled, this feature automatically determines the best SI prefix - to prepend to the label units, while ensuring that axis values are scaled - accordingly. - - For example, if the axis spans values from -0.1 to 0.1 and has units set - to 'V' then the axis would display values -100 to 100 - and the units would appear as 'mV' - - This feature is enabled by default, and is only available when a suffix - (unit string) is provided to display on the label. - """ - self.autoSIPrefix = enable - self.updateAutoSIPrefix() - - def updateAutoSIPrefix(self): - if self.label.isVisible(): - if self.logMode: - _range = 10**np.array(self.range) - else: - _range = self.range - (scale, prefix) = fn.siScale(max(abs(_range[0]*self.scale), abs(_range[1]*self.scale))) - if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. - scale = 1.0 - prefix = '' - self.autoSIPrefixScale = scale - self.labelUnitPrefix = prefix - else: - self.autoSIPrefixScale = 1.0 - - self._updateLabel() - - def setRange(self, mn, mx): - """Set the range of values displayed by the axis. - Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" - if any(np.isinf((mn, mx))) or any(np.isnan((mn, mx))): - raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) - self.range = [mn, mx] - if self.autoSIPrefix: - # XXX: Will already update once! - self.updateAutoSIPrefix() - else: - self.picture = None - self.update() - - def linkedView(self): - """Return the ViewBox this axis is linked to""" - if self._linkedView is None: - return None - else: - return self._linkedView() - - def linkToView(self, view): - """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" - self.unlinkFromView() - - self._linkedView = weakref.ref(view) - if self.orientation in ['right', 'left']: - view.sigYRangeChanged.connect(self.linkedViewChanged) - else: - view.sigXRangeChanged.connect(self.linkedViewChanged) - - view.sigResized.connect(self.linkedViewChanged) - - def unlinkFromView(self): - """Unlink this axis from a ViewBox.""" - oldView = self.linkedView() - self._linkedView = None - if self.orientation in ['right', 'left']: - if oldView is not None: - oldView.sigYRangeChanged.disconnect(self.linkedViewChanged) - else: - if oldView is not None: - oldView.sigXRangeChanged.disconnect(self.linkedViewChanged) - - if oldView is not None: - oldView.sigResized.disconnect(self.linkedViewChanged) - - def linkedViewChanged(self, view, newRange=None): - if self.orientation in ['right', 'left']: - if newRange is None: - newRange = view.viewRange()[1] - if view.yInverted(): - self.setRange(*newRange[::-1]) - else: - self.setRange(*newRange) - else: - if newRange is None: - newRange = view.viewRange()[0] - if view.xInverted(): - self.setRange(*newRange[::-1]) - else: - self.setRange(*newRange) - - def boundingRect(self): - linkedView = self.linkedView() - if linkedView is None or self.grid is False: - rect = self.mapRectFromParent(self.geometry()) - ## extend rect if ticks go in negative direction - ## also extend to account for text that flows past the edges - tl = self.style['tickLength'] - if self.orientation == 'left': - rect = rect.adjusted(0, -15, -min(0,tl), 15) - elif self.orientation == 'right': - rect = rect.adjusted(min(0,tl), -15, 0, 15) - elif self.orientation == 'top': - rect = rect.adjusted(-15, 0, 15, -min(0,tl)) - elif self.orientation == 'bottom': - rect = rect.adjusted(-15, min(0,tl), 15, 0) - return rect - else: - return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) - - def paint(self, p, opt, widget): - profiler = debug.Profiler() - if self.picture is None: - try: - picture = QtGui.QPicture() - painter = QtGui.QPainter(picture) - if self.style["tickFont"]: - painter.setFont(self.style["tickFont"]) - specs = self.generateDrawSpecs(painter) - profiler('generate specs') - if specs is not None: - self.drawPicture(painter, *specs) - profiler('draw picture') - finally: - painter.end() - self.picture = picture - #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ??? - #p.setRenderHint(p.TextAntialiasing, True) - self.picture.play(p) - - def setTicks(self, ticks): - """Explicitly determine which ticks to display. - This overrides the behavior specified by tickSpacing(), tickValues(), and tickStrings() - The format for *ticks* looks like:: - - [ - [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], - [ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ], - ... - ] - - If *ticks* is None, then the default tick system will be used instead. - """ - self._tickLevels = ticks - self.picture = None - self.update() - - def setTickSpacing(self, major=None, minor=None, levels=None): - """ - Explicitly determine the spacing of major and minor ticks. This - overrides the default behavior of the tickSpacing method, and disables - the effect of setTicks(). Arguments may be either *major* and *minor*, - or *levels* which is a list of (spacing, offset) tuples for each - tick level desired. - - If no arguments are given, then the default behavior of tickSpacing - is enabled. - - Examples:: - - # two levels, all offsets = 0 - axis.setTickSpacing(5, 1) - # three levels, all offsets = 0 - axis.setTickSpacing([(3, 0), (1, 0), (0.25, 0)]) - # reset to default - axis.setTickSpacing() - """ - - if levels is None: - if major is None: - levels = None - else: - levels = [(major, 0), (minor, 0)] - self._tickSpacing = levels - self.picture = None - self.update() - - def tickSpacing(self, minVal, maxVal, size): - """Return values describing the desired spacing and offset of ticks. - - This method is called whenever the axis needs to be redrawn and is a - good method to override in subclasses that require control over tick locations. - - The return value must be a list of tuples, one for each set of ticks:: - - [ - (major tick spacing, offset), - (minor tick spacing, offset), - (sub-minor tick spacing, offset), - ... - ] - """ - # First check for override tick spacing - if self._tickSpacing is not None: - return self._tickSpacing - - dif = abs(maxVal - minVal) - if dif == 0: - return [] - - ## decide optimal minor tick spacing in pixels (this is just aesthetics) - optimalTickCount = max(2., np.log(size)) - - ## optimal minor tick spacing - optimalSpacing = dif / optimalTickCount - - ## the largest power-of-10 spacing which is smaller than optimal - p10unit = 10 ** np.floor(np.log10(optimalSpacing)) - - ## Determine major/minor tick spacings which flank the optimal spacing. - intervals = np.array([1., 2., 10., 20., 100.]) * p10unit - minorIndex = 0 - while intervals[minorIndex+1] <= optimalSpacing: - minorIndex += 1 - - levels = [ - (intervals[minorIndex+2], 0), - (intervals[minorIndex+1], 0), - #(intervals[minorIndex], 0) ## Pretty, but eats up CPU - ] - - if self.style['maxTickLevel'] >= 2: - ## decide whether to include the last level of ticks - minSpacing = min(size / 20., 30.) - maxTickCount = size / minSpacing - if dif / intervals[minorIndex] <= maxTickCount: - levels.append((intervals[minorIndex], 0)) - - return levels - - ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection - ### Determine major/minor tick spacings which flank the optimal spacing. - #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit - #minorIndex = 0 - #while intervals[minorIndex+1] <= optimalSpacing: - #minorIndex += 1 - - ### make sure we never see 5 and 2 at the same time - #intIndexes = [ - #[0,1,3], - #[0,2,3], - #[2,3,4], - #[3,4,6], - #[3,5,6], - #][minorIndex] - - #return [ - #(intervals[intIndexes[2]], 0), - #(intervals[intIndexes[1]], 0), - #(intervals[intIndexes[0]], 0) - #] - - def tickValues(self, minVal, maxVal, size): - """ - Return the values and spacing of ticks to draw:: - - [ - (spacing, [major ticks]), - (spacing, [minor ticks]), - ... - ] - - By default, this method calls tickSpacing to determine the correct tick locations. - This is a good method to override in subclasses. - """ - minVal, maxVal = sorted((minVal, maxVal)) - - - minVal *= self.scale - maxVal *= self.scale - #size *= self.scale - - ticks = [] - tickLevels = self.tickSpacing(minVal, maxVal, size) - allValues = np.array([]) - for i in range(len(tickLevels)): - spacing, offset = tickLevels[i] - - ## determine starting tick - start = (np.ceil((minVal-offset) / spacing) * spacing) + offset - - ## determine number of ticks - num = int((maxVal-start) / spacing) + 1 - values = (np.arange(num) * spacing + start) / self.scale - ## remove any ticks that were present in higher levels - ## we assume here that if the difference between a tick value and a previously seen tick value - ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. - values = list(filter(lambda x: all(np.abs(allValues-x) > spacing/self.scale*0.01), values)) - allValues = np.concatenate([allValues, values]) - ticks.append((spacing/self.scale, values)) - - if self.logMode: - return self.logTickValues(minVal, maxVal, size, ticks) - - - #nticks = [] - #for t in ticks: - #nvals = [] - #for v in t[1]: - #nvals.append(v/self.scale) - #nticks.append((t[0]/self.scale,nvals)) - #ticks = nticks - - return ticks - - def logTickValues(self, minVal, maxVal, size, stdTicks): - - ## start with the tick spacing given by tickValues(). - ## Any level whose spacing is < 1 needs to be converted to log scale - - ticks = [] - for (spacing, t) in stdTicks: - if spacing >= 1.0: - ticks.append((spacing, t)) - - if len(ticks) < 3: - v1 = int(np.floor(minVal)) - v2 = int(np.ceil(maxVal)) - #major = list(range(v1+1, v2)) - - minor = [] - for v in range(v1, v2): - minor.extend(v + np.log10(np.arange(1, 10))) - minor = [x for x in minor if x>minVal and x= 10000: - vstr = "%g" % vs - else: - vstr = ("%%0.%df" % places) % vs - strings.append(vstr) - return strings - - def logTickStrings(self, values, scale, spacing): - estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)] - - if sys.version_info < (3, 0): - # python 2 does not support unicode strings like that - return estrings - else: # python 3+ - convdict = {"0": "⁰", - "1": "¹", - "2": "²", - "3": "³", - "4": "⁴", - "5": "⁵", - "6": "⁶", - "7": "⁷", - "8": "⁸", - "9": "⁹", - } - dstrings = [] - for e in estrings: - if e.count("e"): - v, p = e.split("e") - sign = "⁻" if p[0] == "-" else "" - pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")]) - if v == "1": - v = "" - else: - v = v + "·" - dstrings.append(v + "10" + sign + pot) - else: - dstrings.append(e) - return dstrings - - def generateDrawSpecs(self, p): - """ - Calls tickValues() and tickStrings() to determine where and how ticks should - be drawn, then generates from this a set of drawing commands to be - interpreted by drawPicture(). - """ - profiler = debug.Profiler() - if self.style['tickFont'] is not None: - p.setFont(self.style['tickFont']) - bounds = self.mapRectFromParent(self.geometry()) - - linkedView = self.linkedView() - if linkedView is None or self.grid is False: - tickBounds = bounds - else: - tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect()) - - if self.orientation == 'left': - span = (bounds.topRight(), bounds.bottomRight()) - tickStart = tickBounds.right() - tickStop = bounds.right() - tickDir = -1 - axis = 0 - elif self.orientation == 'right': - span = (bounds.topLeft(), bounds.bottomLeft()) - tickStart = tickBounds.left() - tickStop = bounds.left() - tickDir = 1 - axis = 0 - elif self.orientation == 'top': - span = (bounds.bottomLeft(), bounds.bottomRight()) - tickStart = tickBounds.bottom() - tickStop = bounds.bottom() - tickDir = -1 - axis = 1 - elif self.orientation == 'bottom': - span = (bounds.topLeft(), bounds.topRight()) - tickStart = tickBounds.top() - tickStop = bounds.top() - tickDir = 1 - axis = 1 - #print tickStart, tickStop, span - - ## determine size of this item in pixels - points = list(map(self.mapToDevice, span)) - if None in points: - return - lengthInPixels = Point(points[1] - points[0]).length() - if lengthInPixels == 0: - return - - # Determine major / minor / subminor axis ticks - if self._tickLevels is None: - tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels) - tickStrings = None - else: - ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings() - tickLevels = [] - tickStrings = [] - for level in self._tickLevels: - values = [] - strings = [] - tickLevels.append((None, values)) - tickStrings.append(strings) - for val, strn in level: - values.append(val) - strings.append(strn) - - ## determine mapping between tick values and local coordinates - dif = self.range[1] - self.range[0] - if dif == 0: - xScale = 1 - offset = 0 - else: - if axis == 0: - xScale = -bounds.height() / dif - offset = self.range[0] * xScale - bounds.height() - else: - xScale = bounds.width() / dif - offset = self.range[0] * xScale - - xRange = [x * xScale - offset for x in self.range] - xMin = min(xRange) - xMax = max(xRange) - - profiler('init') - - tickPositions = [] # remembers positions of previously drawn ticks - - ## compute coordinates to draw ticks - ## draw three different intervals, long ticks first - tickSpecs = [] - for i in range(len(tickLevels)): - tickPositions.append([]) - ticks = tickLevels[i][1] - - ## length of tick - tickLength = self.style['tickLength'] / ((i*0.5)+1.0) - - lineAlpha = self.style["tickAlpha"] - if lineAlpha is None: - lineAlpha = 255 / (i+1) - if self.grid is not False: - lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) - elif isinstance(lineAlpha, float): - lineAlpha *= 255 - lineAlpha = max(0, int(round(lineAlpha))) - lineAlpha = min(255, int(round(lineAlpha))) - elif isinstance(lineAlpha, int): - if (lineAlpha > 255) or (lineAlpha < 0): - raise ValueError("lineAlpha should be [0..255]") - else: - raise TypeError("Line Alpha should be of type None, float or int") - - for v in ticks: - ## determine actual position to draw this tick - x = (v * xScale) - offset - if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn - tickPositions[i].append(None) - continue - tickPositions[i].append(x) - - p1 = [x, x] - p2 = [x, x] - p1[axis] = tickStart - p2[axis] = tickStop - if self.grid is False: - p2[axis] += tickLength*tickDir - tickPen = self.pen() - color = tickPen.color() - color.setAlpha(int(lineAlpha)) - tickPen.setColor(color) - tickSpecs.append((tickPen, Point(p1), Point(p2))) - profiler('compute ticks') - - - if self.style['stopAxisAtTick'][0] is True: - minTickPosition = min(map(min, tickPositions)) - if axis == 0: - stop = max(span[0].y(), minTickPosition) - span[0].setY(stop) - else: - stop = max(span[0].x(), minTickPosition) - span[0].setX(stop) - if self.style['stopAxisAtTick'][1] is True: - maxTickPosition = max(map(max, tickPositions)) - if axis == 0: - stop = min(span[1].y(), maxTickPosition) - span[1].setY(stop) - else: - stop = min(span[1].x(), maxTickPosition) - span[1].setX(stop) - axisSpec = (self.pen(), span[0], span[1]) - - - textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text - #if self.style['autoExpandTextSpace'] is True: - #textWidth = self.textWidth - #textHeight = self.textHeight - #else: - #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text - #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text - - textSize2 = 0 - lastTextSize2 = 0 - textRects = [] - textSpecs = [] ## list of draw - - # If values are hidden, return early - if not self.style['showValues']: - return (axisSpec, tickSpecs, textSpecs) - - for i in range(min(len(tickLevels), self.style['maxTextLevel']+1)): - ## Get the list of strings to display for this level - if tickStrings is None: - spacing, values = tickLevels[i] - strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing) - else: - strings = tickStrings[i] - - if len(strings) == 0: - continue - - ## ignore strings belonging to ticks that were previously ignored - for j in range(len(strings)): - if tickPositions[i][j] is None: - strings[j] = None - - ## Measure density of text; decide whether to draw this level - rects = [] - for s in strings: - if s is None: - rects.append(None) - else: - br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, asUnicode(s)) - ## boundingRect is usually just a bit too large - ## (but this probably depends on per-font metrics?) - br.setHeight(br.height() * 0.8) - - rects.append(br) - textRects.append(rects[-1]) - - if len(textRects) > 0: - ## measure all text, make sure there's enough room - if axis == 0: - textSize = np.sum([r.height() for r in textRects]) - textSize2 = np.max([r.width() for r in textRects]) - else: - textSize = np.sum([r.width() for r in textRects]) - textSize2 = np.max([r.height() for r in textRects]) - else: - textSize = 0 - textSize2 = 0 - - if i > 0: ## always draw top level - ## If the strings are too crowded, stop drawing text now. - ## We use three different crowding limits based on the number - ## of texts drawn so far. - textFillRatio = float(textSize) / lengthInPixels - finished = False - for nTexts, limit in self.style['textFillLimits']: - if len(textSpecs) >= nTexts and textFillRatio >= limit: - finished = True - break - if finished: - break - - lastTextSize2 = textSize2 - - #spacing, values = tickLevels[best] - #strings = self.tickStrings(values, self.scale, spacing) - # Determine exactly where tick text should be drawn - for j in range(len(strings)): - vstr = strings[j] - if vstr is None: ## this tick was ignored because it is out of bounds - continue - vstr = asUnicode(vstr) - x = tickPositions[i][j] - #textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) - textRect = rects[j] - height = textRect.height() - width = textRect.width() - #self.textHeight = height - offset = max(0,self.style['tickLength']) + textOffset - - if self.orientation == 'left': - alignFlags = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) - elif self.orientation == 'right': - alignFlags = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) - elif self.orientation == 'top': - alignFlags = QtCore.Qt.AlignHCenter|QtCore.Qt.AlignBottom - rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) - elif self.orientation == 'bottom': - alignFlags = QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop - rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) - - if QT_LIB == 'PyQt6': - # PyQt6 doesn't allow or-ing of different enum types - # so we need to take its value property - textFlags = alignFlags.value | QtCore.Qt.TextDontClip.value - else: - # for PyQt5, the following expression is not commutative! - textFlags = alignFlags | QtCore.Qt.TextDontClip - - #p.setPen(self.pen()) - #p.drawText(rect, textFlags, vstr) - textSpecs.append((rect, textFlags, vstr)) - profiler('compute text') - - ## update max text size if needed. - self._updateMaxTextSize(lastTextSize2) - - return (axisSpec, tickSpecs, textSpecs) - - def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): - profiler = debug.Profiler() - - p.setRenderHint(p.Antialiasing, False) - p.setRenderHint(p.TextAntialiasing, True) - - ## draw long line along axis - pen, p1, p2 = axisSpec - p.setPen(pen) - p.drawLine(p1, p2) - p.translate(0.5,0) ## resolves some damn pixel ambiguity - - ## draw ticks - for pen, p1, p2 in tickSpecs: - p.setPen(pen) - p.drawLine(p1, p2) - profiler('draw ticks') - - # Draw all text - if self.style['tickFont'] is not None: - p.setFont(self.style['tickFont']) - p.setPen(self.textPen()) - bounding = self.boundingRect().toAlignedRect() - p.setClipRect(bounding) - for rect, flags, text in textSpecs: - p.drawText(rect, int(flags), text) - - profiler('draw text') - - def show(self): - GraphicsWidget.show(self) - if self.orientation in ['left', 'right']: - self._updateWidth() - else: - self._updateHeight() - - def hide(self): - GraphicsWidget.hide(self) - if self.orientation in ['left', 'right']: - self._updateWidth() - else: - self._updateHeight() - - def wheelEvent(self, ev): - lv = self.linkedView() - if lv is None: - return - if self.orientation in ['left', 'right']: - lv.wheelEvent(ev, axis=1) - else: - lv.wheelEvent(ev, axis=0) - ev.accept() - - def mouseDragEvent(self, event): - lv = self.linkedView() - if lv is None: - return - if self.orientation in ['left', 'right']: - return lv.mouseDragEvent(event, axis=1) - else: - return lv.mouseDragEvent(event, axis=0) - - def mouseClickEvent(self, event): - lv = self.linkedView() - if lv is None: - return - return lv.mouseClickEvent(event) diff --git a/pyqtgraph/graphicsItems/BarGraphItem.py b/pyqtgraph/graphicsItems/BarGraphItem.py deleted file mode 100644 index 4e820cb..0000000 --- a/pyqtgraph/graphicsItems/BarGraphItem.py +++ /dev/null @@ -1,181 +0,0 @@ -from ..Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject -from .. import getConfigOption -from .. import functions as fn -import numpy as np - - -__all__ = ['BarGraphItem'] - -class BarGraphItem(GraphicsObject): - def __init__(self, **opts): - """ - Valid keyword options are: - x, x0, x1, y, y0, y1, width, height, pen, brush - - x specifies the x-position of the center of the bar. - x0, x1 specify left and right edges of the bar, respectively. - width specifies distance from x0 to x1. - You may specify any combination: - - x, width - x0, width - x1, width - x0, x1 - - Likewise y, y0, y1, and height. - If only height is specified, then y0 will be set to 0 - - Example uses: - - BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5) - - - """ - GraphicsObject.__init__(self) - self.opts = dict( - x=None, - y=None, - x0=None, - y0=None, - x1=None, - y1=None, - name=None, - height=None, - width=None, - pen=None, - brush=None, - pens=None, - brushes=None, - ) - self._shape = None - self.picture = None - self.setOpts(**opts) - - def setOpts(self, **opts): - self.opts.update(opts) - self.picture = None - self._shape = None - self.update() - self.informViewBoundsChanged() - - def drawPicture(self): - self.picture = QtGui.QPicture() - self._shape = QtGui.QPainterPath() - p = QtGui.QPainter(self.picture) - - pen = self.opts['pen'] - pens = self.opts['pens'] - - if pen is None and pens is None: - pen = getConfigOption('foreground') - - brush = self.opts['brush'] - brushes = self.opts['brushes'] - if brush is None and brushes is None: - brush = (128, 128, 128) - - def asarray(x): - if x is None or np.isscalar(x) or isinstance(x, np.ndarray): - return x - return np.array(x) - - - x = asarray(self.opts.get('x')) - x0 = asarray(self.opts.get('x0')) - x1 = asarray(self.opts.get('x1')) - width = asarray(self.opts.get('width')) - - if x0 is None: - if width is None: - raise Exception('must specify either x0 or width') - if x1 is not None: - x0 = x1 - width - elif x is not None: - x0 = x - width/2. - else: - raise Exception('must specify at least one of x, x0, or x1') - if width is None: - if x1 is None: - raise Exception('must specify either x1 or width') - width = x1 - x0 - - y = asarray(self.opts.get('y')) - y0 = asarray(self.opts.get('y0')) - y1 = asarray(self.opts.get('y1')) - height = asarray(self.opts.get('height')) - - if y0 is None: - if height is None: - y0 = 0 - elif y1 is not None: - y0 = y1 - height - elif y is not None: - y0 = y - height/2. - else: - y0 = 0 - if height is None: - if y1 is None: - raise Exception('must specify either y1 or height') - height = y1 - y0 - - p.setPen(fn.mkPen(pen)) - p.setBrush(fn.mkBrush(brush)) - for i in range(len(x0 if not np.isscalar(x0) else y0)): - if pens is not None: - p.setPen(fn.mkPen(pens[i])) - if brushes is not None: - p.setBrush(fn.mkBrush(brushes[i])) - - if np.isscalar(x0): - x = x0 - else: - x = x0[i] - if np.isscalar(y0): - y = y0 - else: - y = y0[i] - if np.isscalar(width): - w = width - else: - w = width[i] - if np.isscalar(height): - h = height - else: - h = height[i] - - - rect = QtCore.QRectF(x, y, w, h) - p.drawRect(rect) - self._shape.addRect(rect) - - p.end() - self.prepareGeometryChange() - - - def paint(self, p, *args): - if self.picture is None: - self.drawPicture() - self.picture.play(p) - - def boundingRect(self): - if self.picture is None: - self.drawPicture() - return QtCore.QRectF(self.picture.boundingRect()) - - def shape(self): - if self.picture is None: - self.drawPicture() - return self._shape - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def name(self): - return self.opts.get('name', None) - - def getData(self): - return self.opts.get('x'), self.opts.get('height') diff --git a/pyqtgraph/graphicsItems/ButtonItem.py b/pyqtgraph/graphicsItems/ButtonItem.py deleted file mode 100644 index ccfe4aa..0000000 --- a/pyqtgraph/graphicsItems/ButtonItem.py +++ /dev/null @@ -1,58 +0,0 @@ -from ..Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject - -__all__ = ['ButtonItem'] -class ButtonItem(GraphicsObject): - """Button graphicsItem displaying an image.""" - - clicked = QtCore.Signal(object) - - def __init__(self, imageFile=None, width=None, parentItem=None, pixmap=None): - self.enabled = True - GraphicsObject.__init__(self) - if imageFile is not None: - self.setImageFile(imageFile) - elif pixmap is not None: - self.setPixmap(pixmap) - - if width is not None: - s = float(width) / self.pixmap.width() - self.setScale(s) - if parentItem is not None: - self.setParentItem(parentItem) - self.setOpacity(0.7) - - def setImageFile(self, imageFile): - self.setPixmap(QtGui.QPixmap(imageFile)) - - def setPixmap(self, pixmap): - self.pixmap = pixmap - self.update() - - def mouseClickEvent(self, ev): - if self.enabled: - self.clicked.emit(self) - - def mouseHoverEvent(self, ev): - if not self.enabled: - return - if ev.isEnter(): - self.setOpacity(1.0) - else: - self.setOpacity(0.7) - - def disable(self): - self.enabled = False - self.setOpacity(0.4) - - def enable(self): - self.enabled = True - self.setOpacity(0.7) - - def paint(self, p, *args): - p.setRenderHint(p.Antialiasing) - p.drawPixmap(0, 0, self.pixmap) - - def boundingRect(self): - return QtCore.QRectF(self.pixmap.rect()) - diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py deleted file mode 100644 index 368a0a8..0000000 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ /dev/null @@ -1,122 +0,0 @@ -from ..Qt import QtGui, QtCore -from . import ArrowItem -import numpy as np -from ..Point import Point -import weakref -from .GraphicsObject import GraphicsObject - -__all__ = ['CurvePoint', 'CurveArrow'] -class CurvePoint(GraphicsObject): - """A GraphicsItem that sets its location to a point on a PlotCurveItem. - Also rotates to be tangent to the curve. - The position along the curve is a Qt property, and thus can be easily animated. - - Note: This class does not display anything; see CurveArrow for an applied example - """ - - def __init__(self, curve, index=0, pos=None, rotate=True): - """Position can be set either as an index referring to the sample number or - the position 0.0 - 1.0 - If *rotate* is True, then the item rotates to match the tangent of the curve. - """ - - GraphicsObject.__init__(self) - #QObjectWorkaround.__init__(self) - self._rotate = rotate - self.curve = weakref.ref(curve) - self.setParentItem(curve) - self.setProperty('position', 0.0) - self.setProperty('index', 0) - - if hasattr(self, 'ItemHasNoContents'): - self.setFlags(self.flags() | self.ItemHasNoContents) - - if pos is not None: - self.setPos(pos) - else: - self.setIndex(index) - - def setPos(self, pos): - self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. - - def setIndex(self, index): - self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. - - def event(self, ev): - if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: - return False - - if ev.propertyName() == 'index': - index = self.property('index') - if 'QVariant' in repr(index): - index = index.toInt()[0] - elif ev.propertyName() == 'position': - index = None - else: - return False - - (x, y) = self.curve().getData() - if index is None: - #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() - pos = self.property('position') - if 'QVariant' in repr(pos): ## need to support 2 APIs :( - pos = pos.toDouble()[0] - index = (len(x)-1) * np.clip(pos, 0.0, 1.0) - - if index != int(index): ## interpolate floating-point values - i1 = int(index) - i2 = np.clip(i1+1, 0, len(x)-1) - s2 = index-i1 - s1 = 1.0-s2 - newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) - else: - index = int(index) - i1 = np.clip(index-1, 0, len(x)-1) - i2 = np.clip(index+1, 0, len(x)-1) - newPos = (x[index], y[index]) - - p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) - p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) - ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians - self.resetTransform() - if self._rotate: - self.setRotation(180 + np.rad2deg(ang)) ## takes degrees - QtGui.QGraphicsItem.setPos(self, *newPos) - return True - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): - # In Python 3, a bytes object needs to be used as a property name in - # QPropertyAnimation. PyQt stopped automatically encoding a str when a - # QByteArray was expected in v5.5 (see qbytearray.sip). - if not isinstance(prop, bytes): - prop = prop.encode('latin-1') - anim = QtCore.QPropertyAnimation(self, prop) - anim.setDuration(duration) - anim.setStartValue(start) - anim.setEndValue(end) - anim.setLoopCount(loop) - return anim - - -class CurveArrow(CurvePoint): - """Provides an arrow that points to any specific sample on a PlotCurveItem. - Provides properties that can be animated.""" - - def __init__(self, curve, index=0, pos=None, **opts): - CurvePoint.__init__(self, curve, index=index, pos=pos) - if opts.get('pxMode', True): - opts['pxMode'] = False - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - opts['angle'] = 0 - self.arrow = ArrowItem.ArrowItem(**opts) - self.arrow.setParentItem(self) - - def setStyle(self, **opts): - return self.arrow.setStyle(**opts) - diff --git a/pyqtgraph/graphicsItems/DateAxisItem.py b/pyqtgraph/graphicsItems/DateAxisItem.py deleted file mode 100644 index 3f77cef..0000000 --- a/pyqtgraph/graphicsItems/DateAxisItem.py +++ /dev/null @@ -1,318 +0,0 @@ -import sys -import numpy as np -import time -from datetime import datetime, timedelta - -from .AxisItem import AxisItem -from collections import OrderedDict - -__all__ = ['DateAxisItem'] - -MS_SPACING = 1/1000.0 -SECOND_SPACING = 1 -MINUTE_SPACING = 60 -HOUR_SPACING = 3600 -DAY_SPACING = 24 * HOUR_SPACING -WEEK_SPACING = 7 * DAY_SPACING -MONTH_SPACING = 30 * DAY_SPACING -YEAR_SPACING = 365 * DAY_SPACING - -if sys.platform == 'win32': - _epoch = datetime.utcfromtimestamp(0) - def utcfromtimestamp(timestamp): - return _epoch + timedelta(seconds=timestamp) -else: - utcfromtimestamp = datetime.utcfromtimestamp - -MIN_REGULAR_TIMESTAMP = (datetime(1, 1, 1) - datetime(1970,1,1)).total_seconds() -MAX_REGULAR_TIMESTAMP = (datetime(9999, 1, 1) - datetime(1970,1,1)).total_seconds() -SEC_PER_YEAR = 365.25*24*3600 - -def makeMSStepper(stepSize): - def stepper(val, n): - if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: - return np.inf - - val *= 1000 - f = stepSize * 1000 - return (val // (n*f) + 1) * (n*f) / 1000.0 - return stepper - -def makeSStepper(stepSize): - def stepper(val, n): - if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: - return np.inf - - return (val // (n*stepSize) + 1) * (n*stepSize) - return stepper - -def makeMStepper(stepSize): - def stepper(val, n): - if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: - return np.inf - - d = utcfromtimestamp(val) - base0m = (d.month + n*stepSize - 1) - d = datetime(d.year + base0m // 12, base0m % 12 + 1, 1) - return (d - datetime(1970, 1, 1)).total_seconds() - return stepper - -def makeYStepper(stepSize): - def stepper(val, n): - if val < MIN_REGULAR_TIMESTAMP or val > MAX_REGULAR_TIMESTAMP: - return np.inf - - d = utcfromtimestamp(val) - next_year = (d.year // (n*stepSize) + 1) * (n*stepSize) - if next_year > 9999: - return np.inf - next_date = datetime(next_year, 1, 1) - return (next_date - datetime(1970, 1, 1)).total_seconds() - return stepper - -class TickSpec: - """ Specifies the properties for a set of date ticks and computes ticks - within a given utc timestamp range """ - def __init__(self, spacing, stepper, format, autoSkip=None): - """ - ============= ========================================================== - Arguments - spacing approximate (average) tick spacing - stepper a stepper function that takes a utc time stamp and a step - steps number n to compute the start of the next unit. You - can use the make_X_stepper functions to create common - steppers. - format a strftime compatible format string which will be used to - convert tick locations to date/time strings - autoSkip list of step size multipliers to be applied when the tick - density becomes too high. The tick spec automatically - applies additional powers of 10 (10, 100, ...) to the list - if necessary. Set to None to switch autoSkip off - ============= ========================================================== - - """ - self.spacing = spacing - self.step = stepper - self.format = format - self.autoSkip = autoSkip - - def makeTicks(self, minVal, maxVal, minSpc): - ticks = [] - n = self.skipFactor(minSpc) - x = self.step(minVal, n) - while x <= maxVal: - ticks.append(x) - x = self.step(x, n) - return (np.array(ticks), n) - - def skipFactor(self, minSpc): - if self.autoSkip is None or minSpc < self.spacing: - return 1 - factors = np.array(self.autoSkip, dtype=np.float64) - while True: - for f in factors: - spc = self.spacing * f - if spc > minSpc: - return int(f) - factors *= 10 - - -class ZoomLevel: - """ Generates the ticks which appear in a specific zoom level """ - def __init__(self, tickSpecs, exampleText): - """ - ============= ========================================================== - tickSpecs a list of one or more TickSpec objects with decreasing - coarseness - ============= ========================================================== - - """ - self.tickSpecs = tickSpecs - self.utcOffset = 0 - self.exampleText = exampleText - - def tickValues(self, minVal, maxVal, minSpc): - # return tick values for this format in the range minVal, maxVal - # the return value is a list of tuples (, [tick positions]) - # minSpc indicates the minimum spacing (in seconds) between two ticks - # to fullfill the maxTicksPerPt constraint of the DateAxisItem at the - # current zoom level. This is used for auto skipping ticks. - allTicks = [] - valueSpecs = [] - # back-project (minVal maxVal) to UTC, compute ticks then offset to - # back to local time again - utcMin = minVal - self.utcOffset - utcMax = maxVal - self.utcOffset - for spec in self.tickSpecs: - ticks, skipFactor = spec.makeTicks(utcMin, utcMax, minSpc) - # reposition tick labels to local time coordinates - ticks += self.utcOffset - # remove any ticks that were present in higher levels - tick_list = [x for x in ticks.tolist() if x not in allTicks] - allTicks.extend(tick_list) - valueSpecs.append((spec.spacing, tick_list)) - # if we're skipping ticks on the current level there's no point in - # producing lower level ticks - if skipFactor > 1: - break - return valueSpecs - - -YEAR_MONTH_ZOOM_LEVEL = ZoomLevel([ - TickSpec(YEAR_SPACING, makeYStepper(1), '%Y', autoSkip=[1, 5, 10, 25]), - TickSpec(MONTH_SPACING, makeMStepper(1), '%b') -], "YYYY") -MONTH_DAY_ZOOM_LEVEL = ZoomLevel([ - TickSpec(MONTH_SPACING, makeMStepper(1), '%b'), - TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%d', autoSkip=[1, 5]) -], "MMM") -DAY_HOUR_ZOOM_LEVEL = ZoomLevel([ - TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%a %d'), - TickSpec(HOUR_SPACING, makeSStepper(HOUR_SPACING), '%H:%M', autoSkip=[1, 6]) -], "MMM 00") -HOUR_MINUTE_ZOOM_LEVEL = ZoomLevel([ - TickSpec(DAY_SPACING, makeSStepper(DAY_SPACING), '%a %d'), - TickSpec(MINUTE_SPACING, makeSStepper(MINUTE_SPACING), '%H:%M', - autoSkip=[1, 5, 15]) -], "MMM 00") -HMS_ZOOM_LEVEL = ZoomLevel([ - TickSpec(SECOND_SPACING, makeSStepper(SECOND_SPACING), '%H:%M:%S', - autoSkip=[1, 5, 15, 30]) -], "99:99:99") -MS_ZOOM_LEVEL = ZoomLevel([ - TickSpec(MINUTE_SPACING, makeSStepper(MINUTE_SPACING), '%H:%M:%S'), - TickSpec(MS_SPACING, makeMSStepper(MS_SPACING), '%S.%f', - autoSkip=[1, 5, 10, 25]) -], "99:99:99") - -class DateAxisItem(AxisItem): - """ - **Bases:** :class:`AxisItem ` - - An AxisItem that displays dates from unix timestamps. - - The display format is adjusted automatically depending on the current time - density (seconds/point) on the axis. For more details on changing this - behaviour, see :func:`setZoomLevelForDensity() `. - - Can be added to an existing plot e.g. via - :func:`setAxisItems({'bottom':axis}) `. - - """ - - def __init__(self, orientation='bottom', utcOffset=time.timezone, **kwargs): - """ - Create a new DateAxisItem. - - For `orientation` and `**kwargs`, see - :func:`AxisItem.__init__ `. - - """ - - super(DateAxisItem, self).__init__(orientation, **kwargs) - # Set the zoom level to use depending on the time density on the axis - self.utcOffset = utcOffset - - self.zoomLevels = OrderedDict([ - (np.inf, YEAR_MONTH_ZOOM_LEVEL), - (5 * 3600*24, MONTH_DAY_ZOOM_LEVEL), - (6 * 3600, DAY_HOUR_ZOOM_LEVEL), - (15 * 60, HOUR_MINUTE_ZOOM_LEVEL), - (30, HMS_ZOOM_LEVEL), - (1, MS_ZOOM_LEVEL), - ]) - self.autoSIPrefix = False - - def tickStrings(self, values, scale, spacing): - tickSpecs = self.zoomLevel.tickSpecs - tickSpec = next((s for s in tickSpecs if s.spacing == spacing), None) - try: - dates = [utcfromtimestamp(v - self.utcOffset) for v in values] - except (OverflowError, ValueError, OSError): - # should not normally happen - return ['%g' % ((v-self.utcOffset)//SEC_PER_YEAR + 1970) for v in values] - - formatStrings = [] - for x in dates: - try: - s = x.strftime(tickSpec.format) - if '%f' in tickSpec.format: - # we only support ms precision - s = s[:-3] - elif '%Y' in tickSpec.format: - s = s.lstrip('0') - formatStrings.append(s) - except ValueError: # Windows can't handle dates before 1970 - formatStrings.append('') - return formatStrings - - def tickValues(self, minVal, maxVal, size): - density = (maxVal - minVal) / size - self.setZoomLevelForDensity(density) - values = self.zoomLevel.tickValues(minVal, maxVal, minSpc=self.minSpacing) - return values - - def setZoomLevelForDensity(self, density): - """ - Setting `zoomLevel` and `minSpacing` based on given density of seconds per pixel - - The display format is adjusted automatically depending on the current time - density (seconds/point) on the axis. You can customize the behaviour by - overriding this function or setting a different set of zoom levels - than the default one. The `zoomLevels` variable is a dictionary with the - maximal distance of ticks in seconds which are allowed for each zoom level - before the axis switches to the next coarser level. To customize the zoom level - selection, override this function. - """ - padding = 10 - - # Size in pixels a specific tick label will take - if self.orientation in ['bottom', 'top']: - def sizeOf(text): - return self.fontMetrics.boundingRect(text).width() + padding - else: - def sizeOf(text): - return self.fontMetrics.boundingRect(text).height() + padding - - # Fallback zoom level: Years/Months - self.zoomLevel = YEAR_MONTH_ZOOM_LEVEL - for maximalSpacing, zoomLevel in self.zoomLevels.items(): - size = sizeOf(zoomLevel.exampleText) - - # Test if zoom level is too fine grained - if maximalSpacing/size < density: - break - - self.zoomLevel = zoomLevel - - # Set up zoomLevel - self.zoomLevel.utcOffset = self.utcOffset - - # Calculate minimal spacing of items on the axis - size = sizeOf(self.zoomLevel.exampleText) - self.minSpacing = density*size - - def linkToView(self, view): - super(DateAxisItem, self).linkToView(view) - - # Set default limits - _min = MIN_REGULAR_TIMESTAMP - _max = MAX_REGULAR_TIMESTAMP - - if self.orientation in ['right', 'left']: - view.setLimits(yMin=_min, yMax=_max) - else: - view.setLimits(xMin=_min, xMax=_max) - - def generateDrawSpecs(self, p): - # Get font metrics from QPainter - # Not happening in "paint", as the QPainter p there is a different one from the one here, - # so changing that font could cause unwanted side effects - if self.style['tickFont'] is not None: - p.setFont(self.style['tickFont']) - - self.fontMetrics = p.fontMetrics() - - # Get font scale factor by current window resolution - - return super(DateAxisItem, self).generateDrawSpecs(p) diff --git a/pyqtgraph/graphicsItems/ErrorBarItem.py b/pyqtgraph/graphicsItems/ErrorBarItem.py deleted file mode 100644 index 365f1d4..0000000 --- a/pyqtgraph/graphicsItems/ErrorBarItem.py +++ /dev/null @@ -1,159 +0,0 @@ -from ..Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject -from .. import getConfigOption -from .. import functions as fn -import numpy as np - -__all__ = ['ErrorBarItem'] - -class ErrorBarItem(GraphicsObject): - def __init__(self, **opts): - """ - All keyword arguments are passed to setData(). - """ - GraphicsObject.__init__(self) - self.opts = dict( - x=None, - y=None, - height=None, - width=None, - top=None, - bottom=None, - left=None, - right=None, - beam=None, - pen=None - ) - self.setVisible(False) - self.setData(**opts) - - def setData(self, **opts): - """ - Update the data in the item. All arguments are optional. - - Valid keyword options are: - x, y, height, width, top, bottom, left, right, beam, pen - - * x and y must be numpy arrays specifying the coordinates of data points. - * height, width, top, bottom, left, right, and beam may be numpy arrays, - single values, or None to disable. All values should be positive. - * top, bottom, left, and right specify the lengths of bars extending - in each direction. - * If height is specified, it overrides top and bottom. - * If width is specified, it overrides left and right. - * beam specifies the width of the beam at the end of each bar. - * pen may be any single argument accepted by pg.mkPen(). - - This method was added in version 0.9.9. For prior versions, use setOpts. - """ - self.opts.update(opts) - self.setVisible(all(self.opts[ax] is not None for ax in ['x', 'y'])) - self.path = None - self.update() - self.prepareGeometryChange() - self.informViewBoundsChanged() - - def setOpts(self, **opts): - # for backward compatibility - self.setData(**opts) - - def drawPath(self): - p = QtGui.QPainterPath() - - x, y = self.opts['x'], self.opts['y'] - if x is None or y is None: - self.path = p - return - - beam = self.opts['beam'] - - height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom'] - if height is not None or top is not None or bottom is not None: - ## draw vertical error bars - if height is not None: - y1 = y - height/2. - y2 = y + height/2. - else: - if bottom is None: - y1 = y - else: - y1 = y - bottom - if top is None: - y2 = y - else: - y2 = y + top - - xs = fn.interweaveArrays(x, x) - y1_y2 = fn.interweaveArrays(y1, y2) - verticalLines = fn.arrayToQPath(xs, y1_y2, connect="pairs") - p.addPath(verticalLines) - - if beam is not None and beam > 0: - x1 = x - beam/2. - x2 = x + beam/2. - - x1_x2 = fn.interweaveArrays(x1, x2) - if height is not None or top is not None: - y2s = fn.interweaveArrays(y2, y2) - topEnds = fn.arrayToQPath(x1_x2, y2s, connect="pairs") - p.addPath(topEnds) - - if height is not None or bottom is not None: - y1s = fn.interweaveArrays(y1, y1) - bottomEnds = fn.arrayToQPath(x1_x2, y1s, connect="pairs") - p.addPath(bottomEnds) - - width, right, left = self.opts['width'], self.opts['right'], self.opts['left'] - if width is not None or right is not None or left is not None: - ## draw vertical error bars - if width is not None: - x1 = x - width/2. - x2 = x + width/2. - else: - if left is None: - x1 = x - else: - x1 = x - left - if right is None: - x2 = x - else: - x2 = x + right - - ys = fn.interweaveArrays(y, y) - x1_x2 = fn.interweaveArrays(x1, x2) - ends = fn.arrayToQPath(x1_x2, ys, connect='pairs') - p.addPath(ends) - - if beam is not None and beam > 0: - y1 = y - beam/2. - y2 = y + beam/2. - y1_y2 = fn.interweaveArrays(y1, y2) - if width is not None or right is not None: - x2s = fn.interweaveArrays(x2, x2) - rightEnds = fn.arrayToQPath(x2s, y1_y2, connect="pairs") - p.addPath(rightEnds) - - if width is not None or left is not None: - x1s = fn.interweaveArrays(x1, x1) - leftEnds = fn.arrayToQPath(x1s, y1_y2, connect="pairs") - p.addPath(leftEnds) - - self.path = p - self.prepareGeometryChange() - - - def paint(self, p, *args): - if self.path is None: - self.drawPath() - pen = self.opts['pen'] - if pen is None: - pen = getConfigOption('foreground') - p.setPen(fn.mkPen(pen)) - p.drawPath(self.path) - - def boundingRect(self): - if self.path is None: - self.drawPath() - return self.path.boundingRect() - - diff --git a/pyqtgraph/graphicsItems/FillBetweenItem.py b/pyqtgraph/graphicsItems/FillBetweenItem.py deleted file mode 100644 index b16be85..0000000 --- a/pyqtgraph/graphicsItems/FillBetweenItem.py +++ /dev/null @@ -1,83 +0,0 @@ -from ..Qt import QtGui -from .. import functions as fn -from .PlotDataItem import PlotDataItem -from .PlotCurveItem import PlotCurveItem - -class FillBetweenItem(QtGui.QGraphicsPathItem): - """ - GraphicsItem filling the space between two PlotDataItems. - """ - def __init__(self, curve1=None, curve2=None, brush=None, pen=None): - QtGui.QGraphicsPathItem.__init__(self) - self.curves = None - if curve1 is not None and curve2 is not None: - self.setCurves(curve1, curve2) - elif curve1 is not None or curve2 is not None: - raise Exception("Must specify two curves to fill between.") - - if brush is not None: - self.setBrush(brush) - self.setPen(pen) - self.updatePath() - - def setBrush(self, *args, **kwds): - QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds)) - - def setPen(self, *args, **kwds): - QtGui.QGraphicsPathItem.setPen(self, fn.mkPen(*args, **kwds)) - - def setCurves(self, curve1, curve2): - """Set the curves to fill between. - - Arguments must be instances of PlotDataItem or PlotCurveItem. - - Added in version 0.9.9 - """ - if self.curves is not None: - for c in self.curves: - try: - c.sigPlotChanged.disconnect(self.curveChanged) - except (TypeError, RuntimeError): - pass - - curves = [curve1, curve2] - for c in curves: - if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem): - raise TypeError("Curves must be PlotDataItem or PlotCurveItem.") - self.curves = curves - curve1.sigPlotChanged.connect(self.curveChanged) - curve2.sigPlotChanged.connect(self.curveChanged) - self.setZValue(min(curve1.zValue(), curve2.zValue())-1) - self.curveChanged() - - def setBrush(self, *args, **kwds): - """Change the fill brush. Acceps the same arguments as pg.mkBrush()""" - QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds)) - - def curveChanged(self): - self.updatePath() - - def updatePath(self): - if self.curves is None: - self.setPath(QtGui.QPainterPath()) - return - paths = [] - for c in self.curves: - if isinstance(c, PlotDataItem): - paths.append(c.curve.getPath()) - elif isinstance(c, PlotCurveItem): - paths.append(c.getPath()) - - path = QtGui.QPainterPath() - transform = QtGui.QTransform() - ps1 = paths[0].toSubpathPolygons(transform) - ps2 = paths[1].toReversed().toSubpathPolygons(transform) - ps2.reverse() - if len(ps1) == 0 or len(ps2) == 0: - self.setPath(QtGui.QPainterPath()) - return - - - for p1, p2 in zip(ps1, ps2): - path.addPolygon(p1 + p2) - self.setPath(path) diff --git a/pyqtgraph/graphicsItems/GradientEditorItem.py b/pyqtgraph/graphicsItems/GradientEditorItem.py deleted file mode 100644 index 50d38a0..0000000 --- a/pyqtgraph/graphicsItems/GradientEditorItem.py +++ /dev/null @@ -1,1009 +0,0 @@ -# -*- coding: utf-8 -*- -import operator -import weakref -import numpy as np -from ..Qt import QtGui, QtCore -from .. import functions as fn -from .GraphicsObject import GraphicsObject -from .GraphicsWidget import GraphicsWidget -from ..widgets.SpinBox import SpinBox -from collections import OrderedDict -from ..colormap import ColorMap - -translate = QtCore.QCoreApplication.translate - -__all__ = ['TickSliderItem', 'GradientEditorItem'] - -Gradients = OrderedDict([ - ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), - ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), - ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), - ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), - ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), - ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), - ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), - ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), - # Perceptually uniform sequential colormaps from Matplotlib 2.0 - ('viridis', {'ticks': [(0.0, (68, 1, 84, 255)), (0.25, (58, 82, 139, 255)), (0.5, (32, 144, 140, 255)), (0.75, (94, 201, 97, 255)), (1.0, (253, 231, 36, 255))], 'mode': 'rgb'}), - ('inferno', {'ticks': [(0.0, (0, 0, 3, 255)), (0.25, (87, 15, 109, 255)), (0.5, (187, 55, 84, 255)), (0.75, (249, 142, 8, 255)), (1.0, (252, 254, 164, 255))], 'mode': 'rgb'}), - ('plasma', {'ticks': [(0.0, (12, 7, 134, 255)), (0.25, (126, 3, 167, 255)), (0.5, (203, 71, 119, 255)), (0.75, (248, 149, 64, 255)), (1.0, (239, 248, 33, 255))], 'mode': 'rgb'}), - ('magma', {'ticks': [(0.0, (0, 0, 3, 255)), (0.25, (80, 18, 123, 255)), (0.5, (182, 54, 121, 255)), (0.75, (251, 136, 97, 255)), (1.0, (251, 252, 191, 255))], 'mode': 'rgb'}), -]) - -def addGradientListToDocstring(): - """Decorator to add list of current pre-defined gradients to the end of a function docstring.""" - def dec(fn): - if fn.__doc__ is not None: - fn.__doc__ = fn.__doc__ + str(list(Gradients.keys())).strip('[').strip(']') - return fn - return dec - - - -class TickSliderItem(GraphicsWidget): - ## public class - """**Bases:** :class:`GraphicsWidget ` - - A rectangular item with tick marks along its length that can (optionally) be moved by the user.""" - - sigTicksChanged = QtCore.Signal(object) - sigTicksChangeFinished = QtCore.Signal(object) - - def __init__(self, orientation='bottom', allowAdd=True, allowRemove=True, **kargs): - """ - ============== ================================================================================= - **Arguments:** - orientation Set the orientation of the gradient. Options are: 'left', 'right' - 'top', and 'bottom'. - allowAdd Specifies whether the user can add ticks. - allowRemove Specifies whether the user can remove new ticks. - tickPen Default is white. Specifies the color of the outline of the ticks. - Can be any of the valid arguments for :func:`mkPen ` - ============== ================================================================================= - """ - ## public - GraphicsWidget.__init__(self) - self.orientation = orientation - self.length = 100 - self.tickSize = 15 - self.ticks = {} - self.maxDim = 20 - self.allowAdd = allowAdd - self.allowRemove = allowRemove - if 'tickPen' in kargs: - self.tickPen = fn.mkPen(kargs['tickPen']) - else: - self.tickPen = fn.mkPen('w') - - self.orientations = { - 'left': (90, 1, 1), - 'right': (90, 1, 1), - 'top': (0, 1, -1), - 'bottom': (0, 1, 1) - } - - self.setOrientation(orientation) - #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) - #self.setBackgroundRole(QtGui.QPalette.NoRole) - #self.setMouseTracking(True) - - #def boundingRect(self): - #return self.mapRectFromParent(self.geometry()).normalized() - - #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. - #p = QtGui.QPainterPath() - #p.addRect(self.boundingRect()) - #return p - - def paint(self, p, opt, widget): - #p.setPen(fn.mkPen('g', width=3)) - #p.drawRect(self.boundingRect()) - return - - def keyPressEvent(self, ev): - ev.ignore() - - def setMaxDim(self, mx=None): - if mx is None: - mx = self.maxDim - else: - self.maxDim = mx - - if self.orientation in ['bottom', 'top']: - self.setFixedHeight(mx) - self.setMaximumWidth(16777215) - else: - self.setFixedWidth(mx) - self.setMaximumHeight(16777215) - - - def setOrientation(self, orientation): - ## public - """Set the orientation of the TickSliderItem. - - ============== =================================================================== - **Arguments:** - orientation Options are: 'left', 'right', 'top', 'bottom' - The orientation option specifies which side of the slider the - ticks are on, as well as whether the slider is vertical ('right' - and 'left') or horizontal ('top' and 'bottom'). - ============== =================================================================== - """ - self.orientation = orientation - self.setMaxDim() - self.resetTransform() - ort = orientation - if ort == 'top': - transform = QtGui.QTransform.fromScale(1, -1) - transform.translate(0, -self.height()) - self.setTransform(transform) - elif ort == 'left': - transform = QtGui.QTransform() - transform.rotate(270) - transform.scale(1, -1) - transform.translate(-self.height(), -self.maxDim) - self.setTransform(transform) - elif ort == 'right': - transform = QtGui.QTransform() - transform.rotate(270) - transform.translate(-self.height(), 0) - self.setTransform(transform) - elif ort != 'bottom': - raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort)) - - tr = QtGui.QTransform.fromTranslate(self.tickSize/2., 0) - self.setTransform(tr, True) - - def addTick(self, x, color=None, movable=True, finish=True): - ## public - """ - Add a tick to the item. - - ============== ================================================================== - **Arguments:** - x Position where tick should be added. - color Color of added tick. If color is not specified, the color will be - white. - movable Specifies whether the tick is movable with the mouse. - ============== ================================================================== - """ - - if color is None: - color = QtGui.QColor(255,255,255) - tick = Tick([x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen, removeAllowed=self.allowRemove) - self.ticks[tick] = x - tick.setParentItem(self) - - tick.sigMoving.connect(self.tickMoved) - tick.sigMoved.connect(self.tickMoveFinished) - tick.sigClicked.connect(self.tickClicked) - - self.sigTicksChanged.emit(self) - - if finish: - self.sigTicksChangeFinished.emit(self) - - return tick - - def removeTick(self, tick, finish=True): - ## public - """ - Removes the specified tick. - """ - del self.ticks[tick] - tick.setParentItem(None) - if self.scene() is not None: - self.scene().removeItem(tick) - - self.sigTicksChanged.emit(self) - - if finish: - self.sigTicksChangeFinished.emit(self) - - def tickMoved(self, tick, pos): - #print "tick changed" - ## Correct position of tick if it has left bounds. - newX = min(max(0, pos.x()), self.length) - pos.setX(newX) - tick.setPos(pos) - self.ticks[tick] = float(newX) / self.length - - self.sigTicksChanged.emit(self) - - def tickMoveFinished(self, tick): - self.sigTicksChangeFinished.emit(self) - - def tickClicked(self, tick, ev): - if ev.button() == QtCore.Qt.RightButton and tick.removeAllowed: - self.removeTick(tick) - - def widgetLength(self): - if self.orientation in ['bottom', 'top']: - return self.width() - else: - return self.height() - - def resizeEvent(self, ev): - wlen = max(40, self.widgetLength()) - self.setLength(wlen-self.tickSize-2) - self.setOrientation(self.orientation) - #bounds = self.scene().itemsBoundingRect() - #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) - #bounds.setRight(max(self.length + self.tickSize, bounds.right())) - #self.setSceneRect(bounds) - #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) - - def setLength(self, newLen): - #private - for t, x in list(self.ticks.items()): - t.setPos(x * newLen + 1, t.pos().y()) - self.length = float(newLen) - - #def mousePressEvent(self, ev): - #QtGui.QGraphicsView.mousePressEvent(self, ev) - #self.ignoreRelease = False - #for i in self.items(ev.pos()): - #if isinstance(i, Tick): - #self.ignoreRelease = True - #break - ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks - ##self.ignoreRelease = True - - #def mouseReleaseEvent(self, ev): - #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) - #if self.ignoreRelease: - #return - - #pos = self.mapToScene(ev.pos()) - - #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: - #if pos.x() < 0 or pos.x() > self.length: - #return - #if pos.y() < 0 or pos.y() > self.tickSize: - #return - #pos.setX(min(max(pos.x(), 0), self.length)) - #self.addTick(pos.x()/self.length) - #elif ev.button() == QtCore.Qt.RightButton: - #self.showMenu(ev) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: - pos = ev.pos() - if pos.x() < 0 or pos.x() > self.length: - return - if pos.y() < 0 or pos.y() > self.tickSize: - return - pos.setX(min(max(pos.x(), 0), self.length)) - self.addTick(pos.x()/self.length) - elif ev.button() == QtCore.Qt.RightButton: - self.showMenu(ev) - - #if ev.button() == QtCore.Qt.RightButton: - #if self.moving: - #ev.accept() - #self.setPos(self.startPosition) - #self.moving = False - #self.sigMoving.emit(self) - #self.sigMoved.emit(self) - #else: - #pass - #self.view().tickClicked(self, ev) - ###remove - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.RightButton) - ## show ghost tick - #self.currentPen = fn.mkPen(255, 0,0) - #else: - #self.currentPen = self.pen - #self.update() - - def showMenu(self, ev): - pass - - def setTickColor(self, tick, color): - """Set the color of the specified tick. - - ============== ================================================================== - **Arguments:** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted to change the middle tick, the index would be 1. - color The color to make the tick. Can be any argument that is valid for - :func:`mkBrush ` - ============== ================================================================== - """ - tick = self.getTick(tick) - tick.color = color - tick.update() - #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) - - self.sigTicksChanged.emit(self) - self.sigTicksChangeFinished.emit(self) - - def setTickValue(self, tick, val): - ## public - """ - Set the position (along the slider) of the tick. - - ============== ================================================================== - **Arguments:** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted to change the middle tick, the index would be 1. - val The desired position of the tick. If val is < 0, position will be - set to 0. If val is > 1, position will be set to 1. - ============== ================================================================== - """ - tick = self.getTick(tick) - val = min(max(0.0, val), 1.0) - x = val * self.length - pos = tick.pos() - pos.setX(x) - tick.setPos(pos) - self.ticks[tick] = val - - self.update() - self.sigTicksChanged.emit(self) - self.sigTicksChangeFinished.emit(self) - - def tickValue(self, tick): - ## public - """Return the value (from 0.0 to 1.0) of the specified tick. - - ============== ================================================================== - **Arguments:** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted the value of the middle tick, the index would be 1. - ============== ================================================================== - """ - tick = self.getTick(tick) - return self.ticks[tick] - - def getTick(self, tick): - ## public - """Return the Tick object at the specified index. - - ============== ================================================================== - **Arguments:** - tick An integer corresponding to the index of the desired tick. If the - argument is not an integer it will be returned unchanged. - ============== ================================================================== - """ - if type(tick) is int: - tick = self.listTicks()[tick][0] - return tick - - #def mouseMoveEvent(self, ev): - #QtGui.QGraphicsView.mouseMoveEvent(self, ev) - - def listTicks(self): - """Return a sorted list of all the Tick objects on the slider.""" - ## public - ticks = sorted(self.ticks.items(), key=operator.itemgetter(1)) - return ticks - - -class GradientEditorItem(TickSliderItem): - """ - **Bases:** :class:`TickSliderItem ` - - An item that can be used to define a color gradient. Implements common pre-defined gradients that are - customizable by the user. :class: `GradientWidget ` provides a widget - with a GradientEditorItem that can be added to a GUI. - - ================================ =========================================================== - **Signals:** - sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal - is emitted in real time while ticks are being dragged or - colors are being changed. - sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing. - ================================ =========================================================== - - """ - - sigGradientChanged = QtCore.Signal(object) - sigGradientChangeFinished = QtCore.Signal(object) - - def __init__(self, *args, **kargs): - """ - Create a new GradientEditorItem. - All arguments are passed to :func:`TickSliderItem.__init__ ` - - =============== ================================================================================= - **Arguments:** - orientation Set the orientation of the gradient. Options are: 'left', 'right' - 'top', and 'bottom'. - allowAdd Default is True. Specifies whether ticks can be added to the item. - tickPen Default is white. Specifies the color of the outline of the ticks. - Can be any of the valid arguments for :func:`mkPen ` - =============== ================================================================================= - """ - self.currentTick = None - self.currentTickColor = None - self.rectSize = 15 - self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) - self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) - self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) - self.colorMode = 'rgb' - - TickSliderItem.__init__(self, *args, **kargs) - - self.colorDialog = QtGui.QColorDialog() - self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) - self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) - - self.colorDialog.currentColorChanged.connect(self.currentColorChanged) - self.colorDialog.rejected.connect(self.currentColorRejected) - self.colorDialog.accepted.connect(self.currentColorAccepted) - - self.backgroundRect.setParentItem(self) - self.gradRect.setParentItem(self) - - self.setMaxDim(self.rectSize + self.tickSize) - - self.rgbAction = QtGui.QAction(translate("GradiantEditorItem", 'RGB'), self) - self.rgbAction.setCheckable(True) - self.rgbAction.triggered.connect(self._setColorModeToRGB) - self.hsvAction = QtGui.QAction(translate("GradiantEditorItem", 'HSV'), self) - self.hsvAction.setCheckable(True) - self.hsvAction.triggered.connect(self._setColorModeToHSV) - - self.menu = QtGui.QMenu() - - ## build context menu of gradients - l = self.length - self.length = 100 - global Gradients - for g in Gradients: - px = QtGui.QPixmap(100, 15) - p = QtGui.QPainter(px) - self.restoreState(Gradients[g]) - grad = self.getGradient() - brush = QtGui.QBrush(grad) - p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) - p.end() - label = QtGui.QLabel() - label.setPixmap(px) - label.setContentsMargins(1, 1, 1, 1) - labelName = QtGui.QLabel(g) - hbox = QtGui.QHBoxLayout() - hbox.addWidget(labelName) - hbox.addWidget(label) - widget = QtGui.QWidget() - widget.setLayout(hbox) - act = QtGui.QWidgetAction(self) - act.setDefaultWidget(widget) - act.triggered.connect(self.contextMenuClicked) - act.name = g - self.menu.addAction(act) - self.length = l - self.menu.addSeparator() - self.menu.addAction(self.rgbAction) - self.menu.addAction(self.hsvAction) - - - for t in list(self.ticks.keys()): - self.removeTick(t) - self.addTick(0, QtGui.QColor(0,0,0), True) - self.addTick(1, QtGui.QColor(255,0,0), True) - self.setColorMode('rgb') - self.updateGradient() - self.linkedGradients = {} - - self.sigTicksChanged.connect(self._updateGradientIgnoreArgs) - self.sigTicksChangeFinished.connect(self.sigGradientChangeFinished.emit) - - def showTicks(self, show=True): - for tick in self.ticks.keys(): - if show: - tick.show() - orig = getattr(self, '_allowAdd_backup', None) - if orig: - self.allowAdd = orig - else: - self._allowAdd_backup = self.allowAdd - self.allowAdd = False #block tick creation - tick.hide() - - def setOrientation(self, orientation): - ## public - """ - Set the orientation of the GradientEditorItem. - - ============== =================================================================== - **Arguments:** - orientation Options are: 'left', 'right', 'top', 'bottom' - The orientation option specifies which side of the gradient the - ticks are on, as well as whether the gradient is vertical ('right' - and 'left') or horizontal ('top' and 'bottom'). - ============== =================================================================== - """ - TickSliderItem.setOrientation(self, orientation) - tr = QtGui.QTransform.fromTranslate(0, self.rectSize) - self.setTransform(tr, True) - - def showMenu(self, ev): - #private - self.menu.popup(ev.screenPos().toQPoint()) - - def contextMenuClicked(self, b=None): - #private - #global Gradients - act = self.sender() - self.loadPreset(act.name) - - @addGradientListToDocstring() - def loadPreset(self, name): - """ - Load a predefined gradient. Currently defined gradients are: - """## TODO: provide image with names of defined gradients - - #global Gradients - self.restoreState(Gradients[name]) - - def setColorMode(self, cm): - """ - Set the color mode for the gradient. Options are: 'hsv', 'rgb' - - """ - - ## public - if cm not in ['rgb', 'hsv']: - raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) - - try: - self.rgbAction.blockSignals(True) - self.hsvAction.blockSignals(True) - self.rgbAction.setChecked(cm == 'rgb') - self.hsvAction.setChecked(cm == 'hsv') - finally: - self.rgbAction.blockSignals(False) - self.hsvAction.blockSignals(False) - self.colorMode = cm - - self.sigTicksChanged.emit(self) - self.sigGradientChangeFinished.emit(self) - - def _setColorModeToRGB(self): - self.setColorMode("rgb") - - def _setColorModeToHSV(self): - self.setColorMode("hsv") - - def colorMap(self): - """Return a ColorMap object representing the current state of the editor.""" - if self.colorMode == 'hsv': - raise NotImplementedError('hsv colormaps not yet supported') - pos = [] - color = [] - for t,x in self.listTicks(): - pos.append(x) - c = t.color - color.append([c.red(), c.green(), c.blue(), c.alpha()]) - return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte)) - - def updateGradient(self): - #private - self.gradient = self.getGradient() - self.gradRect.setBrush(QtGui.QBrush(self.gradient)) - self.sigGradientChanged.emit(self) - - def _updateGradientIgnoreArgs(self, *args, **kwargs): - self.updateGradient() - - def setLength(self, newLen): - #private (but maybe public) - TickSliderItem.setLength(self, newLen) - self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize) - self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize) - self.sigTicksChanged.emit(self) - - def currentColorChanged(self, color): - #private - if color.isValid() and self.currentTick is not None: - self.setTickColor(self.currentTick, color) - - def currentColorRejected(self): - #private - self.setTickColor(self.currentTick, self.currentTickColor) - - def currentColorAccepted(self): - self.sigGradientChangeFinished.emit(self) - - def tickClicked(self, tick, ev): - #private - if ev.button() == QtCore.Qt.LeftButton: - self.raiseColorDialog(tick) - elif ev.button() == QtCore.Qt.RightButton: - self.raiseTickContextMenu(tick, ev) - - def raiseColorDialog(self, tick): - if not tick.colorChangeAllowed: - return - self.currentTick = tick - self.currentTickColor = tick.color - self.colorDialog.setCurrentColor(tick.color) - self.colorDialog.open() - - def raiseTickContextMenu(self, tick, ev): - self.tickMenu = TickMenu(tick, self) - self.tickMenu.popup(ev.screenPos().toQPoint()) - - def tickMoveFinished(self, tick): - self.sigGradientChangeFinished.emit(self) - - def getGradient(self): - """Return a QLinearGradient object.""" - g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) - if self.colorMode == 'rgb': - ticks = self.listTicks() - g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) - elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop - ticks = self.listTicks() - stops = [] - stops.append((ticks[0][1], ticks[0][0].color)) - for i in range(1,len(ticks)): - x1 = ticks[i-1][1] - x2 = ticks[i][1] - dx = (x2-x1) / 10. - for j in range(1,10): - x = x1 + dx*j - stops.append((x, self.getColor(x))) - stops.append((x2, self.getColor(x2))) - g.setStops(stops) - return g - - def getColor(self, x, toQColor=True): - """ - Return a color for a given value. - - ============== ================================================================== - **Arguments:** - x Value (position on gradient) of requested color. - toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. - ============== ================================================================== - """ - ticks = self.listTicks() - if x <= ticks[0][1]: - c = ticks[0][0].color - if toQColor: - return QtGui.QColor(c) # always copy colors before handing them out - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - if x >= ticks[-1][1]: - c = ticks[-1][0].color - if toQColor: - return QtGui.QColor(c) # always copy colors before handing them out - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - - x2 = ticks[0][1] - for i in range(1,len(ticks)): - x1 = x2 - x2 = ticks[i][1] - if x1 <= x and x2 >= x: - break - - dx = (x2-x1) - if dx == 0: - f = 0. - else: - f = (x-x1) / dx - c1 = ticks[i-1][0].color - c2 = ticks[i][0].color - if self.colorMode == 'rgb': - r = c1.red() * (1.-f) + c2.red() * f - g = c1.green() * (1.-f) + c2.green() * f - b = c1.blue() * (1.-f) + c2.blue() * f - a = c1.alpha() * (1.-f) + c2.alpha() * f - if toQColor: - return QtGui.QColor(int(r), int(g), int(b), int(a)) - else: - return (r,g,b,a) - elif self.colorMode == 'hsv': - h1,s1,v1,_ = c1.getHsv() - h2,s2,v2,_ = c2.getHsv() - h = h1 * (1.-f) + h2 * f - s = s1 * (1.-f) + s2 * f - v = v1 * (1.-f) + v2 * f - c = QtGui.QColor() - c.setHsv(*map(int, [h,s,v])) - if toQColor: - return c - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - - def getLookupTable(self, nPts, alpha=None): - """ - Return an RGB(A) lookup table (ndarray). - - ============== ============================================================================ - **Arguments:** - nPts The number of points in the returned lookup table. - alpha True, False, or None - Specifies whether or not alpha values are included - in the table.If alpha is None, alpha will be automatically determined. - ============== ============================================================================ - """ - if alpha is None: - alpha = self.usesAlpha() - if alpha: - table = np.empty((nPts,4), dtype=np.ubyte) - else: - table = np.empty((nPts,3), dtype=np.ubyte) - - for i in range(nPts): - x = float(i)/(nPts-1) - color = self.getColor(x, toQColor=False) - table[i] = color[:table.shape[1]] - - return table - - def usesAlpha(self): - """Return True if any ticks have an alpha < 255""" - - ticks = self.listTicks() - for t in ticks: - if t[0].color.alpha() < 255: - return True - - return False - - def isLookupTrivial(self): - """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" - ticks = self.listTicks() - if len(ticks) != 2: - return False - if ticks[0][1] != 0.0 or ticks[1][1] != 1.0: - return False - c1 = fn.colorTuple(ticks[0][0].color) - c2 = fn.colorTuple(ticks[1][0].color) - if c1 != (0,0,0,255) or c2 != (255,255,255,255): - return False - return True - - def addTick(self, x, color=None, movable=True, finish=True): - """ - Add a tick to the gradient. Return the tick. - - ============== ================================================================== - **Arguments:** - x Position where tick should be added. - color Color of added tick. If color is not specified, the color will be - the color of the gradient at the specified position. - movable Specifies whether the tick is movable with the mouse. - ============== ================================================================== - """ - - if color is None: - color = self.getColor(x) - t = TickSliderItem.addTick(self, x, color=color, movable=movable, finish=finish) - t.colorChangeAllowed = True - - return t - - def saveState(self): - """ - Return a dictionary with parameters for rebuilding the gradient. Keys will include: - - - 'mode': hsv or rgb - - 'ticks': a list of tuples (pos, (r,g,b,a)) - """ - ## public - ticks = [] - for t in self.ticks: - c = t.color - ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) - state = {'mode': self.colorMode, - 'ticks': ticks, - 'ticksVisible': next(iter(self.ticks)).isVisible()} - return state - - def restoreState(self, state): - """ - Restore the gradient specified in state. - - ============== ==================================================================== - **Arguments:** - state A dictionary with same structure as those returned by - :func:`saveState ` - - Keys must include: - - - 'mode': hsv or rgb - - 'ticks': a list of tuples (pos, (r,g,b,a)) - ============== ==================================================================== - """ - ## public - - # Mass edit ticks without graphics update - signalsBlocked = self.blockSignals(True) - - self.setColorMode(state['mode']) - for t in list(self.ticks.keys()): - self.removeTick(t, finish=False) - for t in state['ticks']: - c = QtGui.QColor(*t[1]) - self.addTick(t[0], c, finish=False) - self.showTicks( state.get('ticksVisible', - next(iter(self.ticks)).isVisible()) ) - - # Close with graphics update - self.blockSignals(signalsBlocked) - self.sigTicksChanged.emit(self) - self.sigGradientChangeFinished.emit(self) - - def setColorMap(self, cm): - # Mass edit ticks without graphics update - signalsBlocked = self.blockSignals(True) - - self.setColorMode('rgb') - for t in list(self.ticks.keys()): - self.removeTick(t, finish=False) - colors = cm.getColors(mode='qcolor') - for i in range(len(cm.pos)): - x = cm.pos[i] - c = colors[i] - self.addTick(x, c, finish=False) - - # Close with graphics update - self.blockSignals(signalsBlocked) - self.sigTicksChanged.emit(self) - self.sigGradientChangeFinished.emit(self) - - def linkGradient(self, slaveGradient, connect=True): - if connect: - fn = lambda g, slave=slaveGradient:slave.restoreState( - g.saveState()) - self.linkedGradients[id(slaveGradient)] = fn - self.sigGradientChanged.connect(fn) - self.sigGradientChanged.emit(self) - else: - fn = self.linkedGradients.get(id(slaveGradient), None) - if fn: - self.sigGradientChanged.disconnect(fn) - - -class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsObject instead results in - ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 - ## private class - - # When making Tick a subclass of QtGui.QGraphicsObject as origin, - # ..GraphicsScene.items(self, *args) will get Tick object as a - # class of QtGui.QMultimediaWidgets.QGraphicsVideoItem in python2.7-PyQt5(5.4.0) - - sigMoving = QtCore.Signal(object, object) - sigMoved = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - - def __init__(self, pos, color, movable=True, scale=10, pen='w', removeAllowed=True): - self.movable = movable - self.moving = False - self.scale = scale - self.color = color - self.pen = fn.mkPen(pen) - self.hoverPen = fn.mkPen(255,255,0) - self.currentPen = self.pen - self.removeAllowed = removeAllowed - self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) - self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) - self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) - self.pg.closeSubpath() - - QtGui.QGraphicsWidget.__init__(self) - self.setPos(pos[0], pos[1]) - if self.movable: - self.setZValue(1) - else: - self.setZValue(0) - - def boundingRect(self): - return self.pg.boundingRect() - - def shape(self): - return self.pg - - def paint(self, p, *args): - p.setRenderHints(QtGui.QPainter.Antialiasing) - p.fillPath(self.pg, fn.mkBrush(self.color)) - - p.setPen(self.currentPen) - p.drawPath(self.pg) - - - def mouseDragEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self.moving = True - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.startPosition = self.pos() - ev.accept() - - if not self.moving: - return - - newPos = self.cursorOffset + self.mapToParent(ev.pos()) - newPos.setY(self.pos().y()) - - self.setPos(newPos) - self.sigMoving.emit(self, newPos) - if ev.isFinish(): - self.moving = False - self.sigMoved.emit(self) - - def mouseClickEvent(self, ev): - ev.accept() - if ev.button() == QtCore.Qt.RightButton and self.moving: - self.setPos(self.startPosition) - self.moving = False - self.sigMoving.emit(self, self.startPosition) - self.sigMoved.emit(self) - else: - self.sigClicked.emit(self, ev) - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) - ev.acceptClicks(QtCore.Qt.RightButton) - self.currentPen = self.hoverPen - else: - self.currentPen = self.pen - self.update() - - -class TickMenu(QtGui.QMenu): - - def __init__(self, tick, sliderItem): - QtGui.QMenu.__init__(self) - - self.tick = weakref.ref(tick) - self.sliderItem = weakref.ref(sliderItem) - - self.removeAct = self.addAction(translate("GradientEditorItem", "Remove Tick"), lambda: self.sliderItem().removeTick(tick)) - if (not self.tick().removeAllowed) or len(self.sliderItem().ticks) < 3: - self.removeAct.setEnabled(False) - - positionMenu = self.addMenu(translate("GradientEditorItem", "Set Position")) - w = QtGui.QWidget() - l = QtGui.QGridLayout() - w.setLayout(l) - - value = sliderItem.tickValue(tick) - self.fracPosSpin = SpinBox() - self.fracPosSpin.setOpts(value=value, bounds=(0.0, 1.0), step=0.01, decimals=2) - #self.dataPosSpin = SpinBox(value=dataVal) - #self.dataPosSpin.setOpts(decimals=3, siPrefix=True) - - l.addWidget(QtGui.QLabel(f"{translate('GradiantEditorItem', 'Position')}:"), 0,0) - l.addWidget(self.fracPosSpin, 0, 1) - #l.addWidget(QtGui.QLabel("Position (data units):"), 1, 0) - #l.addWidget(self.dataPosSpin, 1,1) - - #if self.sliderItem().dataParent is None: - # self.dataPosSpin.setEnabled(False) - - a = QtGui.QWidgetAction(self) - a.setDefaultWidget(w) - positionMenu.addAction(a) - - self.fracPosSpin.sigValueChanging.connect(self.fractionalValueChanged) - #self.dataPosSpin.valueChanged.connect(self.dataValueChanged) - - colorAct = self.addAction(translate("Context Menu", "Set Color"), lambda: self.sliderItem().raiseColorDialog(self.tick())) - if not self.tick().colorChangeAllowed: - colorAct.setEnabled(False) - - def fractionalValueChanged(self, x): - self.sliderItem().setTickValue(self.tick(), self.fracPosSpin.value()) - #if self.sliderItem().dataParent is not None: - # self.dataPosSpin.blockSignals(True) - # self.dataPosSpin.setValue(self.sliderItem().tickDataValue(self.tick())) - # self.dataPosSpin.blockSignals(False) - - #def dataValueChanged(self, val): - # self.sliderItem().setTickValue(self.tick(), val, dataUnits=True) - # self.fracPosSpin.blockSignals(True) - # self.fracPosSpin.setValue(self.sliderItem().tickValue(self.tick())) - # self.fracPosSpin.blockSignals(False) diff --git a/pyqtgraph/graphicsItems/GradientLegend.py b/pyqtgraph/graphicsItems/GradientLegend.py deleted file mode 100644 index 649a23b..0000000 --- a/pyqtgraph/graphicsItems/GradientLegend.py +++ /dev/null @@ -1,114 +0,0 @@ -from ..Qt import QtGui, QtCore -from .UIGraphicsItem import * -from .. import functions as fn - -__all__ = ['GradientLegend'] - -class GradientLegend(UIGraphicsItem): - """ - Draws a color gradient rectangle along with text labels denoting the value at specific - points along the gradient. - """ - - def __init__(self, size, offset): - self.size = size - self.offset = offset - UIGraphicsItem.__init__(self) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) - self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) - self.labels = {'max': 1, 'min': 0} - self.gradient = QtGui.QLinearGradient() - self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) - self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) - - def setGradient(self, g): - self.gradient = g - self.update() - - def setIntColorScale(self, minVal, maxVal, *args, **kargs): - colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] - g = QtGui.QLinearGradient() - for i in range(len(colors)): - x = float(i)/len(colors) - g.setColorAt(x, colors[i]) - self.setGradient(g) - if 'labels' not in kargs: - self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) - else: - self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) - - def setLabels(self, l): - """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" - self.labels = l - self.update() - - def paint(self, p, opt, widget): - UIGraphicsItem.paint(self, p, opt, widget) - rect = self.boundingRect() ## Boundaries of visible area in scene coords. - unit = self.pixelSize() ## Size of one view pixel in scene coords. - if unit[0] is None: - return - - ## Have to scale painter so that text and gradients are correct size and not upside down - p.scale(unit[0], -unit[1]) - - ## determine max width of all labels - labelWidth = 0 - labelHeight = 0 - for k in self.labels: - b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) - labelWidth = max(labelWidth, b.width()) - labelHeight = max(labelHeight, b.height()) - - textPadding = 2 # in px - - xR = rect.right() / unit[0] - xL = rect.left() / unit[0] - yB = -(rect.top() / unit[1]) - yT = -(rect.bottom() / unit[1]) - - # coordinates describe edges of text and bar, additional margins will be added for background - if self.offset[0] < 0: - x3 = xR + self.offset[0] # right edge from right edge of view, offset is negative! - x2 = x3 - labelWidth - 2*textPadding # right side of color bar - x1 = x2 - self.size[0] # left side of color bar - else: - x1 = xL + self.offset[0] # left edge from left edge of view - x2 = x1 + self.size[0] - x3 = x2 + labelWidth + 2*textPadding # leave room for 2x textpadding between bar and text - if self.offset[1] < 0: - y2 = yB + self.offset[1] # bottom edge from bottom of view, offset is negative! - y1 = y2 - self.size[1] - else: - y1 = yT + self.offset[1] # top edge from top of view - y2 = y1 + self.size[1] - self.b = [x1,x2,x3,y1,y2,labelWidth] - - ## Draw background - p.setPen(self.pen) - p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) - rect = QtCore.QRectF( - QtCore.QPointF(x1 - textPadding, y1-labelHeight/2 - textPadding), # extra left/top padding - QtCore.QPointF(x3 + textPadding, y2+labelHeight/2 + textPadding) # extra bottom/right padding - ) - p.drawRect(rect) - - ## Draw color bar - self.gradient.setStart(0, y1) - self.gradient.setFinalStop(0, y2) - p.setBrush(self.gradient) - rect = QtCore.QRectF( - QtCore.QPointF(x1, y1), - QtCore.QPointF(x2, y2) - ) - p.drawRect(rect) - - ## draw labels - p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) - tx = x2 + 2 * textPadding # margin between bar and text - lh = labelHeight - lw = labelWidth - for k in self.labels: - y = y1 + self.labels[k] * (y2-y1) - p.drawText(QtCore.QRectF(tx, y - lh/2, lw, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) diff --git a/pyqtgraph/graphicsItems/GraphItem.py b/pyqtgraph/graphicsItems/GraphItem.py deleted file mode 100644 index 9c4976f..0000000 --- a/pyqtgraph/graphicsItems/GraphItem.py +++ /dev/null @@ -1,144 +0,0 @@ -from .. import functions as fn -from .GraphicsObject import GraphicsObject -from .ScatterPlotItem import ScatterPlotItem -from ..Qt import QtGui, QtCore -import numpy as np -from .. import getConfigOption - -__all__ = ['GraphItem'] - - -class GraphItem(GraphicsObject): - """A GraphItem displays graph information as - a set of nodes connected by lines (as in 'graph theory', not 'graphics'). - Useful for drawing networks, trees, etc. - """ - - def __init__(self, **kwds): - GraphicsObject.__init__(self) - self.scatter = ScatterPlotItem() - self.scatter.setParentItem(self) - self.adjacency = None - self.pos = None - self.picture = None - self.pen = 'default' - self.setData(**kwds) - - def setData(self, **kwds): - """ - Change the data displayed by the graph. - - ============== ======================================================================= - **Arguments:** - pos (N,2) array of the positions of each node in the graph. - adj (M,2) array of connection data. Each row contains indexes - of two nodes that are connected or None to hide lines - pen The pen to use when drawing lines between connected - nodes. May be one of: - - * QPen - * a single argument to pass to pg.mkPen - * a record array of length M - with fields (red, green, blue, alpha, width). Note - that using this option may have a significant performance - cost. - * None (to disable connection drawing) - * 'default' to use the default foreground color. - - symbolPen The pen(s) used for drawing nodes. - symbolBrush The brush(es) used for drawing nodes. - ``**opts`` All other keyword arguments are given to - :func:`ScatterPlotItem.setData() ` - to affect the appearance of nodes (symbol, size, brush, - etc.) - ============== ======================================================================= - """ - if 'adj' in kwds: - self.adjacency = kwds.pop('adj') - if hasattr(self.adjacency, '__len__') and len(self.adjacency) == 0: - self.adjacency = None - elif self.adjacency is not None and self.adjacency.dtype.kind not in 'iu': - raise Exception("adjacency must be None or an array of either int or unsigned type.") - self._update() - if 'pos' in kwds: - self.pos = kwds['pos'] - self._update() - if 'pen' in kwds: - self.setPen(kwds.pop('pen')) - self._update() - - if 'symbolPen' in kwds: - kwds['pen'] = kwds.pop('symbolPen') - if 'symbolBrush' in kwds: - kwds['brush'] = kwds.pop('symbolBrush') - self.scatter.setData(**kwds) - self.informViewBoundsChanged() - - def _update(self): - self.picture = None - self.prepareGeometryChange() - self.update() - - def setPen(self, *args, **kwargs): - """ - Set the pen used to draw graph lines. - May be: - - * None to disable line drawing - * Record array with fields (red, green, blue, alpha, width) - * Any set of arguments and keyword arguments accepted by - :func:`mkPen `. - * 'default' to use the default foreground color. - """ - if len(args) == 1 and len(kwargs) == 0: - self.pen = args[0] - else: - self.pen = fn.mkPen(*args, **kwargs) - self.picture = None - self.update() - - def generatePicture(self): - self.picture = QtGui.QPicture() - if self.pen is None or self.pos is None or self.adjacency is None: - return - - p = QtGui.QPainter(self.picture) - try: - pts = self.pos[self.adjacency] - pen = self.pen - if isinstance(pen, np.ndarray): - lastPen = None - for i in range(pts.shape[0]): - pen = self.pen[i] - if np.any(pen != lastPen): - lastPen = pen - if pen.dtype.fields is None: - p.setPen(fn.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) - else: - p.setPen(fn.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) - p.drawLine(QtCore.QPointF(*pts[i][0]), QtCore.QPointF(*pts[i][1])) - else: - if pen == 'default': - pen = getConfigOption('foreground') - p.setPen(fn.mkPen(pen)) - pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) - path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') - p.drawPath(path) - finally: - p.end() - - def paint(self, p, *args): - if self.picture == None: - self.generatePicture() - if getConfigOption('antialias') is True: - p.setRenderHint(p.Antialiasing) - self.picture.play(p) - - def boundingRect(self): - return self.scatter.boundingRect() - - def dataBounds(self, *args, **kwds): - return self.scatter.dataBounds(*args, **kwds) - - def pixelPadding(self): - return self.scatter.pixelPadding() diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/pyqtgraph/graphicsItems/GraphicsItem.py deleted file mode 100644 index 61f48a4..0000000 --- a/pyqtgraph/graphicsItems/GraphicsItem.py +++ /dev/null @@ -1,618 +0,0 @@ -import warnings -from collections import OrderedDict -from functools import reduce -from ..Qt import QtGui, QtCore, isQObjectAlive -from ..GraphicsScene import GraphicsScene -from ..Point import Point -from .. import functions as fn -import weakref -import operator - - -# Recipe from https://docs.python.org/3.8/library/collections.html#collections.OrderedDict -# slightly adapted for Python 3.7 compatibility -class LRU(OrderedDict): - 'Limit size, evicting the least recently looked-up key when full' - - def __init__(self, maxsize=128, *args, **kwds): - self.maxsize = maxsize - super().__init__(*args, **kwds) - - def __getitem__(self, key): - value = super().__getitem__(key) - self.move_to_end(key) - return value - - def __setitem__(self, key, value): - if key in self: - self.move_to_end(key) - super().__setitem__(key, value) - if len(self) > self.maxsize: - oldest = next(iter(self)) - del self[oldest] - - -class GraphicsItem(object): - """ - **Bases:** :class:`object` - - Abstract class providing useful methods to GraphicsObject and GraphicsWidget. - (This is required because we cannot have multiple inheritance with QObject subclasses.) - - A note about Qt's GraphicsView framework: - - The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. - """ - _pixelVectorGlobalCache = LRU(100) - - def __init__(self, register=None): - if not hasattr(self, '_qtBaseClass'): - for b in self.__class__.__bases__: - if issubclass(b, QtGui.QGraphicsItem): - self.__class__._qtBaseClass = b - break - if not hasattr(self, '_qtBaseClass'): - raise Exception('Could not determine Qt base class for GraphicsItem: %s' % str(self)) - - self._pixelVectorCache = [None, None] - self._viewWidget = None - self._viewBox = None - self._connectedView = None - self._exportOpts = False ## If False, not currently exporting. Otherwise, contains dict of export options. - self._cachedView = None - if register is not None and register: - warnings.warn( - "'register' argument is deprecated and does nothing, will be removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - - def getViewWidget(self): - """ - Return the view widget for this item. - - If the scene has multiple views, only the first view is returned. - The return value is cached; clear the cached value with forgetViewWidget(). - If the view has been deleted by Qt, return None. - """ - if self._viewWidget is None: - scene = self.scene() - if scene is None: - return None - views = scene.views() - if len(views) < 1: - return None - self._viewWidget = weakref.ref(self.scene().views()[0]) - - v = self._viewWidget() - if v is not None and not isQObjectAlive(v): - return None - - return v - - def forgetViewWidget(self): - self._viewWidget = None - - def getViewBox(self): - """ - Return the first ViewBox or GraphicsView which bounds this item's visible space. - If this item is not contained within a ViewBox, then the GraphicsView is returned. - If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. - The result is cached; clear the cache with forgetViewBox() - """ - if self._viewBox is None: - p = self - while True: - try: - p = p.parentItem() - except RuntimeError: ## sometimes happens as items are being removed from a scene and collected. - return None - if p is None: - vb = self.getViewWidget() - if vb is None: - return None - else: - self._viewBox = weakref.ref(vb) - break - if hasattr(p, 'implements') and p.implements('ViewBox'): - self._viewBox = weakref.ref(p) - break - return self._viewBox() ## If we made it this far, _viewBox is definitely not None - - def forgetViewBox(self): - self._viewBox = None - - def deviceTransform(self, viewportTransform=None): - """ - Return the transform that converts local item coordinates to device coordinates (usually pixels). - Extends deviceTransform to automatically determine the viewportTransform. - """ - if viewportTransform is None: - view = self.getViewWidget() - if view is None: - return None - viewportTransform = view.viewportTransform() - dt = self._qtBaseClass.deviceTransform(self, viewportTransform) - - #xmag = abs(dt.m11())+abs(dt.m12()) - #ymag = abs(dt.m21())+abs(dt.m22()) - #if xmag * ymag == 0: - if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed - return None - else: - return dt - - def viewTransform(self): - """Return the transform that maps from local coordinates to the item's ViewBox coordinates - If there is no ViewBox, return the scene transform. - Returns None if the item does not have a view.""" - view = self.getViewBox() - if view is None: - return None - if hasattr(view, 'implements') and view.implements('ViewBox'): - tr = self.itemTransform(view.innerSceneItem()) - if isinstance(tr, tuple): - tr = tr[0] ## difference between pyside and pyqt - return tr - else: - return self.sceneTransform() - #return self.deviceTransform(view.viewportTransform()) - - - - def getBoundingParents(self): - """Return a list of parents to this item that have child clipping enabled.""" - p = self - parents = [] - while True: - p = p.parentItem() - if p is None: - break - if p.flags() & self.ItemClipsChildrenToShape: - parents.append(p) - return parents - - def viewRect(self): - """Return the visible bounds of this item's ViewBox or GraphicsWidget, - in the local coordinate system of the item.""" - if self._cachedView is not None: - return self._cachedView - - # Note that in cases of early returns here, the view cache stays empty (None). - view = self.getViewBox() - if view is None: - return None - bounds = self.mapRectFromView(view.viewRect()) - if bounds is None: - return None - - bounds = bounds.normalized() - - self._cachedView = bounds - - ## nah. - #for p in self.getBoundingParents(): - #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) - - return bounds - - - - def pixelVectors(self, direction=None): - """Return vectors in local coordinates representing the width and height of a view pixel. - If direction is specified, then return vectors parallel and orthogonal to it. - - Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) - or if pixel size is below floating-point precision limit. - """ - - ## This is an expensive function that gets called very frequently. - ## We have two levels of cache to try speeding things up. - - dt = self.deviceTransform() - if dt is None: - return None, None - - ## Ignore translation. If the translation is much larger than the scale - ## (such as when looking at unix timestamps), we can get floating-point errors. - dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) - - if direction is None: - direction = QtCore.QPointF(1, 0) - elif direction.manhattanLength() == 0: - raise Exception("Cannot compute pixel length for 0-length vector.") - - key = (dt.m11(), dt.m21(), dt.m12(), dt.m22(), direction.x(), direction.y()) - - ## check local cache - if key == self._pixelVectorCache[0]: - return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* - - ## check global cache - pv = self._pixelVectorGlobalCache.get(key, None) - if pv is not None: - self._pixelVectorCache = [key, pv] - return tuple(map(Point,pv)) ## return a *copy* - - ## attempt to re-scale direction vector to fit within the precision of the coordinate system - ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. - ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. - ## Example: - ## dt = [ 1, 0, 2 - ## 0, 2, 1e20 - ## 0, 0, 1 ] - ## Then we map the origin (0,0) and direction (0,1) and get: - ## o' = 2,1e20 - ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float - ## - ## |o' - d'| == 0 <-- this is the problem. - - ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? - - #if direction.x() == 0: - #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) - ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) - #elif direction.y() == 0: - #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) - ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) - #else: - #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 - #if r == 0: - #r = 1. ## shouldn't need to do this; probably means the math above is wrong? - #directionr = direction * r - directionr = direction - - ## map direction vector onto device - #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) - #mdirection = dt.map(directionr) - dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) - viewDir = dt.map(dirLine) - if viewDir.length() == 0: - return None, None ## pixel size cannot be represented on this scale - - ## get unit vector and orthogonal vector (length of pixel) - #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space - try: - normView = viewDir.unitVector() - #normView = viewDir.norm() ## direction of one pixel orthogonal to line - normOrtho = normView.normalVector() - #normOrtho = orthoDir.norm() - except: - raise Exception("Invalid direction %s" %directionr) - - ## map back to item - dti = fn.invertQTransform(dt) - #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) - pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) - self._pixelVectorCache[1] = pv - self._pixelVectorCache[0] = dt - self._pixelVectorGlobalCache[key] = pv - return self._pixelVectorCache[1] - - - def pixelLength(self, direction, ortho=False): - """Return the length of one pixel in the direction indicated (in local coordinates) - If ortho=True, then return the length of one pixel orthogonal to the direction indicated. - - Return None if pixel size is not yet defined (usually because the item has not yet been displayed). - """ - normV, orthoV = self.pixelVectors(direction) - if normV == None or orthoV == None: - return None - if ortho: - return orthoV.length() - return normV.length() - - - def pixelSize(self): - ## deprecated - v = self.pixelVectors() - if v == (None, None): - return None, None - return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 - - def pixelWidth(self): - ## deprecated - vt = self.deviceTransform() - if vt is None: - return 0 - vt = fn.invertQTransform(vt) - return vt.map(QtCore.QLineF(0, 0, 1, 0)).length() - - def pixelHeight(self): - ## deprecated - vt = self.deviceTransform() - if vt is None: - return 0 - vt = fn.invertQTransform(vt) - return vt.map(QtCore.QLineF(0, 0, 0, 1)).length() - #return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() - - - def mapToDevice(self, obj): - """ - Return *obj* mapped from local coordinates to device coordinates (pixels). - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - return vt.map(obj) - - def mapFromDevice(self, obj): - """ - Return *obj* mapped from device coordinates (pixels) to local coordinates. - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - if isinstance(obj, QtCore.QPoint): - obj = QtCore.QPointF(obj) - vt = fn.invertQTransform(vt) - return vt.map(obj) - - def mapRectToDevice(self, rect): - """ - Return *rect* mapped from local coordinates to device coordinates (pixels). - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - return vt.mapRect(rect) - - def mapRectFromDevice(self, rect): - """ - Return *rect* mapped from device coordinates (pixels) to local coordinates. - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.mapRect(rect) - - def mapToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.map(obj) - - def mapRectToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.mapRect(obj) - - def mapFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.map(obj) - - def mapRectFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.mapRect(obj) - - def pos(self): - return Point(self._qtBaseClass.pos(self)) - - def viewPos(self): - return self.mapToView(self.mapFromParent(self.pos())) - - def parentItem(self): - ## PyQt bug -- some items are returned incorrectly. - return GraphicsScene.translateGraphicsItem(self._qtBaseClass.parentItem(self)) - - def setParentItem(self, parent): - ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 - if parent is not None: - pscene = parent.scene() - if pscene is not None and self.scene() is not pscene: - pscene.addItem(self) - return self._qtBaseClass.setParentItem(self, parent) - - def childItems(self): - ## PyQt bug -- some child items are returned incorrectly. - return list(map(GraphicsScene.translateGraphicsItem, self._qtBaseClass.childItems(self))) - - - def sceneTransform(self): - ## Qt bug: do no allow access to sceneTransform() until - ## the item has a scene. - - if self.scene() is None: - return self.transform() - else: - return self._qtBaseClass.sceneTransform(self) - - - def transformAngle(self, relativeItem=None): - """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) - If relativeItem is given, then the angle is determined relative to that item. - """ - if relativeItem is None: - relativeItem = self.parentItem() - - - tr = self.itemTransform(relativeItem) - if isinstance(tr, tuple): ## difference between pyside and pyqt - tr = tr[0] - #vec = tr.map(Point(1,0)) - tr.map(Point(0,0)) - vec = tr.map(QtCore.QLineF(0,0,1,0)) - #return Point(vec).angle(Point(1,0)) - return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0))) - - #def itemChange(self, change, value): - #ret = self._qtBaseClass.itemChange(self, change, value) - #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: - #print "Item scene changed:", self - #self.setChildScene(self) ## This is bizarre. - #return ret - - #def setChildScene(self, ch): - #scene = self.scene() - #for ch2 in ch.childItems(): - #if ch2.scene() is not scene: - #print "item", ch2, "has different scene:", ch2.scene(), scene - #scene.addItem(ch2) - #QtGui.QApplication.processEvents() - #print " --> ", ch2.scene() - #self.setChildScene(ch2) - - def parentChanged(self): - """Called when the item's parent has changed. - This method handles connecting / disconnecting from ViewBox signals - to make sure viewRangeChanged works properly. It should generally be - extended, not overridden.""" - self._updateView() - - - def _updateView(self): - ## called to see whether this item has a new view to connect to - ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange. - - if not hasattr(self, '_connectedView'): - # Happens when Python is shutting down. - return - - ## It is possible this item has moved to a different ViewBox or widget; - ## clear out previously determined references to these. - self.forgetViewBox() - self.forgetViewWidget() - - ## check for this item's current viewbox or view widget - view = self.getViewBox() - #if view is None: - ##print " no view" - #return - - oldView = None - if self._connectedView is not None: - oldView = self._connectedView() - - if view is oldView: - #print " already have view", view - return - - ## disconnect from previous view - if oldView is not None: - for signal, slot in [('sigRangeChanged', self.viewRangeChanged), - ('sigDeviceRangeChanged', self.viewRangeChanged), - ('sigTransformChanged', self.viewTransformChanged), - ('sigDeviceTransformChanged', self.viewTransformChanged)]: - try: - getattr(oldView, signal).disconnect(slot) - except (TypeError, AttributeError, RuntimeError): - # TypeError and RuntimeError are from pyqt and pyside, respectively - pass - - self._connectedView = None - - ## connect to new view - if view is not None: - #print "connect:", self, view - if hasattr(view, 'sigDeviceRangeChanged'): - # connect signals from GraphicsView - view.sigDeviceRangeChanged.connect(self.viewRangeChanged) - view.sigDeviceTransformChanged.connect(self.viewTransformChanged) - else: - # connect signals from ViewBox - view.sigRangeChanged.connect(self.viewRangeChanged) - view.sigTransformChanged.connect(self.viewTransformChanged) - self._connectedView = weakref.ref(view) - self.viewRangeChanged() - self.viewTransformChanged() - - ## inform children that their view might have changed - self._replaceView(oldView) - - self.viewChanged(view, oldView) - - def viewChanged(self, view, oldView): - """Called when this item's view has changed - (ie, the item has been added to or removed from a ViewBox)""" - pass - - def _replaceView(self, oldView, item=None): - if item is None: - item = self - for child in item.childItems(): - if isinstance(child, GraphicsItem): - if child.getViewBox() is oldView: - child._updateView() - #self._replaceView(oldView, child) - else: - self._replaceView(oldView, child) - - - - def viewRangeChanged(self): - """ - Called whenever the view coordinates of the ViewBox containing this item have changed. - """ - pass - - def viewTransformChanged(self): - """ - Called whenever the transformation matrix of the view has changed. - (eg, the view range has changed or the view was resized) - Invalidates the viewRect cache. - """ - self._cachedView = None - - #def prepareGeometryChange(self): - #self._qtBaseClass.prepareGeometryChange(self) - #self.informViewBoundsChanged() - - def informViewBoundsChanged(self): - """ - Inform this item's container ViewBox that the bounds of this item have changed. - This is used by ViewBox to react if auto-range is enabled. - """ - view = self.getViewBox() - if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'): - view.itemBoundsChanged(self) ## inform view so it can update its range if it wants - - def childrenShape(self): - """Return the union of the shapes of all descendants of this item in local coordinates.""" - childs = self.allChildItems() - shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()] - return reduce(operator.add, shapes) - - def allChildItems(self, root=None): - """Return list of the entire item tree descending from this item.""" - if root is None: - root = self - tree = [] - for ch in root.childItems(): - tree.append(ch) - tree.extend(self.allChildItems(ch)) - return tree - - - def setExportMode(self, export, opts=None): - """ - This method is called by exporters to inform items that they are being drawn for export - with a specific set of options. Items access these via self._exportOptions. - When exporting is complete, _exportOptions is set to False. - """ - if opts is None: - opts = {} - if export: - self._exportOpts = opts - #if 'antialias' not in opts: - #self._exportOpts['antialias'] = True - else: - self._exportOpts = False - - #def update(self): - #self._qtBaseClass.update(self) - #print "Update:", self - - def getContextMenus(self, event): - return [self.getMenu()] if hasattr(self, "getMenu") else [] diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/pyqtgraph/graphicsItems/GraphicsLayout.py deleted file mode 100644 index 9c20935..0000000 --- a/pyqtgraph/graphicsItems/GraphicsLayout.py +++ /dev/null @@ -1,194 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import functions as fn -from .GraphicsWidget import GraphicsWidget -## Must be imported at the end to avoid cyclic-dependency hell: -from .ViewBox import ViewBox -from .PlotItem import PlotItem -from .LabelItem import LabelItem - -__all__ = ['GraphicsLayout'] -class GraphicsLayout(GraphicsWidget): - """ - Used for laying out GraphicsWidgets in a grid. - This is usually created automatically as part of a :class:`GraphicsWindow ` or :class:`GraphicsLayoutWidget `. - """ - - - def __init__(self, parent=None, border=None): - GraphicsWidget.__init__(self, parent) - if border is True: - border = (100,100,100) - self.border = border - self.layout = QtGui.QGraphicsGridLayout() - self.setLayout(self.layout) - self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item - self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item - self.itemBorders = {} ## {item1: QtGui.QGraphicsRectItem, ...} border rects - self.currentRow = 0 - self.currentCol = 0 - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - - #def resizeEvent(self, ev): - #ret = GraphicsWidget.resizeEvent(self, ev) - #print self.pos(), self.mapToDevice(self.rect().topLeft()) - #return ret - - def setBorder(self, *args, **kwds): - """ - Set the pen used to draw border between cells. - - See :func:`mkPen ` for arguments. - """ - self.border = fn.mkPen(*args, **kwds) - - for borderRect in self.itemBorders.values(): - borderRect.setPen(self.border) - - def nextRow(self): - """Advance to next row for automatic item placement""" - self.currentRow += 1 - self.currentCol = -1 - self.nextColumn() - - def nextColumn(self): - """Advance to next available column - (generally only for internal use--called by addItem)""" - self.currentCol += 1 - while self.getItem(self.currentRow, self.currentCol) is not None: - self.currentCol += 1 - - def nextCol(self, *args, **kargs): - """Alias of nextColumn""" - return self.nextColumn(*args, **kargs) - - def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a PlotItem and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`PlotItem.__init__ ` - Returns the created item. - """ - plot = PlotItem(**kargs) - self.addItem(plot, row, col, rowspan, colspan) - return plot - - def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a ViewBox and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`ViewBox.__init__ ` - Returns the created item. - """ - vb = ViewBox(**kargs) - self.addItem(vb, row, col, rowspan, colspan) - return vb - - def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a LabelItem with *text* and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`LabelItem.__init__ ` - Returns the created item. - - To create a vertical label, use *angle* = -90. - """ - text = LabelItem(text, **kargs) - self.addItem(text, row, col, rowspan, colspan) - return text - - def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ ` - Returns the created item. - """ - layout = GraphicsLayout(**kargs) - self.addItem(layout, row, col, rowspan, colspan) - return layout - - def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): - """ - Add an item to the layout and place it in the next available cell (or in the cell specified). - The item must be an instance of a QGraphicsWidget subclass. - """ - if row is None: - row = self.currentRow - if col is None: - col = self.currentCol - - self.items[item] = [] - for i in range(rowspan): - for j in range(colspan): - row2 = row + i - col2 = col + j - if row2 not in self.rows: - self.rows[row2] = {} - self.rows[row2][col2] = item - self.items[item].append((row2, col2)) - - borderRect = QtGui.QGraphicsRectItem() - - borderRect.setParentItem(self) - borderRect.setZValue(1e3) - borderRect.setPen(fn.mkPen(self.border)) - - self.itemBorders[item] = borderRect - - item.geometryChanged.connect(self._updateItemBorder) - - self.layout.addItem(item, row, col, rowspan, colspan) - self.layout.activate() # Update layout, recalculating bounds. - # Allows some PyQtGraph features to also work without Qt event loop. - - self.nextColumn() - - def getItem(self, row, col): - """Return the item in (*row*, *col*). If the cell is empty, return None.""" - return self.rows.get(row, {}).get(col, None) - - def boundingRect(self): - return self.rect() - - def itemIndex(self, item): - for i in range(self.layout.count()): - if self.layout.itemAt(i).graphicsItem() is item: - return i - raise Exception("Could not determine index of item " + str(item)) - - def removeItem(self, item): - """Remove *item* from the layout.""" - ind = self.itemIndex(item) - self.layout.removeAt(ind) - self.scene().removeItem(item) - - for r, c in self.items[item]: - del self.rows[r][c] - del self.items[item] - - item.geometryChanged.disconnect(self._updateItemBorder) - del self.itemBorders[item] - - self.update() - - def clear(self): - for i in list(self.items.keys()): - self.removeItem(i) - self.currentRow = 0 - self.currentCol = 0 - - def setContentsMargins(self, *args): - # Wrap calls to layout. This should happen automatically, but there - # seems to be a Qt bug: - # http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout - self.layout.setContentsMargins(*args) - - def setSpacing(self, *args): - self.layout.setSpacing(*args) - - def _updateItemBorder(self): - if self.border is None: - return - - item = self.sender() - if item is None: - return - - r = item.mapRectToParent(item.boundingRect()) - self.itemBorders[item].setRect(r) diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/pyqtgraph/graphicsItems/GraphicsObject.py deleted file mode 100644 index 2e63b53..0000000 --- a/pyqtgraph/graphicsItems/GraphicsObject.py +++ /dev/null @@ -1,39 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB -if QT_LIB.startswith('PyQt'): - from ..Qt import sip -from .GraphicsItem import GraphicsItem - -__all__ = ['GraphicsObject'] -class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): - """ - **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsObject` - - Extension of QGraphicsObject with some useful methods (provided by :class:`GraphicsItem `) - """ - _qtBaseClass = QtGui.QGraphicsObject - def __init__(self, *args): - self.__inform_view_on_changes = True - QtGui.QGraphicsObject.__init__(self, *args) - self.setFlag(self.ItemSendsGeometryChanges) - GraphicsItem.__init__(self) - - def itemChange(self, change, value): - ret = super().itemChange(change, value) - if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: - self.parentChanged() - try: - inform_view_on_change = self.__inform_view_on_changes - except AttributeError: - # It's possible that the attribute was already collected when the itemChange happened - # (if it was triggered during the gc of the object). - pass - else: - if inform_view_on_change and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]: - self.informViewBoundsChanged() - - ## workaround for pyqt bug: - ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html - if QT_LIB in ['PyQt4', 'PyQt5'] and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): - ret = sip.cast(ret, QtGui.QGraphicsItem) - - return ret diff --git a/pyqtgraph/graphicsItems/GraphicsWidget.py b/pyqtgraph/graphicsItems/GraphicsWidget.py deleted file mode 100644 index c379ce8..0000000 --- a/pyqtgraph/graphicsItems/GraphicsWidget.py +++ /dev/null @@ -1,59 +0,0 @@ -from ..Qt import QtGui, QtCore -from ..GraphicsScene import GraphicsScene -from .GraphicsItem import GraphicsItem - -__all__ = ['GraphicsWidget'] - -class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget): - - _qtBaseClass = QtGui.QGraphicsWidget - def __init__(self, *args, **kargs): - """ - **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsWidget` - - Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. - Most of the extra functionality is inherited from :class:`GraphicsItem `. - """ - QtGui.QGraphicsWidget.__init__(self, *args, **kargs) - GraphicsItem.__init__(self) - - ## done by GraphicsItem init - #GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() - - # Removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 - #def itemChange(self, change, value): - ## BEWARE: Calling QGraphicsWidget.itemChange can lead to crashing! - ##ret = QtGui.QGraphicsWidget.itemChange(self, change, value) ## segv occurs here - ## The default behavior is just to return the value argument, so we'll do that - ## without calling the original method. - #ret = value - #if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: - #self._updateView() - #return ret - - def setFixedHeight(self, h): - self.setMaximumHeight(h) - self.setMinimumHeight(h) - - def setFixedWidth(self, h): - self.setMaximumWidth(h) - self.setMinimumWidth(h) - - def height(self): - return self.geometry().height() - - def width(self): - return self.geometry().width() - - def boundingRect(self): - br = self.mapRectFromParent(self.geometry()).normalized() - #print "bounds:", br - return br - - def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. - p = QtGui.QPainterPath() - p.addRect(self.boundingRect()) - #print "shape:", p.boundingRect() - return p - - diff --git a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py b/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py deleted file mode 100644 index 251bc0c..0000000 --- a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py +++ /dev/null @@ -1,110 +0,0 @@ -from ..Qt import QtGui, QtCore -from ..Point import Point - - -class GraphicsWidgetAnchor(object): - """ - Class used to allow GraphicsWidgets to anchor to a specific position on their - parent. The item will be automatically repositioned if the parent is resized. - This is used, for example, to anchor a LegendItem to a corner of its parent - PlotItem. - - """ - - def __init__(self): - self.__parent = None - self.__parentAnchor = None - self.__itemAnchor = None - self.__offset = (0,0) - if hasattr(self, 'geometryChanged'): - self.geometryChanged.connect(self.__geometryChanged) - - def anchor(self, itemPos, parentPos, offset=(0,0)): - """ - Anchors the item at its local itemPos to the item's parent at parentPos. - Both positions are expressed in values relative to the size of the item or parent; - a value of 0 indicates left or top edge, while 1 indicates right or bottom edge. - - Optionally, offset may be specified to introduce an absolute offset. - - Example: anchor a box such that its upper-right corner is fixed 10px left - and 10px down from its parent's upper-right corner:: - - box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10)) - """ - parent = self.parentItem() - if parent is None: - raise Exception("Cannot anchor; parent is not set.") - - if self.__parent is not parent: - if self.__parent is not None: - self.__parent.geometryChanged.disconnect(self.__geometryChanged) - - self.__parent = parent - parent.geometryChanged.connect(self.__geometryChanged) - - self.__itemAnchor = itemPos - self.__parentAnchor = parentPos - self.__offset = offset - self.__geometryChanged() - - - def autoAnchor(self, pos, relative=True): - """ - Set the position of this item relative to its parent by automatically - choosing appropriate anchor settings. - - If relative is True, one corner of the item will be anchored to - the appropriate location on the parent with no offset. The anchored - corner will be whichever is closest to the parent's boundary. - - If relative is False, one corner of the item will be anchored to the same - corner of the parent, with an absolute offset to achieve the correct - position. - """ - pos = Point(pos) - br = self.mapRectToParent(self.boundingRect()).translated(pos - self.pos()) - pbr = self.parentItem().boundingRect() - anchorPos = [0,0] - parentPos = Point() - itemPos = Point() - if abs(br.left() - pbr.left()) < abs(br.right() - pbr.right()): - anchorPos[0] = 0 - parentPos[0] = pbr.left() - itemPos[0] = br.left() - else: - anchorPos[0] = 1 - parentPos[0] = pbr.right() - itemPos[0] = br.right() - - if abs(br.top() - pbr.top()) < abs(br.bottom() - pbr.bottom()): - anchorPos[1] = 0 - parentPos[1] = pbr.top() - itemPos[1] = br.top() - else: - anchorPos[1] = 1 - parentPos[1] = pbr.bottom() - itemPos[1] = br.bottom() - - if relative: - relPos = [(itemPos[0]-pbr.left()) / pbr.width(), (itemPos[1]-pbr.top()) / pbr.height()] - self.anchor(anchorPos, relPos) - else: - offset = itemPos - parentPos - self.anchor(anchorPos, anchorPos, offset) - - def __geometryChanged(self): - if self.__parent is None: - return - if self.__itemAnchor is None: - return - - o = self.mapToParent(Point(0,0)) - a = self.boundingRect().bottomRight() * Point(self.__itemAnchor) - a = self.mapToParent(a) - p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor) - off = Point(self.__offset) - pos = p + (o-a) + off - self.setPos(pos) - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/GridItem.py b/pyqtgraph/graphicsItems/GridItem.py deleted file mode 100644 index 4cb30bf..0000000 --- a/pyqtgraph/graphicsItems/GridItem.py +++ /dev/null @@ -1,206 +0,0 @@ -from ..Qt import QtGui, QtCore -from .UIGraphicsItem import * -import numpy as np -from ..Point import Point -from .. import functions as fn -from .. import getConfigOption - -__all__ = ['GridItem'] -class GridItem(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` - - Displays a rectangular grid of lines indicating major divisions within a coordinate system. - Automatically determines what divisions to use. - """ - - def __init__(self, pen='default', textPen='default'): - UIGraphicsItem.__init__(self) - #QtGui.QGraphicsItem.__init__(self, *args) - #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.opts = {} - - self.setPen(pen) - self.setTextPen(textPen) - self.setTickSpacing(x=[None, None, None], y=[None, None, None]) - - - def setPen(self, *args, **kwargs): - """Set the pen used to draw the grid.""" - if kwargs == {} and (args == () or args == ('default',)): - self.opts['pen'] = fn.mkPen(getConfigOption('foreground')) - else: - self.opts['pen'] = fn.mkPen(*args, **kwargs) - - self.picture = None - self.update() - - - def setTextPen(self, *args, **kwargs): - """Set the pen used to draw the texts.""" - if kwargs == {} and (args == () or args == ('default',)): - self.opts['textPen'] = fn.mkPen(getConfigOption('foreground')) - else: - if args == (None,): - self.opts['textPen'] = None - else: - self.opts['textPen'] = fn.mkPen(*args, **kwargs) - - self.picture = None - self.update() - - - def setTickSpacing(self, x=None, y=None): - """ - Set the grid tick spacing to use. - - Tick spacing for each axis shall be specified as an array of - descending values, one for each tick scale. When the value - is set to None, grid line distance is chosen automatically - for this particular level. - - Example: - Default setting of 3 scales for each axis: - setTickSpacing(x=[None, None, None], y=[None, None, None]) - - Single scale with distance of 1.0 for X axis, Two automatic - scales for Y axis: - setTickSpacing(x=[1.0], y=[None, None]) - - Single scale with distance of 1.0 for X axis, Two scales - for Y axis, one with spacing of 1.0, other one automatic: - setTickSpacing(x=[1.0], y=[1.0, None]) - """ - self.opts['tickSpacing'] = (x or self.opts['tickSpacing'][0], - y or self.opts['tickSpacing'][1]) - - self.grid_depth = max([len(s) for s in self.opts['tickSpacing']]) - - self.picture = None - self.update() - - - def viewRangeChanged(self): - UIGraphicsItem.viewRangeChanged(self) - self.picture = None - #UIGraphicsItem.viewRangeChanged(self) - #self.update() - - def paint(self, p, opt, widget): - #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) - #p.drawRect(self.boundingRect()) - #UIGraphicsItem.paint(self, p, opt, widget) - ### draw picture - if self.picture is None: - #print "no pic, draw.." - self.generatePicture() - p.drawPicture(QtCore.QPointF(0, 0), self.picture) - #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - #p.drawLine(0, -100, 0, 100) - #p.drawLine(-100, 0, 100, 0) - #print "drawing Grid." - - - def generatePicture(self): - self.picture = QtGui.QPicture() - p = QtGui.QPainter() - p.begin(self.picture) - - vr = self.getViewWidget().rect() - unit = self.pixelWidth(), self.pixelHeight() - dim = [vr.width(), vr.height()] - lvr = self.boundingRect() - ul = np.array([lvr.left(), lvr.top()]) - br = np.array([lvr.right(), lvr.bottom()]) - - texts = [] - - if ul[1] > br[1]: - x = ul[1] - ul[1] = br[1] - br[1] = x - - lastd = [None, None] - for i in range(self.grid_depth - 1, -1, -1): - dist = br-ul - nlTarget = 10.**i - - d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) - for ax in range(0,2): - ts = self.opts['tickSpacing'][ax] - try: - if ts[i] is not None: - d[ax] = ts[i] - except IndexError: - pass - lastd[ax] = d[ax] - - ul1 = np.floor(ul / d) * d - br1 = np.ceil(br / d) * d - dist = br1-ul1 - nl = (dist / d) + 0.5 - #print "level", i - #print " dim", dim - #print " dist", dist - #print " d", d - #print " nl", nl - for ax in range(0,2): ## Draw grid for both axes - if i >= len(self.opts['tickSpacing'][ax]): - continue - if d[ax] < lastd[ax]: - continue - - ppl = dim[ax] / nl[ax] - c = np.clip(5 * (ppl-3), 0., 50.).astype(int) - - linePen = self.opts['pen'] - lineColor = self.opts['pen'].color() - lineColor.setAlpha(c) - linePen.setColor(lineColor) - - textPen = self.opts['textPen'] - if textPen is not None: - textColor = self.opts['textPen'].color() - textColor.setAlpha(c * 2) - textPen.setColor(textColor) - - bx = (ax+1) % 2 - for x in range(0, int(nl[ax])): - linePen.setCosmetic(False) - if ax == 0: - linePen.setWidthF(self.pixelWidth()) - else: - linePen.setWidthF(self.pixelHeight()) - p.setPen(linePen) - p1 = np.array([0.,0.]) - p2 = np.array([0.,0.]) - p1[ax] = ul1[ax] + x * d[ax] - p2[ax] = p1[ax] - p1[bx] = ul[bx] - p2[bx] = br[bx] - ## don't draw lines that are out of bounds. - if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): - continue - p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) - if i < 2 and textPen is not None: - if ax == 0: - x = p1[0] + unit[0] - y = ul[1] + unit[1] * 8. - else: - x = ul[0] + unit[0]*3 - y = p1[1] + unit[1] - texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) - tr = self.deviceTransform() - p.setWorldTransform(fn.invertQTransform(tr)) - - if textPen is not None and len(texts) > 0: - # if there is at least one text, then c is set - textColor.setAlpha(c * 2) - p.setPen(QtGui.QPen(textColor)) - for t in texts: - x = tr.map(t[0]) + Point(0.5, 0.5) - p.drawText(x, t[1]) - - p.end() diff --git a/pyqtgraph/graphicsItems/HistogramLUTItem.py b/pyqtgraph/graphicsItems/HistogramLUTItem.py deleted file mode 100644 index 89c4556..0000000 --- a/pyqtgraph/graphicsItems/HistogramLUTItem.py +++ /dev/null @@ -1,350 +0,0 @@ -# -*- coding: utf-8 -*- -""" -GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. -""" - - -from ..Qt import QtGui, QtCore -from .. import functions as fn -from .GraphicsWidget import GraphicsWidget -from .ViewBox import * -from .GradientEditorItem import * -from .LinearRegionItem import * -from .PlotDataItem import * -from .AxisItem import * -from .GridItem import * -from ..Point import Point -from .. import functions as fn -import numpy as np -from .. import debug as debug - -import weakref - -__all__ = ['HistogramLUTItem'] - - -class HistogramLUTItem(GraphicsWidget): - """ - This is a graphicsWidget which provides controls for adjusting the display of an image. - - Includes: - - - Image histogram - - Movable region over histogram to select black/white levels - - Gradient editor to define color lookup table for single-channel images - - ================ =========================================================== - image (:class:`~pyqtgraph.ImageItem` or ``None``) If *image* is - provided, then the control will be automatically linked to - the image and changes to the control will be immediately - reflected in the image's appearance. - fillHistogram (bool) By default, the histogram is rendered with a fill. - For performance, set ``fillHistogram=False`` - rgbHistogram (bool) Sets whether the histogram is computed once over all - channels of the image, or once per channel. - levelMode 'mono' or 'rgba'. If 'mono', then only a single set of - black/white level lines is drawn, and the levels apply to - all channels in the image. If 'rgba', then one set of - levels is drawn for each channel. - ================ =========================================================== - """ - - sigLookupTableChanged = QtCore.Signal(object) - sigLevelsChanged = QtCore.Signal(object) - sigLevelChangeFinished = QtCore.Signal(object) - - def __init__(self, image=None, fillHistogram=True, rgbHistogram=False, levelMode='mono'): - GraphicsWidget.__init__(self) - self.lut = None - self.imageItem = lambda: None # fake a dead weakref - self.levelMode = levelMode - self.rgbHistogram = rgbHistogram - - self.layout = QtGui.QGraphicsGridLayout() - self.setLayout(self.layout) - self.layout.setContentsMargins(1,1,1,1) - self.layout.setSpacing(0) - self.vb = ViewBox(parent=self) - self.vb.setMaximumWidth(152) - self.vb.setMinimumWidth(45) - self.vb.setMouseEnabled(x=False, y=True) - self.gradient = GradientEditorItem() - self.gradient.setOrientation('right') - self.gradient.loadPreset('grey') - self.regions = [ - LinearRegionItem([0, 1], 'horizontal', swapMode='block'), - LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='r', - brush=fn.mkBrush((255, 50, 50, 50)), span=(0., 1/3.)), - LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='g', - brush=fn.mkBrush((50, 255, 50, 50)), span=(1/3., 2/3.)), - LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='b', - brush=fn.mkBrush((50, 50, 255, 80)), span=(2/3., 1.)), - LinearRegionItem([0, 1], 'horizontal', swapMode='block', pen='w', - brush=fn.mkBrush((255, 255, 255, 50)), span=(2/3., 1.))] - for region in self.regions: - region.setZValue(1000) - self.vb.addItem(region) - region.lines[0].addMarker('<|', 0.5) - region.lines[1].addMarker('|>', 0.5) - region.sigRegionChanged.connect(self.regionChanging) - region.sigRegionChangeFinished.connect(self.regionChanged) - - self.region = self.regions[0] # for backward compatibility. - - self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self) - self.layout.addItem(self.axis, 0, 0) - self.layout.addItem(self.vb, 0, 1) - self.layout.addItem(self.gradient, 0, 2) - self.range = None - self.gradient.setFlag(self.gradient.ItemStacksBehindParent) - self.vb.setFlag(self.gradient.ItemStacksBehindParent) - - self.gradient.sigGradientChanged.connect(self.gradientChanged) - self.vb.sigRangeChanged.connect(self.viewRangeChanged) - add = QtGui.QPainter.CompositionMode_Plus - self.plots = [ - PlotCurveItem(pen=(200, 200, 200, 100)), # mono - PlotCurveItem(pen=(255, 0, 0, 100), compositionMode=add), # r - PlotCurveItem(pen=(0, 255, 0, 100), compositionMode=add), # g - PlotCurveItem(pen=(0, 0, 255, 100), compositionMode=add), # b - PlotCurveItem(pen=(200, 200, 200, 100), compositionMode=add), # a - ] - - self.plot = self.plots[0] # for backward compatibility. - for plot in self.plots: - plot.setRotation(90) - self.vb.addItem(plot) - - self.fillHistogram(fillHistogram) - self._showRegions() - - self.vb.addItem(self.plot) - self.autoHistogramRange() - - if image is not None: - self.setImageItem(image) - - def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)): - colors = [color, (255, 0, 0, 50), (0, 255, 0, 50), (0, 0, 255, 50), (255, 255, 255, 50)] - for i,plot in enumerate(self.plots): - if fill: - plot.setFillLevel(level) - plot.setBrush(colors[i]) - else: - plot.setFillLevel(None) - - def paint(self, p, *args): - if self.levelMode != 'mono': - return - - pen = self.region.lines[0].pen - rgn = self.getLevels() - p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0])) - p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1])) - gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) - p.setRenderHint(QtGui.QPainter.Antialiasing) - for pen in [fn.mkPen((0, 0, 0, 100), width=3), pen]: - p.setPen(pen) - p.drawLine(p1 + Point(0, 5), gradRect.bottomLeft()) - p.drawLine(p2 - Point(0, 5), gradRect.topLeft()) - p.drawLine(gradRect.topLeft(), gradRect.topRight()) - p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) - - def setHistogramRange(self, mn, mx, padding=0.1): - """Set the Y range on the histogram plot. This disables auto-scaling.""" - self.vb.enableAutoRange(self.vb.YAxis, False) - self.vb.setYRange(mn, mx, padding) - - def autoHistogramRange(self): - """Enable auto-scaling on the histogram plot.""" - self.vb.enableAutoRange(self.vb.XYAxes) - - def setImageItem(self, img): - """Set an ImageItem to have its levels and LUT automatically controlled - by this HistogramLUTItem. - """ - self.imageItem = weakref.ref(img) - img.sigImageChanged.connect(self.imageChanged) - self._setImageLookupTable() - self.regionChanged() - self.imageChanged(autoLevel=True) - - def viewRangeChanged(self): - self.update() - - def gradientChanged(self): - if self.imageItem() is not None: - self._setImageLookupTable() - - self.lut = None - self.sigLookupTableChanged.emit(self) - - def _setImageLookupTable(self): - if self.gradient.isLookupTrivial(): - self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8)) - else: - self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result - - def getLookupTable(self, img=None, n=None, alpha=None): - """Return a lookup table from the color gradient defined by this - HistogramLUTItem. - """ - if self.levelMode != 'mono': - return None - if n is None: - if img.dtype == np.uint8: - n = 256 - else: - n = 512 - if self.lut is None: - self.lut = self.gradient.getLookupTable(n, alpha=alpha) - return self.lut - - def regionChanged(self): - if self.imageItem() is not None: - self.imageItem().setLevels(self.getLevels()) - self.sigLevelChangeFinished.emit(self) - - def regionChanging(self): - if self.imageItem() is not None: - self.imageItem().setLevels(self.getLevels()) - self.update() - self.sigLevelsChanged.emit(self) - - def imageChanged(self, autoLevel=False, autoRange=False): - if self.imageItem() is None: - return - - if self.levelMode == 'mono': - for plt in self.plots[1:]: - plt.setVisible(False) - self.plots[0].setVisible(True) - # plot one histogram for all image data - profiler = debug.Profiler() - h = self.imageItem().getHistogram() - profiler('get histogram') - if h[0] is None: - return - self.plot.setData(*h) - profiler('set plot') - if autoLevel: - mn = h[0][0] - mx = h[0][-1] - self.region.setRegion([mn, mx]) - profiler('set region') - else: - mn, mx = self.imageItem().levels - self.region.setRegion([mn, mx]) - else: - # plot one histogram for each channel - self.plots[0].setVisible(False) - ch = self.imageItem().getHistogram(perChannel=True) - if ch[0] is None: - return - for i in range(1, 5): - if len(ch) >= i: - h = ch[i-1] - self.plots[i].setVisible(True) - self.plots[i].setData(*h) - if autoLevel: - mn = h[0][0] - mx = h[0][-1] - self.region[i].setRegion([mn, mx]) - else: - # hide channels not present in image data - self.plots[i].setVisible(False) - # make sure we are displaying the correct number of channels - self._showRegions() - - def getLevels(self): - """Return the min and max levels. - - For rgba mode, this returns a list of the levels for each channel. - """ - if self.levelMode == 'mono': - return self.region.getRegion() - else: - nch = self.imageItem().channels() - if nch is None: - nch = 3 - return [r.getRegion() for r in self.regions[1:nch+1]] - - def setLevels(self, min=None, max=None, rgba=None): - """Set the min/max (bright and dark) levels. - - Arguments may be *min* and *max* for single-channel data, or - *rgba* = [(rmin, rmax), ...] for multi-channel data. - """ - if self.levelMode == 'mono': - if min is None: - min, max = rgba[0] - assert None not in (min, max) - self.region.setRegion((min, max)) - else: - if rgba is None: - raise TypeError("Must specify rgba argument when levelMode != 'mono'.") - for i, levels in enumerate(rgba): - self.regions[i+1].setRegion(levels) - - def setLevelMode(self, mode): - """ Set the method of controlling the image levels offered to the user. - Options are 'mono' or 'rgba'. - """ - assert mode in ('mono', 'rgba') - - if mode == self.levelMode: - return - - oldLevels = self.getLevels() - self.levelMode = mode - self._showRegions() - - # do our best to preserve old levels - if mode == 'mono': - levels = np.array(oldLevels).mean(axis=0) - self.setLevels(*levels) - else: - levels = [oldLevels] * 4 - self.setLevels(rgba=levels) - - # force this because calling self.setLevels might not set the imageItem - # levels if there was no change to the region item - self.imageItem().setLevels(self.getLevels()) - - self.imageChanged() - self.update() - - def _showRegions(self): - for i in range(len(self.regions)): - self.regions[i].setVisible(False) - - if self.levelMode == 'rgba': - imax = 4 - if self.imageItem() is not None: - # Only show rgb channels if connected image lacks alpha. - nch = self.imageItem().channels() - if nch is None: - nch = 3 - xdif = 1.0 / nch - for i in range(1, nch+1): - self.regions[i].setVisible(True) - self.regions[i].setSpan((i-1) * xdif, i * xdif) - self.gradient.hide() - elif self.levelMode == 'mono': - self.regions[0].setVisible(True) - self.gradient.show() - else: - raise ValueError("Unknown level mode %r" % self.levelMode) - - def saveState(self): - return { - 'gradient': self.gradient.saveState(), - 'levels': self.getLevels(), - 'mode': self.levelMode, - } - - def restoreState(self, state): - if 'mode' in state: - self.setLevelMode(state['mode']) - self.gradient.restoreState(state['gradient']) - self.setLevels(*state['levels']) diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py deleted file mode 100644 index 9d9efa9..0000000 --- a/pyqtgraph/graphicsItems/ImageItem.py +++ /dev/null @@ -1,712 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import division - -import numpy - -from .GraphicsObject import GraphicsObject -from .. import debug as debug -from .. import functions as fn -from .. import getConfigOption -from ..Point import Point -from ..Qt import QtGui, QtCore -from ..util.cupy_helper import getCupy - -try: - from collections.abc import Callable -except ImportError: - # fallback for python < 3.3 - from collections import Callable - -translate = QtCore.QCoreApplication.translate - -__all__ = ['ImageItem'] - - -class ImageItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - GraphicsObject displaying an image. Optimized for rapid update (ie video display). - This item displays either a 2D numpy array (height, width) or - a 3D array (height, width, RGBa). This array is optionally scaled (see - :func:`setLevels `) and/or colored - with a lookup table (see :func:`setLookupTable `) - before being displayed. - - ImageItem is frequently used in conjunction with - :class:`HistogramLUTItem ` or - :class:`HistogramLUTWidget ` to provide a GUI - for controlling the levels and lookup table used to display the image. - """ - - sigImageChanged = QtCore.Signal() - sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu - - def __init__(self, image=None, **kargs): - """ - See :func:`setImage ` for all allowed initialization arguments. - """ - GraphicsObject.__init__(self) - self.menu = None - self.image = None ## original image data - self.qimage = None ## rendered image for display - - self.paintMode = None - self.levels = None ## [min, max] or [[redMin, redMax], ...] - self.lut = None - self.autoDownsample = False - self._lastDownsample = (1, 1) - self._processingBuffer = None - self._displayBuffer = None - self._renderRequired = True - self._unrenderable = False - self._cupy = getCupy() - self._xp = None # either numpy or cupy, to match the image data - self._defferedLevels = None - - self.axisOrder = getConfigOption('imageAxisOrder') - - # In some cases, we use a modified lookup table to handle both rescaling - # and LUT more efficiently - self._effectiveLut = None - - self.drawKernel = None - self.border = None - self.removable = False - - if image is not None: - self.setImage(image, **kargs) - else: - self.setOpts(**kargs) - - def setCompositionMode(self, mode): - """Change the composition mode of the item (see QPainter::CompositionMode - in the Qt documentation). This is useful when overlaying multiple ImageItems. - - ============================================ ============================================================ - **Most common arguments:** - QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it - is opaque. Otherwise, it uses the alpha channel to blend - the image with the background. - QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to - reflect the lightness or darkness of the background. - QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels - are added together. - QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background. - ============================================ ============================================================ - """ - self.paintMode = mode - self.update() - - def setBorder(self, b): - self.border = fn.mkPen(b) - self.update() - - def width(self): - if self.image is None: - return None - axis = 0 if self.axisOrder == 'col-major' else 1 - return self.image.shape[axis] - - def height(self): - if self.image is None: - return None - axis = 1 if self.axisOrder == 'col-major' else 0 - return self.image.shape[axis] - - def channels(self): - if self.image is None: - return None - return self.image.shape[2] if self.image.ndim == 3 else 1 - - def boundingRect(self): - if self.image is None: - return QtCore.QRectF(0., 0., 0., 0.) - return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) - - def setLevels(self, levels, update=True): - """ - Set image scaling levels. Can be one of: - - * [blackLevel, whiteLevel] - * [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] - - Only the first format is compatible with lookup tables. See :func:`makeARGB ` - for more details on how levels are applied. - """ - if self._xp is None: - self.levels = levels - self._defferedLevels = levels - return - if levels is not None: - levels = self._xp.asarray(levels) - self.levels = levels - self._effectiveLut = None - if update: - self.updateImage() - - def getLevels(self): - return self.levels - #return self.whiteLevel, self.blackLevel - - def setLookupTable(self, lut, update=True): - """ - Set the lookup table (numpy array) to use for this image. (see - :func:`makeARGB ` for more information on how this is used). - Optionally, lut can be a callable that accepts the current image as an - argument and returns the lookup table to use. - - Ordinarily, this table is supplied by a :class:`HistogramLUTItem ` - or :class:`GradientEditorItem `. - """ - if lut is not self.lut: - self.lut = lut - self._effectiveLut = None - if update: - self.updateImage() - - def setAutoDownsample(self, ads): - """ - Set the automatic downsampling mode for this ImageItem. - - Added in version 0.9.9 - """ - self.autoDownsample = ads - self._renderRequired = True - self.update() - - def setOpts(self, update=True, **kargs): - if 'axisOrder' in kargs: - val = kargs['axisOrder'] - if val not in ('row-major', 'col-major'): - raise ValueError('axisOrder must be either "row-major" or "col-major"') - self.axisOrder = val - if 'lut' in kargs: - self.setLookupTable(kargs['lut'], update=update) - if 'levels' in kargs: - self.setLevels(kargs['levels'], update=update) - #if 'clipLevel' in kargs: - #self.setClipLevel(kargs['clipLevel']) - if 'opacity' in kargs: - self.setOpacity(kargs['opacity']) - if 'compositionMode' in kargs: - self.setCompositionMode(kargs['compositionMode']) - if 'border' in kargs: - self.setBorder(kargs['border']) - if 'removable' in kargs: - self.removable = kargs['removable'] - self.menu = None - if 'autoDownsample' in kargs: - self.setAutoDownsample(kargs['autoDownsample']) - if update: - self.update() - - def setRect(self, rect): - """Scale and translate the image to fit within rect (must be a QRect or QRectF).""" - tr = QtGui.QTransform() - tr.translate(rect.left(), rect.top()) - tr.scale(rect.width() / self.width(), rect.height() / self.height()) - self.setTransform(tr) - - def clear(self): - self.image = None - self.prepareGeometryChange() - self.informViewBoundsChanged() - self.update() - - def _buildQImageBuffer(self, shape): - self._displayBuffer = numpy.empty(shape[:2] + (4,), dtype=numpy.ubyte) - if self._xp == self._cupy: - self._processingBuffer = self._xp.empty(shape[:2] + (4,), dtype=self._xp.ubyte) - else: - self._processingBuffer = self._displayBuffer - self.qimage = fn.makeQImage(self._displayBuffer, transpose=False, copy=False) - - def setImage(self, image=None, autoLevels=None, **kargs): - """ - Update the image displayed by this item. For more information on how the image - is processed before displaying, see :func:`makeARGB ` - - ================= ========================================================================= - **Arguments:** - image (numpy array) Specifies the image data. May be 2D (width, height) or - 3D (width, height, RGBa). The array dtype must be integer or floating - point of any bit depth. For 3D arrays, the third dimension must - be of length 3 (RGB) or 4 (RGBA). See *notes* below. - autoLevels (bool) If True, this forces the image to automatically select - levels based on the maximum and minimum values in the data. - By default, this argument is true unless the levels argument is - given. - lut (numpy array) The color lookup table to use when displaying the image. - See :func:`setLookupTable `. - levels (min, max) The minimum and maximum values to use when rescaling the image - data. By default, this will be set to the minimum and maximum values - in the image. If the image array has dtype uint8, no rescaling is necessary. - opacity (float 0.0-1.0) - compositionMode See :func:`setCompositionMode ` - border Sets the pen used when drawing the image border. Default is None. - autoDownsample (bool) If True, the image is automatically downsampled to match the - screen resolution. This improves performance for large images and - reduces aliasing. If autoDownsample is not specified, then ImageItem will - choose whether to downsample the image based on its size. - ================= ========================================================================= - - - **Notes:** - - For backward compatibility, image data is assumed to be in column-major order (column, row). - However, most image data is stored in row-major order (row, column) and will need to be - transposed before calling setImage():: - - imageitem.setImage(imagedata.T) - - This requirement can be changed by calling ``image.setOpts(axisOrder='row-major')`` or - by changing the ``imageAxisOrder`` :ref:`global configuration option `. - - - """ - profile = debug.Profiler() - - gotNewData = False - if image is None: - if self.image is None: - return - else: - old_xp = self._xp - self._xp = self._cupy.get_array_module(image) if self._cupy else numpy - gotNewData = True - processingSubstrateChanged = old_xp != self._xp - if processingSubstrateChanged: - self._processingBuffer = None - shapeChanged = (processingSubstrateChanged or self.image is None or image.shape != self.image.shape) - image = image.view() - if self.image is None or image.dtype != self.image.dtype: - self._effectiveLut = None - self.image = image - if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1: - if 'autoDownsample' not in kargs: - kargs['autoDownsample'] = True - if shapeChanged: - self.prepareGeometryChange() - self.informViewBoundsChanged() - - profile() - - if autoLevels is None: - if 'levels' in kargs: - autoLevels = False - else: - autoLevels = True - if autoLevels: - img = self.image - while img.size > 2**16: - img = img[::2, ::2] - mn, mx = self._xp.nanmin(img), self._xp.nanmax(img) - # mn and mx can still be NaN if the data is all-NaN - if mn == mx or self._xp.isnan(mn) or self._xp.isnan(mx): - mn = 0 - mx = 255 - kargs['levels'] = [mn,mx] - - profile() - - self.setOpts(update=False, **kargs) - - profile() - - self._renderRequired = True - self.update() - - profile() - - if gotNewData: - self.sigImageChanged.emit() - if self._defferedLevels is not None: - levels = self._defferedLevels - self._defferedLevels = None - self.setLevels((levels)) - - def dataTransform(self): - """Return the transform that maps from this image's input array to its - local coordinate system. - - This transform corrects for the transposition that occurs when image data - is interpreted in row-major order. - """ - # Might eventually need to account for downsampling / clipping here - tr = QtGui.QTransform() - if self.axisOrder == 'row-major': - # transpose - tr.scale(1, -1) - tr.rotate(-90) - return tr - - def inverseDataTransform(self): - """Return the transform that maps from this image's local coordinate - system to its input array. - - See dataTransform() for more information. - """ - tr = QtGui.QTransform() - if self.axisOrder == 'row-major': - # transpose - tr.scale(1, -1) - tr.rotate(-90) - return tr - - def mapToData(self, obj): - tr = self.inverseDataTransform() - return tr.map(obj) - - def mapFromData(self, obj): - tr = self.dataTransform() - return tr.map(obj) - - def quickMinMax(self, targetSize=1e6): - """ - Estimate the min/max values of the image data by subsampling. - """ - data = self.image - while data.size > targetSize: - ax = self._xp.argmax(data.shape) - sl = [slice(None)] * data.ndim - sl[ax] = slice(None, None, 2) - data = data[sl] - return self._xp.nanmin(data), self._xp.nanmax(data) - - def updateImage(self, *args, **kargs): - ## used for re-rendering qimage from self.image. - - ## can we make any assumptions here that speed things up? - ## dtype, range, size are all the same? - defaults = { - 'autoLevels': False, - } - defaults.update(kargs) - return self.setImage(*args, **defaults) - - def render(self): - # Convert data to QImage for display. - self._unrenderable = True - if self.image is None or self.image.size == 0: - return - - # Request a lookup table if this image has only one channel - if self.image.ndim == 2 or self.image.shape[2] == 1: - if isinstance(self.lut, Callable): - lut = self.lut(self.image) - else: - lut = self.lut - else: - lut = None - - if self.autoDownsample: - xds, yds = self._computeDownsampleFactors() - if xds is None: - return - - axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1] - image = fn.downsample(self.image, xds, axis=axes[0]) - image = fn.downsample(image, yds, axis=axes[1]) - self._lastDownsample = (xds, yds) - - # Check if downsampling reduced the image size to zero due to inf values. - if image.size == 0: - return - else: - image = self.image - - # if the image data is a small int, then we can combine levels + lut - # into a single lut for better performance - levels = self.levels - if levels is not None and lut is not None and levels.ndim == 1 and \ - image.dtype in (self._xp.ubyte, self._xp.uint16): - if self._effectiveLut is None: - eflsize = 2**(image.itemsize*8) - ind = self._xp.arange(eflsize) - minlev, maxlev = levels - levdiff = maxlev - minlev - levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0 - lutdtype = self._xp.min_scalar_type(lut.shape[0] - 1) - efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/levdiff, - offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1)) - efflut = lut[efflut] - - self._effectiveLut = efflut - lut = self._effectiveLut - levels = None - - # Convert single-channel image to 2D array - if image.ndim == 3 and image.shape[-1] == 1: - image = image[..., 0] - - # Assume images are in column-major order for backward compatibility - # (most images are in row-major order) - if self.axisOrder == 'col-major': - image = image.transpose((1, 0, 2)[:image.ndim]) - - if self._processingBuffer is None or self._processingBuffer.shape[:2] != image.shape[:2]: - self._buildQImageBuffer(image.shape) - - fn.makeARGB(image, lut=lut, levels=levels, output=self._processingBuffer) - if self._xp == self._cupy: - self._processingBuffer.get(out=self._displayBuffer) - self._renderRequired = False - self._unrenderable = False - - def paint(self, p, *args): - profile = debug.Profiler() - if self.image is None: - return - if self._renderRequired: - self.render() - if self._unrenderable: - return - profile('render QImage') - if self.paintMode is not None: - p.setCompositionMode(self.paintMode) - profile('set comp mode') - - shape = self.image.shape[:2] if self.axisOrder == 'col-major' else self.image.shape[:2][::-1] - p.drawImage(QtCore.QRectF(0,0,*shape), self.qimage) - profile('p.drawImage') - if self.border is not None: - p.setPen(self.border) - p.drawRect(self.boundingRect()) - - def save(self, fileName, *args): - """Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data.""" - if self._renderRequired: - self.render() - self.qimage.save(fileName, *args) - - def getHistogram(self, bins='auto', step='auto', perChannel=False, targetImageSize=200, - targetHistogramSize=500, **kwds): - """Returns x and y arrays containing the histogram values for the current image. - For an explanation of the return format, see numpy.histogram(). - - The *step* argument causes pixels to be skipped when computing the histogram to save time. - If *step* is 'auto', then a step is chosen such that the analyzed data has - dimensions roughly *targetImageSize* for each axis. - - The *bins* argument and any extra keyword arguments are passed to - self.xp.histogram(). If *bins* is 'auto', then a bin number is automatically - chosen based on the image characteristics: - - * Integer images will have approximately *targetHistogramSize* bins, - with each bin having an integer width. - * All other types will have *targetHistogramSize* bins. - - If *perChannel* is True, then the histogram is computed once per channel - and the output is a list of the results. - - This method is also used when automatically computing levels. - """ - if self.image is None or self.image.size == 0: - return None, None - if step == 'auto': - step = (max(1, int(self._xp.ceil(self.image.shape[0] / targetImageSize))), - max(1, int(self._xp.ceil(self.image.shape[1] / targetImageSize)))) - if self._xp.isscalar(step): - step = (step, step) - stepData = self.image[::step[0], ::step[1]] - - if isinstance(bins, str) and bins == 'auto': - mn = self._xp.nanmin(stepData).item() - mx = self._xp.nanmax(stepData).item() - if mx == mn: - # degenerate image, arange will fail - mx += 1 - if self._xp.isnan(mn) or self._xp.isnan(mx): - # the data are all-nan - return None, None - if stepData.dtype.kind in "ui": - # For integer data, we select the bins carefully to avoid aliasing - step = int(self._xp.ceil((mx - mn) / 500.)) - bins = [] - if step > 0.0: - bins = self._xp.arange(mn, mx + 1.01 * step, step, dtype=self._xp.int) - else: - # for float data, let numpy select the bins. - bins = self._xp.linspace(mn, mx, 500) - - if len(bins) == 0: - bins = self._xp.asarray((mn, mx)) - - kwds['bins'] = bins - - if perChannel: - hist = [] - for i in range(stepData.shape[-1]): - stepChan = stepData[..., i] - stepChan = stepChan[self._xp.isfinite(stepChan)] - h = self._xp.histogram(stepChan, **kwds) - if self._cupy: - hist.append((self._cupy.asnumpy(h[1][:-1]), self._cupy.asnumpy(h[0]))) - else: - hist.append((h[1][:-1], h[0])) - return hist - else: - stepData = stepData[self._xp.isfinite(stepData)] - hist = self._xp.histogram(stepData, **kwds) - if self._cupy: - return self._cupy.asnumpy(hist[1][:-1]), self._cupy.asnumpy(hist[0]) - else: - return hist[1][:-1], hist[0] - - def setPxMode(self, b): - """ - Set whether the item ignores transformations and draws directly to screen pixels. - If True, the item will not inherit any scale or rotation transformations from its - parent items, but its position will be transformed as usual. - (see GraphicsItem::ItemIgnoresTransformations in the Qt documentation) - """ - self.setFlag(self.ItemIgnoresTransformations, b) - - def setScaledMode(self): - self.setPxMode(False) - - def getPixmap(self): - if self._renderRequired: - self.render() - if self._unrenderable: - return None - return QtGui.QPixmap.fromImage(self.qimage) - - def pixelSize(self): - """return scene-size of a single pixel in the image""" - br = self.sceneBoundingRect() - if self.image is None: - return 1,1 - return br.width()/self.width(), br.height()/self.height() - - def viewTransformChanged(self): - if self.autoDownsample: - xds, yds = self._computeDownsampleFactors() - if xds is None: - self._renderRequired = True - self._unrenderable = True - return - if (xds, yds) != self._lastDownsample: - self._renderRequired = True - self.update() - - def _computeDownsampleFactors(self): - # reduce dimensions of image based on screen resolution - o = self.mapToDevice(QtCore.QPointF(0, 0)) - x = self.mapToDevice(QtCore.QPointF(1, 0)) - y = self.mapToDevice(QtCore.QPointF(0, 1)) - # scene may not be available yet - if o is None: - return None, None - w = Point(x - o).length() - h = Point(y - o).length() - if w == 0 or h == 0: - return None, None - return max(1, int(1.0 / w)), max(1, int(1.0 / h)) - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - return - elif self.drawKernel is not None: - ev.accept() - self.drawAt(ev.pos(), ev) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: - if self.raiseContextMenu(ev): - ev.accept() - if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: - self.drawAt(ev.pos(), ev) - - def raiseContextMenu(self, ev): - menu = self.getMenu() - if menu is None: - return False - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - return True - - def getMenu(self): - if self.menu is None: - if not self.removable: - return None - self.menu = QtGui.QMenu() - self.menu.setTitle(translate("ImageItem", "Image")) - remAct = QtGui.QAction(translate("ImageItem", "Remove image"), self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - return self.menu - - def hoverEvent(self, ev): - if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. - ev.acceptClicks(QtCore.Qt.RightButton) - elif not ev.isExit() and self.removable: - ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks - - def tabletEvent(self, ev): - pass - #print(ev.device()) - #print(ev.pointerType()) - #print(ev.pressure()) - - def drawAt(self, pos, ev=None): - pos = [int(pos.x()), int(pos.y())] - dk = self.drawKernel - kc = self.drawKernelCenter - sx = [0,dk.shape[0]] - sy = [0,dk.shape[1]] - tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] - ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] - - for i in [0,1]: - dx1 = -min(0, tx[i]) - dx2 = min(0, self.image.shape[0]-tx[i]) - tx[i] += dx1+dx2 - sx[i] += dx1+dx2 - - dy1 = -min(0, ty[i]) - dy2 = min(0, self.image.shape[1]-ty[i]) - ty[i] += dy1+dy2 - sy[i] += dy1+dy2 - - ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) - ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) - mask = self.drawMask - src = dk - - if isinstance(self.drawMode, Callable): - self.drawMode(dk, self.image, mask, ss, ts, ev) - else: - src = src[ss] - if self.drawMode == 'set': - if mask is not None: - mask = mask[ss] - self.image[ts] = self.image[ts] * (1-mask) + src * mask - else: - self.image[ts] = src - elif self.drawMode == 'add': - self.image[ts] += src - else: - raise Exception("Unknown draw mode '%s'" % self.drawMode) - self.updateImage() - - def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): - self.drawKernel = kernel - self.drawKernelCenter = center - self.drawMode = mode - self.drawMask = mask - - def removeClicked(self): - ## Send remove event only after we have exited the menu event handler - self.removeTimer = QtCore.QTimer() - self.removeTimer.timeout.connect(self.emitRemoveRequested) - self.removeTimer.start(0) - - def emitRemoveRequested(self): - self.removeTimer.timeout.disconnect(self.emitRemoveRequested) - self.sigRemoveRequested.emit(self) diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py deleted file mode 100644 index 74a8da9..0000000 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ /dev/null @@ -1,622 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from ..Point import Point -from .GraphicsObject import GraphicsObject -from .GraphicsItem import GraphicsItem -from .TextItem import TextItem -from .ViewBox import ViewBox -from .. import functions as fn -import numpy as np -import weakref - - -__all__ = ['InfiniteLine', 'InfLineLabel'] - - -class InfiniteLine(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - Displays a line of infinite length. - This line may be dragged to indicate a position in data coordinates. - - =============================== =================================================== - **Signals:** - sigDragged(self) - sigPositionChangeFinished(self) - sigPositionChanged(self) - sigclicked(self, ev) - =============================== =================================================== - """ - - sigDragged = QtCore.Signal(object) - sigPositionChangeFinished = QtCore.Signal(object) - sigPositionChanged = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - - def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None, - hoverPen=None, label=None, labelOpts=None, span=(0, 1), markers=None, - name=None): - """ - =============== ================================================================== - **Arguments:** - pos Position of the line. This can be a QPointF or a single value for - vertical/horizontal lines. - angle Angle of line in degrees. 0 is horizontal, 90 is vertical. - pen Pen to use when drawing line. Can be any arguments that are valid - for :func:`mkPen `. Default pen is transparent - yellow. - hoverPen Pen to use when the mouse cursor hovers over the line. - Only used when movable=True. - movable If True, the line can be dragged to a new position by the user. - bounds Optional [min, max] bounding values. Bounds are only valid if the - line is vertical or horizontal. - hoverPen Pen to use when drawing line when hovering over it. Can be any - arguments that are valid for :func:`mkPen `. - Default pen is red. - label Text to be displayed in a label attached to the line, or - None to show no label (default is None). May optionally - include formatting strings to display the line value. - labelOpts A dict of keyword arguments to use when constructing the - text label. See :class:`InfLineLabel`. - span Optional tuple (min, max) giving the range over the view to draw - the line. For example, with a vertical line, use span=(0.5, 1) - to draw only on the top half of the view. - markers List of (marker, position, size) tuples, one per marker to display - on the line. See the addMarker method. - name Name of the item - =============== ================================================================== - """ - self._boundingRect = None - - self._name = name - - GraphicsObject.__init__(self) - - if bounds is None: ## allowed value boundaries for orthogonal lines - self.maxRange = [None, None] - else: - self.maxRange = bounds - self.moving = False - self.setMovable(movable) - self.mouseHovering = False - self.p = [0, 0] - self.setAngle(angle) - - if pos is None: - pos = Point(0,0) - self.setPos(pos) - - if pen is None: - pen = (200, 200, 100) - self.setPen(pen) - - if hoverPen is None: - self.setHoverPen(color=(255,0,0), width=self.pen.width()) - else: - self.setHoverPen(hoverPen) - - self.span = span - self.currentPen = self.pen - - self.markers = [] - self._maxMarkerSize = 0 - if markers is not None: - for m in markers: - self.addMarker(*m) - - # Cache variables for managing bounds - self._endPoints = [0, 1] # - self._bounds = None - self._lastViewSize = None - - if label is not None: - labelOpts = {} if labelOpts is None else labelOpts - self.label = InfLineLabel(self, text=label, **labelOpts) - - def setMovable(self, m): - """Set whether the line is movable by the user.""" - self.movable = m - self.setAcceptHoverEvents(m) - - def setBounds(self, bounds): - """Set the (minimum, maximum) allowable values when dragging.""" - self.maxRange = bounds - self.setValue(self.value()) - - def bounds(self): - """Return the (minimum, maximum) values allowed when dragging. - """ - return self.maxRange[:] - - def setPen(self, *args, **kwargs): - """Set the pen for drawing the line. Allowable arguments are any that are valid - for :func:`mkPen `.""" - self.pen = fn.mkPen(*args, **kwargs) - if not self.mouseHovering: - self.currentPen = self.pen - self.update() - - def setHoverPen(self, *args, **kwargs): - """Set the pen for drawing the line while the mouse hovers over it. - Allowable arguments are any that are valid - for :func:`mkPen `. - - If the line is not movable, then hovering is also disabled. - - Added in version 0.9.9.""" - # If user did not supply a width, then copy it from pen - widthSpecified = ((len(args) == 1 and - (isinstance(args[0], QtGui.QPen) or - (isinstance(args[0], dict) and 'width' in args[0])) - ) or 'width' in kwargs) - self.hoverPen = fn.mkPen(*args, **kwargs) - if not widthSpecified: - self.hoverPen.setWidth(self.pen.width()) - - if self.mouseHovering: - self.currentPen = self.hoverPen - self.update() - - def addMarker(self, marker, position=0.5, size=10.0): - """Add a marker to be displayed on the line. - - ============= ========================================================= - **Arguments** - marker String indicating the style of marker to add: - ``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``, - ``'>|<'``, ``'^'``, ``'v'``, ``'o'`` - position Position (0.0-1.0) along the visible extent of the line - to place the marker. Default is 0.5. - size Size of the marker in pixels. Default is 10.0. - ============= ========================================================= - """ - path = QtGui.QPainterPath() - if marker == 'o': - path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) - if '<|' in marker: - p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)]) - path.addPolygon(p) - path.closeSubpath() - if '|>' in marker: - p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)]) - path.addPolygon(p) - path.closeSubpath() - if '>|' in marker: - p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)]) - path.addPolygon(p) - path.closeSubpath() - if '|<' in marker: - p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)]) - path.addPolygon(p) - path.closeSubpath() - if '^' in marker: - p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)]) - path.addPolygon(p) - path.closeSubpath() - if 'v' in marker: - p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)]) - path.addPolygon(p) - path.closeSubpath() - - self.markers.append((path, position, size)) - self._maxMarkerSize = max([m[2] / 2. for m in self.markers]) - self.update() - - def clearMarkers(self): - """ Remove all markers from this line. - """ - self.markers = [] - self._maxMarkerSize = 0 - self.update() - - def setAngle(self, angle): - """ - Takes angle argument in degrees. - 0 is horizontal; 90 is vertical. - - Note that the use of value() and setValue() changes if the line is - not vertical or horizontal. - """ - self.angle = angle #((angle+45) % 180) - 45 ## -45 <= angle < 135 - self.resetTransform() - self.setRotation(self.angle) - self.update() - - def setPos(self, pos): - - if isinstance(pos, (list, tuple, np.ndarray)) and not np.ndim(pos) == 0: - newPos = list(pos) - elif isinstance(pos, QtCore.QPointF): - newPos = [pos.x(), pos.y()] - else: - if self.angle == 90: - newPos = [pos, 0] - elif self.angle == 0: - newPos = [0, pos] - else: - raise Exception("Must specify 2D coordinate for non-orthogonal lines.") - - ## check bounds (only works for orthogonal lines) - if self.angle == 90: - if self.maxRange[0] is not None: - newPos[0] = max(newPos[0], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[0] = min(newPos[0], self.maxRange[1]) - elif self.angle == 0: - if self.maxRange[0] is not None: - newPos[1] = max(newPos[1], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[1] = min(newPos[1], self.maxRange[1]) - - if self.p != newPos: - self.p = newPos - self.viewTransformChanged() - GraphicsObject.setPos(self, Point(self.p)) - self.sigPositionChanged.emit(self) - - def getXPos(self): - return self.p[0] - - def getYPos(self): - return self.p[1] - - def getPos(self): - return self.p - - def value(self): - """Return the value of the line. Will be a single number for horizontal and - vertical lines, and a list of [x,y] values for diagonal lines.""" - if self.angle%180 == 0: - return self.getYPos() - elif self.angle%180 == 90: - return self.getXPos() - else: - return self.getPos() - - def setValue(self, v): - """Set the position of the line. If line is horizontal or vertical, v can be - a single value. Otherwise, a 2D coordinate must be specified (list, tuple and - QPointF are all acceptable).""" - self.setPos(v) - - ## broken in 4.7 - #def itemChange(self, change, val): - #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: - #self.updateLine() - #print "update", change - #print self.getBoundingParents() - #else: - #print "ignore", change - #return GraphicsObject.itemChange(self, change, val) - - def setSpan(self, mn, mx): - if self.span != (mn, mx): - self.span = (mn, mx) - self.update() - - def _computeBoundingRect(self): - #br = UIGraphicsItem.boundingRect(self) - vr = self.viewRect() # bounds of containing ViewBox mapped to local coords. - if vr is None: - return QtCore.QRectF() - - ## add a 4-pixel radius around the line for mouse interaction. - - px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line - if px is None: - px = 0 - pw = max(self.pen.width() / 2, self.hoverPen.width() / 2) - w = max(4, self._maxMarkerSize + pw) + 1 - w = w * px - br = QtCore.QRectF(vr) - br.setBottom(-w) - br.setTop(w) - - length = br.width() - left = br.left() + length * self.span[0] - right = br.left() + length * self.span[1] - br.setLeft(left) - br.setRight(right) - br = br.normalized() - - vs = self.getViewBox().size() - - if self._bounds != br or self._lastViewSize != vs: - self._bounds = br - self._lastViewSize = vs - self.prepareGeometryChange() - - self._endPoints = (left, right) - self._lastViewRect = vr - - return self._bounds - - def boundingRect(self): - if self._boundingRect is None: - self._boundingRect = self._computeBoundingRect() - return self._boundingRect - - def paint(self, p, *args): - p.setRenderHint(p.Antialiasing) - - left, right = self._endPoints - pen = self.currentPen - pen.setJoinStyle(QtCore.Qt.MiterJoin) - p.setPen(pen) - p.drawLine(Point(left, 0), Point(right, 0)) - - - if len(self.markers) == 0: - return - - # paint markers in native coordinate system - tr = p.transform() - p.resetTransform() - - start = tr.map(Point(left, 0)) - end = tr.map(Point(right, 0)) - up = tr.map(Point(left, 1)) - dif = end - start - length = Point(dif).length() - angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi - - p.translate(start) - p.rotate(angle) - - up = up - start - det = up.x() * dif.y() - dif.x() * up.y() - p.scale(1, 1 if det > 0 else -1) - - p.setBrush(fn.mkBrush(self.currentPen.color())) - #p.setPen(fn.mkPen(None)) - tr = p.transform() - for path, pos, size in self.markers: - p.setTransform(tr) - x = length * pos - p.translate(x, 0) - p.scale(size, size) - p.drawPath(path) - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - if axis == 0: - return None ## x axis should never be auto-scaled - else: - return (0,0) - - def mouseDragEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self.moving = True - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.startPosition = self.pos() - ev.accept() - - if not self.moving: - return - - self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) - self.sigDragged.emit(self) - if ev.isFinish(): - self.moving = False - self.sigPositionChangeFinished.emit(self) - - def mouseClickEvent(self, ev): - self.sigClicked.emit(self, ev) - if self.moving and ev.button() == QtCore.Qt.RightButton: - ev.accept() - self.setPos(self.startPosition) - self.moving = False - self.sigDragged.emit(self) - self.sigPositionChangeFinished.emit(self) - - def hoverEvent(self, ev): - if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton): - self.setMouseHover(True) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the item that the mouse is (not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - self.currentPen = self.hoverPen - else: - self.currentPen = self.pen - self.update() - - def viewTransformChanged(self): - """ - Called whenever the transformation matrix of the view has changed. - (eg, the view range has changed or the view was resized) - """ - self._boundingRect = None - GraphicsItem.viewTransformChanged(self) - - def setName(self, name): - self._name = name - - def name(self): - return self._name - - -class InfLineLabel(TextItem): - """ - A TextItem that attaches itself to an InfiniteLine. - - This class extends TextItem with the following features: - - * Automatically positions adjacent to the line at a fixed position along - the line and within the view box. - * Automatically reformats text when the line value has changed. - * Can optionally be dragged to change its location along the line. - * Optionally aligns to its parent line. - - =============== ================================================================== - **Arguments:** - line The InfiniteLine to which this label will be attached. - text String to display in the label. May contain a {value} formatting - string to display the current value of the line. - movable Bool; if True, then the label can be dragged along the line. - position Relative position (0.0-1.0) within the view to position the label - along the line. - anchors List of (x,y) pairs giving the text anchor positions that should - be used when the line is moved to one side of the view or the - other. This allows text to switch to the opposite side of the line - as it approaches the edge of the view. These are automatically - selected for some common cases, but may be specified if the - default values give unexpected results. - =============== ================================================================== - - All extra keyword arguments are passed to TextItem. A particularly useful - option here is to use `rotateAxis=(1, 0)`, which will cause the text to - be automatically rotated parallel to the line. - """ - def __init__(self, line, text="", movable=False, position=0.5, anchors=None, **kwds): - self.line = line - self.movable = movable - self.moving = False - self.orthoPos = position # text will always be placed on the line at a position relative to view bounds - self.format = text - self.line.sigPositionChanged.connect(self.valueChanged) - self._endpoints = (None, None) - if anchors is None: - # automatically pick sensible anchors - rax = kwds.get('rotateAxis', None) - if rax is not None: - if tuple(rax) == (1,0): - anchors = [(0.5, 0), (0.5, 1)] - else: - anchors = [(0, 0.5), (1, 0.5)] - else: - if line.angle % 180 == 0: - anchors = [(0.5, 0), (0.5, 1)] - else: - anchors = [(0, 0.5), (1, 0.5)] - - self.anchors = anchors - TextItem.__init__(self, **kwds) - self.setParentItem(line) - self.valueChanged() - - def valueChanged(self): - if not self.isVisible(): - return - value = self.line.value() - self.setText(self.format.format(value=value)) - self.updatePosition() - - def getEndpoints(self): - # calculate points where line intersects view box - # (in line coordinates) - if self._endpoints[0] is None: - lr = self.line.boundingRect() - pt1 = Point(lr.left(), 0) - pt2 = Point(lr.right(), 0) - - if self.line.angle % 90 != 0: - # more expensive to find text position for oblique lines. - view = self.getViewBox() - if not self.isVisible() or not isinstance(view, ViewBox): - # not in a viewbox, skip update - return (None, None) - p = QtGui.QPainterPath() - p.moveTo(pt1) - p.lineTo(pt2) - p = self.line.itemTransform(view)[0].map(p) - vr = QtGui.QPainterPath() - vr.addRect(view.boundingRect()) - paths = vr.intersected(p).toSubpathPolygons(QtGui.QTransform()) - if len(paths) > 0: - l = list(paths[0]) - pt1 = self.line.mapFromItem(view, l[0]) - pt2 = self.line.mapFromItem(view, l[1]) - self._endpoints = (pt1, pt2) - return self._endpoints - - def updatePosition(self): - # update text position to relative view location along line - self._endpoints = (None, None) - pt1, pt2 = self.getEndpoints() - if pt1 is None: - return - pt = pt2 * self.orthoPos + pt1 * (1-self.orthoPos) - self.setPos(pt) - - # update anchor to keep text visible as it nears the view box edge - vr = self.line.viewRect() - if vr is not None: - self.setAnchor(self.anchors[0 if vr.center().y() < 0 else 1]) - - def setVisible(self, v): - TextItem.setVisible(self, v) - if v: - self.valueChanged() - - def setMovable(self, m): - """Set whether this label is movable by dragging along the line. - """ - self.movable = m - self.setAcceptHoverEvents(m) - - def setPosition(self, p): - """Set the relative position (0.0-1.0) of this label within the view box - and along the line. - - For horizontal (angle=0) and vertical (angle=90) lines, a value of 0.0 - places the text at the bottom or left of the view, respectively. - """ - self.orthoPos = p - self.updatePosition() - - def setFormat(self, text): - """Set the text format string for this label. - - May optionally contain "{value}" to include the lines current value - (the text will be reformatted whenever the line is moved). - """ - self.format = text - self.valueChanged() - - def mouseDragEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self._moving = True - self._cursorOffset = self._posToRel(ev.buttonDownPos()) - self._startPosition = self.orthoPos - ev.accept() - - if not self._moving: - return - - rel = self._posToRel(ev.pos()) - self.orthoPos = np.clip(self._startPosition + rel - self._cursorOffset, 0, 1) - self.updatePosition() - if ev.isFinish(): - self._moving = False - - def mouseClickEvent(self, ev): - if self.moving and ev.button() == QtCore.Qt.RightButton: - ev.accept() - self.orthoPos = self._startPosition - self.moving = False - - def hoverEvent(self, ev): - if not ev.isExit() and self.movable: - ev.acceptDrags(QtCore.Qt.LeftButton) - - def viewTransformChanged(self): - GraphicsItem.viewTransformChanged(self) - self.updatePosition() - TextItem.viewTransformChanged(self) - - def _posToRel(self, pos): - # convert local position to relative position along line between view bounds - pt1, pt2 = self.getEndpoints() - if pt1 is None: - return 0 - view = self.getViewBox() - pos = self.mapToParent(pos) - return (pos.x() - pt1.x()) / (pt2.x()-pt1.x()) diff --git a/pyqtgraph/graphicsItems/IsocurveItem.py b/pyqtgraph/graphicsItems/IsocurveItem.py deleted file mode 100644 index 03ebc69..0000000 --- a/pyqtgraph/graphicsItems/IsocurveItem.py +++ /dev/null @@ -1,111 +0,0 @@ -from .. import getConfigOption -from .GraphicsObject import * -from .. import functions as fn -from ..Qt import QtGui, QtCore - - -class IsocurveItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - Item displaying an isocurve of a 2D array. To align this item correctly with an - ImageItem, call ``isocurve.setParentItem(image)``. - """ - def __init__(self, data=None, level=0, pen='w', axisOrder=None): - """ - Create a new isocurve item. - - ============== =============================================================== - **Arguments:** - data A 2-dimensional ndarray. Can be initialized as None, and set - later using :func:`setData ` - level The cutoff value at which to draw the isocurve. - pen The color of the curve item. Can be anything valid for - :func:`mkPen ` - axisOrder May be either 'row-major' or 'col-major'. By default this uses - the ``imageAxisOrder`` - :ref:`global configuration option `. - ============== =============================================================== - """ - GraphicsObject.__init__(self) - - self.level = level - self.data = None - self.path = None - self.axisOrder = getConfigOption('imageAxisOrder') if axisOrder is None else axisOrder - self.setPen(pen) - self.setData(data, level) - - def setData(self, data, level=None): - """ - Set the data/image to draw isocurves for. - - ============== ======================================================================== - **Arguments:** - data A 2-dimensional ndarray. - level The cutoff value at which to draw the curve. If level is not specified, - the previously set level is used. - ============== ======================================================================== - """ - if level is None: - level = self.level - self.level = level - self.data = data - self.path = None - self.prepareGeometryChange() - self.update() - - def setLevel(self, level): - """Set the level at which the isocurve is drawn.""" - self.level = level - self.path = None - self.prepareGeometryChange() - self.update() - - def setPen(self, *args, **kwargs): - """Set the pen used to draw the isocurve. Arguments can be any that are valid - for :func:`mkPen `""" - self.pen = fn.mkPen(*args, **kwargs) - self.update() - - def setBrush(self, *args, **kwargs): - """Set the brush used to draw the isocurve. Arguments can be any that are valid - for :func:`mkBrush `""" - self.brush = fn.mkBrush(*args, **kwargs) - self.update() - - def updateLines(self, data, level): - self.setData(data, level) - - def boundingRect(self): - if self.data is None: - return QtCore.QRectF() - if self.path is None: - self.generatePath() - return self.path.boundingRect() - - def generatePath(self): - if self.data is None: - self.path = None - return - - if self.axisOrder == 'row-major': - data = self.data.T - else: - data = self.data - - lines = fn.isocurve(data, self.level, connected=True, extendToEdge=True) - self.path = QtGui.QPainterPath() - for line in lines: - self.path.moveTo(*line[0]) - for p in line[1:]: - self.path.lineTo(*p) - - def paint(self, p, *args): - if self.data is None: - return - if self.path is None: - self.generatePath() - p.setPen(self.pen) - p.drawPath(self.path) - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ItemGroup.py b/pyqtgraph/graphicsItems/ItemGroup.py deleted file mode 100644 index 4eb0ee0..0000000 --- a/pyqtgraph/graphicsItems/ItemGroup.py +++ /dev/null @@ -1,23 +0,0 @@ -from ..Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject - -__all__ = ['ItemGroup'] -class ItemGroup(GraphicsObject): - """ - Replacement for QGraphicsItemGroup - """ - - def __init__(self, *args): - GraphicsObject.__init__(self, *args) - if hasattr(self, "ItemHasNoContents"): - self.setFlag(self.ItemHasNoContents) - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def addItem(self, item): - item.setParentItem(self) - diff --git a/pyqtgraph/graphicsItems/LabelItem.py b/pyqtgraph/graphicsItems/LabelItem.py deleted file mode 100644 index ba8b72f..0000000 --- a/pyqtgraph/graphicsItems/LabelItem.py +++ /dev/null @@ -1,142 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import functions as fn -from .GraphicsWidget import GraphicsWidget -from .GraphicsWidgetAnchor import GraphicsWidgetAnchor -from .. import getConfigOption - - -__all__ = ['LabelItem'] - -class LabelItem(GraphicsWidget, GraphicsWidgetAnchor): - """ - GraphicsWidget displaying text. - Used mainly as axis labels, titles, etc. - - Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem - """ - - - def __init__(self, text=' ', parent=None, angle=0, **args): - GraphicsWidget.__init__(self, parent) - GraphicsWidgetAnchor.__init__(self) - self.item = QtGui.QGraphicsTextItem(self) - self.opts = { - 'color': None, - 'justify': 'center' - } - self.opts.update(args) - self._sizeHint = {} - self.setText(text) - self.setAngle(angle) - - def setAttr(self, attr, value): - """Set default text properties. See setText() for accepted parameters.""" - self.opts[attr] = value - - def setText(self, text, **args): - """Set the text and text properties in the label. Accepts optional arguments for auto-generating - a CSS style string: - - ==================== ============================== - **Style Arguments:** - color (str) example: 'CCFF00' - size (str) example: '8pt' - bold (bool) - italic (bool) - ==================== ============================== - """ - self.text = text - opts = self.opts - for k in args: - opts[k] = args[k] - - optlist = [] - - color = self.opts['color'] - if color is None: - color = getConfigOption('foreground') - color = fn.mkColor(color) - optlist.append('color: #' + fn.colorStr(color)[:6]) - if 'size' in opts: - optlist.append('font-size: ' + opts['size']) - if 'bold' in opts and opts['bold'] in [True, False]: - optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) - if 'italic' in opts and opts['italic'] in [True, False]: - optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) - full = "%s" % ('; '.join(optlist), text) - #print full - self.item.setHtml(full) - self.updateMin() - self.resizeEvent(None) - self.updateGeometry() - - def resizeEvent(self, ev): - #c1 = self.boundingRect().center() - #c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() - #dif = c1 - c2 - #self.item.moveBy(dif.x(), dif.y()) - #print c1, c2, dif, self.item.pos() - self.item.setPos(0,0) - bounds = self.itemRect() - left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0)) - rect = self.rect() - - if self.opts['justify'] == 'left': - if left.x() != 0: - bounds.moveLeft(rect.left()) - if left.y() < 0: - bounds.moveTop(rect.top()) - elif left.y() > 0: - bounds.moveBottom(rect.bottom()) - - elif self.opts['justify'] == 'center': - bounds.moveCenter(rect.center()) - #bounds = self.itemRect() - #self.item.setPos(self.width()/2. - bounds.width()/2., 0) - elif self.opts['justify'] == 'right': - if left.x() != 0: - bounds.moveRight(rect.right()) - if left.y() < 0: - bounds.moveBottom(rect.bottom()) - elif left.y() > 0: - bounds.moveTop(rect.top()) - #bounds = self.itemRect() - #self.item.setPos(self.width() - bounds.width(), 0) - - self.item.setPos(bounds.topLeft() - self.itemRect().topLeft()) - self.updateMin() - - def setAngle(self, angle): - self.angle = angle - self.item.resetTransform() - self.item.setRotation(angle) - self.updateMin() - - - def updateMin(self): - bounds = self.itemRect() - self.setMinimumWidth(bounds.width()) - self.setMinimumHeight(bounds.height()) - - self._sizeHint = { - QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), - QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), - QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2), - QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? - } - self.updateGeometry() - - def sizeHint(self, hint, constraint): - if hint not in self._sizeHint: - return QtCore.QSizeF(0, 0) - return QtCore.QSizeF(*self._sizeHint[hint]) - - def itemRect(self): - return self.item.mapRectToParent(self.item.boundingRect()) - - #def paint(self, p, *args): - #p.setPen(fn.mkPen('r')) - #p.drawRect(self.rect()) - #p.setPen(fn.mkPen('g')) - #p.drawRect(self.itemRect()) - diff --git a/pyqtgraph/graphicsItems/LegendItem.py b/pyqtgraph/graphicsItems/LegendItem.py deleted file mode 100644 index 7b46849..0000000 --- a/pyqtgraph/graphicsItems/LegendItem.py +++ /dev/null @@ -1,392 +0,0 @@ -# -*- coding: utf-8 -*- -import math - -from .GraphicsWidget import GraphicsWidget -from .LabelItem import LabelItem -from ..Qt import QtGui, QtCore -from .. import functions as fn -from ..icons import invisibleEye -from ..Point import Point -from .ScatterPlotItem import ScatterPlotItem, drawSymbol -from .PlotDataItem import PlotDataItem -from .GraphicsWidgetAnchor import GraphicsWidgetAnchor -from .BarGraphItem import BarGraphItem - -__all__ = ['LegendItem', 'ItemSample'] - - -class LegendItem(GraphicsWidget, GraphicsWidgetAnchor): - """ - Displays a legend used for describing the contents of a plot. - - LegendItems are most commonly created by calling :meth:`PlotItem.addLegend - `. - - Note that this item should *not* be added directly to a PlotItem (via - :meth:`PlotItem.addItem `). Instead, make it a - direct descendant of the PlotItem:: - - legend.setParentItem(plotItem) - - """ - - def __init__(self, size=None, offset=None, horSpacing=25, verSpacing=0, - pen=None, brush=None, labelTextColor=None, frame=True, - labelTextSize='9pt', colCount=1, sampleType=None, **kwargs): - """ - ============== =============================================================== - **Arguments:** - size Specifies the fixed size (width, height) of the legend. If - this argument is omitted, the legend will automatically resize - to fit its contents. - offset Specifies the offset position relative to the legend's parent. - Positive values offset from the left or top; negative values - offset from the right or bottom. If offset is None, the - legend must be anchored manually by calling anchor() or - positioned by calling setPos(). - horSpacing Specifies the spacing between the line symbol and the label. - verSpacing Specifies the spacing between individual entries of the legend - vertically. (Can also be negative to have them really close) - pen Pen to use when drawing legend border. Any single argument - accepted by :func:`mkPen ` is allowed. - brush QBrush to use as legend background filling. Any single argument - accepted by :func:`mkBrush ` is allowed. - labelTextColor Pen to use when drawing legend text. Any single argument - accepted by :func:`mkPen ` is allowed. - labelTextSize Size to use when drawing legend text. Accepts CSS style - string arguments, e.g. '9pt'. - colCount Specifies the integer number of columns that the legend should - be divided into. The number of rows will be calculated - based on this argument. This is useful for plots with many - curves displayed simultaneously. Default: 1 column. - sampleType Customizes the item sample class of the `LegendItem`. - ============== =============================================================== - - """ - GraphicsWidget.__init__(self) - GraphicsWidgetAnchor.__init__(self) - self.setFlag(self.ItemIgnoresTransformations) - self.layout = QtGui.QGraphicsGridLayout() - self.layout.setVerticalSpacing(verSpacing) - self.layout.setHorizontalSpacing(horSpacing) - - self.setLayout(self.layout) - self.items = [] - self.size = size - self.offset = offset - self.frame = frame - self.columnCount = colCount - self.rowCount = 1 - if size is not None: - self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1])) - - if sampleType is not None: - if not issubclass(sampleType, GraphicsWidget): - raise RuntimeError("Only classes of type `GraphicsWidgets` " - "are allowed as `sampleType`") - self.sampleType = sampleType - else: - self.sampleType = ItemSample - - self.opts = { - 'pen': fn.mkPen(pen), - 'brush': fn.mkBrush(brush), - 'labelTextColor': labelTextColor, - 'labelTextSize': labelTextSize, - 'offset': offset, - } - self.opts.update(kwargs) - - def setSampleType(self, sample): - """Set the new sample item claspes""" - if sample is self.sampleType: - return - - # Clear the legend, but before create a list of items - items = list(self.items) - self.sampleType = sample - self.clear() - - # Refill the legend with the item list and new sample item - for sample, label in items: - plot_item = sample.item - plot_name = label.text - self.addItem(plot_item, plot_name) - - self.updateSize() - - def offset(self): - """Get the offset position relative to the parent.""" - return self.opts['offset'] - - def setOffset(self, offset): - """Set the offset position relative to the parent.""" - self.opts['offset'] = offset - - offset = Point(self.opts['offset']) - anchorx = 1 if offset[0] <= 0 else 0 - anchory = 1 if offset[1] <= 0 else 0 - anchor = (anchorx, anchory) - self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) - - def pen(self): - """Get the QPen used to draw the border around the legend.""" - return self.opts['pen'] - - def setPen(self, *args, **kargs): - """Set the pen used to draw a border around the legend. - - Accepts the same arguments as :func:`~pyqtgraph.mkPen`. - """ - pen = fn.mkPen(*args, **kargs) - self.opts['pen'] = pen - - self.update() - - def brush(self): - """Get the QBrush used to draw the legend background.""" - return self.opts['brush'] - - def setBrush(self, *args, **kargs): - """Set the brush used to draw the legend background. - - Accepts the same arguments as :func:`~pyqtgraph.mkBrush`. - """ - brush = fn.mkBrush(*args, **kargs) - if self.opts['brush'] == brush: - return - self.opts['brush'] = brush - - self.update() - - def labelTextColor(self): - """Get the QColor used for the item labels.""" - return self.opts['labelTextColor'] - - def setLabelTextColor(self, *args, **kargs): - """Set the color of the item labels. - - Accepts the same arguments as :func:`~pyqtgraph.mkColor`. - """ - self.opts['labelTextColor'] = fn.mkColor(*args, **kargs) - for sample, label in self.items: - label.setAttr('color', self.opts['labelTextColor']) - - self.update() - - def labelTextSize(self): - """Get the `labelTextSize` used for the item labels.""" - return self.opts['labelTextSize'] - - def setLabelTextSize(self, size): - """Set the `size` of the item labels. - - Accepts the CSS style string arguments, e.g. '8pt'. - """ - self.opts['labelTextSize'] = size - for _, label in self.items: - label.setAttr('size', self.opts['labelTextSize']) - - self.update() - - def setParentItem(self, p): - """Set the parent.""" - ret = GraphicsWidget.setParentItem(self, p) - if self.opts['offset'] is not None: - offset = Point(self.opts['offset']) - anchorx = 1 if offset[0] <= 0 else 0 - anchory = 1 if offset[1] <= 0 else 0 - anchor = (anchorx, anchory) - self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) - return ret - - def addItem(self, item, name): - """ - Add a new entry to the legend. - - ============== ======================================================== - **Arguments:** - item A :class:`~pyqtgraph.PlotDataItem` from which the line - and point style of the item will be determined or an - instance of ItemSample (or a subclass), allowing the - item display to be customized. - title The title to display for this item. Simple HTML allowed. - ============== ======================================================== - """ - label = LabelItem(name, color=self.opts['labelTextColor'], - justify='left', size=self.opts['labelTextSize']) - if isinstance(item, self.sampleType): - sample = item - else: - sample = self.sampleType(item) - self.items.append((sample, label)) - self._addItemToLayout(sample, label) - self.updateSize() - - def _addItemToLayout(self, sample, label): - col = self.layout.columnCount() - row = self.layout.rowCount() - if row: - row -= 1 - nCol = self.columnCount * 2 - # FIRST ROW FULL - if col == nCol: - for col in range(0, nCol, 2): - # FIND RIGHT COLUMN - if not self.layout.itemAt(row, col): - break - else: - if col + 2 == nCol: - # MAKE NEW ROW - col = 0 - row += 1 - self.layout.addItem(sample, row, col) - self.layout.addItem(label, row, col + 1) - # Keep rowCount in sync with the number of rows if items are added - self.rowCount = max(self.rowCount, row + 1) - - def setColumnCount(self, columnCount): - """change the orientation of all items of the legend - """ - if columnCount != self.columnCount: - self.columnCount = columnCount - - self.rowCount = math.ceil(len(self.items) / columnCount) - for i in range(self.layout.count() - 1, -1, -1): - self.layout.removeAt(i) # clear layout - for sample, label in self.items: - self._addItemToLayout(sample, label) - self.updateSize() - - def getLabel(self, plotItem): - """Return the labelItem inside the legend for a given plotItem - - The label-text can be changed via labelItem.setText - """ - out = [(it, lab) for it, lab in self.items if it.item == plotItem] - try: - return out[0][1] - except IndexError: - return None - - def removeItem(self, item): - """Removes one item from the legend. - - ============== ======================================================== - **Arguments:** - item The item to remove or its name. - ============== ======================================================== - """ - for sample, label in self.items: - if sample.item is item or label.text == item: - self.items.remove((sample, label)) # remove from itemlist - self.layout.removeItem(sample) # remove from layout - sample.close() # remove from drawing - self.layout.removeItem(label) - label.close() - self.updateSize() # redraq box - return # return after first match - - def clear(self): - """Remove all items from the legend.""" - for sample, label in self.items: - self.layout.removeItem(sample) - sample.close() - self.layout.removeItem(label) - label.close() - - self.items = [] - self.updateSize() - - def updateSize(self): - if self.size is not None: - return - height = 0 - width = 0 - for row in range(self.layout.rowCount()): - row_height = 0 - col_width = 0 - for col in range(self.layout.columnCount()): - item = self.layout.itemAt(row, col) - if item: - col_width += item.width() + 3 - row_height = max(row_height, item.height()) - width = max(width, col_width) - height += row_height - self.setGeometry(0, 0, width, height) - return - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.width(), self.height()) - - def paint(self, p, *args): - if self.frame: - p.setPen(self.opts['pen']) - p.setBrush(self.opts['brush']) - p.drawRect(self.boundingRect()) - - def hoverEvent(self, ev): - ev.acceptDrags(QtCore.Qt.LeftButton) - - def mouseDragEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - dpos = ev.pos() - ev.lastPos() - self.autoAnchor(self.pos() + dpos) - - -class ItemSample(GraphicsWidget): - """Class responsible for drawing a single item in a LegendItem (sans label) - """ - - def __init__(self, item): - GraphicsWidget.__init__(self) - self.item = item - - def boundingRect(self): - return QtCore.QRectF(0, 0, 20, 20) - - def paint(self, p, *args): - opts = self.item.opts - if opts.get('antialias'): - p.setRenderHint(p.Antialiasing) - - visible = self.item.isVisible() - if not visible: - icon = invisibleEye.qicon - p.drawPixmap(QtCore.QPoint(1, 1), icon.pixmap(18, 18)) - return - - if not isinstance(self.item, ScatterPlotItem): - p.setPen(fn.mkPen(opts['pen'])) - p.drawLine(0, 11, 20, 11) - - if (opts.get('fillLevel', None) is not None and - opts.get('fillBrush', None) is not None): - p.setBrush(fn.mkBrush(opts['fillBrush'])) - p.setPen(fn.mkPen(opts['fillBrush'])) - p.drawPolygon(QtGui.QPolygonF( - [QtCore.QPointF(2, 18), QtCore.QPointF(18, 2), - QtCore.QPointF(18, 18)])) - - symbol = opts.get('symbol', None) - if symbol is not None: - if isinstance(self.item, PlotDataItem): - opts = self.item.scatter.opts - p.translate(10, 10) - drawSymbol(p, symbol, opts['size'], fn.mkPen(opts['pen']), - fn.mkBrush(opts['brush'])) - - if isinstance(self.item, BarGraphItem): - p.setBrush(fn.mkBrush(opts['brush'])) - p.drawRect(QtCore.QRectF(2, 2, 18, 18)) - - def mouseClickEvent(self, event): - """Use the mouseClick event to toggle the visibility of the plotItem - """ - if event.button() == QtCore.Qt.LeftButton: - visible = self.item.isVisible() - self.item.setVisible(not visible) - - event.accept() - self.update() diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/pyqtgraph/graphicsItems/LinearRegionItem.py deleted file mode 100644 index 6f88861..0000000 --- a/pyqtgraph/graphicsItems/LinearRegionItem.py +++ /dev/null @@ -1,304 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject -from .InfiniteLine import InfiniteLine -from .. import functions as fn -from .. import debug as debug - -__all__ = ['LinearRegionItem'] - -class LinearRegionItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - Used for marking a horizontal or vertical region in plots. - The region can be dragged and is bounded by lines which can be dragged individually. - - =============================== ============================================================================= - **Signals:** - sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) - and when the region is changed programatically. - sigRegionChanged(self) Emitted while the user is dragging the region (or one of its lines) - and when the region is changed programatically. - =============================== ============================================================================= - """ - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - Vertical = 0 - Horizontal = 1 - _orientation_axis = { - Vertical: 0, - Horizontal: 1, - 'vertical': 0, - 'horizontal': 1, - } - - def __init__(self, values=(0, 1), orientation='vertical', brush=None, pen=None, - hoverBrush=None, hoverPen=None, movable=True, bounds=None, - span=(0, 1), swapMode='sort'): - """Create a new LinearRegionItem. - - ============== ===================================================================== - **Arguments:** - values A list of the positions of the lines in the region. These are not - limits; limits can be set by specifying bounds. - orientation Options are 'vertical' or 'horizontal', indicating the - The default is 'vertical', indicating that the - brush Defines the brush that fills the region. Can be any arguments that - are valid for :func:`mkBrush `. Default is - transparent blue. - pen The pen to use when drawing the lines that bound the region. - hoverBrush The brush to use when the mouse is hovering over the region. - hoverPen The pen to use when the mouse is hovering over the region. - movable If True, the region and individual lines are movable by the user; if - False, they are static. - bounds Optional [min, max] bounding values for the region - span Optional [min, max] giving the range over the view to draw - the region. For example, with a vertical line, use - ``span=(0.5, 1)`` to draw only on the top half of the - view. - swapMode Sets the behavior of the region when the lines are moved such that - their order reverses. "block" means the user cannot drag - one line past the other. "push" causes both lines to be - moved if one would cross the other. "sort" means that - lines may trade places, but the output of getRegion - always gives the line positions in ascending order. None - means that no attempt is made to handle swapped line - positions. The default is "sort". - ============== ===================================================================== - """ - - GraphicsObject.__init__(self) - self.orientation = orientation - self.bounds = QtCore.QRectF() - self.blockLineSignal = False - self.moving = False - self.mouseHovering = False - self.span = span - self.swapMode = swapMode - self._bounds = None - - # note LinearRegionItem.Horizontal and LinearRegionItem.Vertical - # are kept for backward compatibility. - lineKwds = dict( - movable=movable, - bounds=bounds, - span=span, - pen=pen, - hoverPen=hoverPen, - ) - - if orientation in ('horizontal', LinearRegionItem.Horizontal): - self.lines = [ - # rotate lines to 180 to preserve expected line orientation - # with respect to region. This ensures that placing a '<|' - # marker on lines[0] causes it to point left in vertical mode - # and down in horizontal mode. - InfiniteLine(QtCore.QPointF(0, values[0]), angle=0, **lineKwds), - InfiniteLine(QtCore.QPointF(0, values[1]), angle=0, **lineKwds)] - tr = QtGui.QTransform.fromScale(1, -1) - self.lines[0].setTransform(tr, True) - self.lines[1].setTransform(tr, True) - elif orientation in ('vertical', LinearRegionItem.Vertical): - self.lines = [ - InfiniteLine(QtCore.QPointF(values[0], 0), angle=90, **lineKwds), - InfiniteLine(QtCore.QPointF(values[1], 0), angle=90, **lineKwds)] - else: - raise Exception("Orientation must be 'vertical' or 'horizontal'.") - - for l in self.lines: - l.setParentItem(self) - l.sigPositionChangeFinished.connect(self.lineMoveFinished) - self.lines[0].sigPositionChanged.connect(self._line0Moved) - self.lines[1].sigPositionChanged.connect(self._line1Moved) - - if brush is None: - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) - self.setBrush(brush) - - if hoverBrush is None: - c = self.brush.color() - c.setAlpha(min(c.alpha() * 2, 255)) - hoverBrush = fn.mkBrush(c) - self.setHoverBrush(hoverBrush) - - self.setMovable(movable) - - def getRegion(self): - """Return the values at the edges of the region.""" - r = (self.lines[0].value(), self.lines[1].value()) - if self.swapMode == 'sort': - return (min(r), max(r)) - else: - return r - - def setRegion(self, rgn): - """Set the values for the edges of the region. - - ============== ============================================== - **Arguments:** - rgn A list or tuple of the lower and upper values. - ============== ============================================== - """ - if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: - return - self.blockLineSignal = True - self.lines[0].setValue(rgn[0]) - self.blockLineSignal = False - self.lines[1].setValue(rgn[1]) - #self.blockLineSignal = False - self.lineMoved(0) - self.lineMoved(1) - self.lineMoveFinished() - - def setBrush(self, *br, **kargs): - """Set the brush that fills the region. Can have any arguments that are valid - for :func:`mkBrush `. - """ - self.brush = fn.mkBrush(*br, **kargs) - self.currentBrush = self.brush - - def setHoverBrush(self, *br, **kargs): - """Set the brush that fills the region when the mouse is hovering over. - Can have any arguments that are valid - for :func:`mkBrush `. - """ - self.hoverBrush = fn.mkBrush(*br, **kargs) - - def setBounds(self, bounds): - """Optional [min, max] bounding values for the region. To have no bounds on the - region use [None, None]. - Does not affect the current position of the region unless it is outside the new bounds. - See :func:`setRegion ` to set the position - of the region.""" - for l in self.lines: - l.setBounds(bounds) - - def setMovable(self, m): - """Set lines to be movable by the user, or not. If lines are movable, they will - also accept HoverEvents.""" - for l in self.lines: - l.setMovable(m) - self.movable = m - self.setAcceptHoverEvents(m) - - def setSpan(self, mn, mx): - if self.span == (mn, mx): - return - self.span = (mn, mx) - self.lines[0].setSpan(mn, mx) - self.lines[1].setSpan(mn, mx) - self.update() - - def boundingRect(self): - br = QtCore.QRectF(self.viewRect()) # bounds of containing ViewBox mapped to local coords. - - rng = self.getRegion() - if self.orientation in ('vertical', LinearRegionItem.Vertical): - br.setLeft(rng[0]) - br.setRight(rng[1]) - length = br.height() - br.setBottom(br.top() + length * self.span[1]) - br.setTop(br.top() + length * self.span[0]) - else: - br.setTop(rng[0]) - br.setBottom(rng[1]) - length = br.width() - br.setRight(br.left() + length * self.span[1]) - br.setLeft(br.left() + length * self.span[0]) - - br = br.normalized() - - if self._bounds != br: - self._bounds = br - self.prepareGeometryChange() - - return br - - def paint(self, p, *args): - profiler = debug.Profiler() - p.setBrush(self.currentBrush) - p.setPen(fn.mkPen(None)) - p.drawRect(self.boundingRect()) - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - if axis == self._orientation_axis[self.orientation]: - return self.getRegion() - else: - return None - - def lineMoved(self, i): - if self.blockLineSignal: - return - - # lines swapped - if self.lines[0].value() > self.lines[1].value(): - if self.swapMode == 'block': - self.lines[i].setValue(self.lines[1-i].value()) - elif self.swapMode == 'push': - self.lines[1-i].setValue(self.lines[i].value()) - - self.prepareGeometryChange() - self.sigRegionChanged.emit(self) - - def _line0Moved(self): - self.lineMoved(0) - - def _line1Moved(self): - self.lineMoved(1) - - def lineMoveFinished(self): - self.sigRegionChangeFinished.emit(self) - - def mouseDragEvent(self, ev): - if not self.movable or ev.button() != QtCore.Qt.LeftButton: - return - ev.accept() - - if ev.isStart(): - bdp = ev.buttonDownPos() - self.cursorOffsets = [l.pos() - bdp for l in self.lines] - self.startPositions = [l.pos() for l in self.lines] - self.moving = True - - if not self.moving: - return - - self.lines[0].blockSignals(True) # only want to update once - for i, l in enumerate(self.lines): - l.setPos(self.cursorOffsets[i] + ev.pos()) - self.lines[0].blockSignals(False) - self.prepareGeometryChange() - - if ev.isFinish(): - self.moving = False - self.sigRegionChangeFinished.emit(self) - else: - self.sigRegionChanged.emit(self) - - def mouseClickEvent(self, ev): - if self.moving and ev.button() == QtCore.Qt.RightButton: - ev.accept() - for i, l in enumerate(self.lines): - l.setPos(self.startPositions[i]) - self.moving = False - self.sigRegionChanged.emit(self) - self.sigRegionChangeFinished.emit(self) - - def hoverEvent(self, ev): - if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - self.setMouseHover(True) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the item that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - self.currentBrush = self.hoverBrush - else: - self.currentBrush = self.brush - self.update() diff --git a/pyqtgraph/graphicsItems/MultiPlotItem.py b/pyqtgraph/graphicsItems/MultiPlotItem.py deleted file mode 100644 index fb045af..0000000 --- a/pyqtgraph/graphicsItems/MultiPlotItem.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MultiPlotItem.py - Graphics item used for displaying an array of PlotItems -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" -from . import GraphicsLayout -from ..metaarray import * - -__all__ = ['MultiPlotItem'] - - -class MultiPlotItem(GraphicsLayout.GraphicsLayout): - """ - :class:`~pyqtgraph.GraphicsLayout` that automatically generates a grid of - plots from a MetaArray. - - .. seealso:: :class:`~pyqtgraph.MultiPlotWidget`: Widget containing a MultiPlotItem - """ - - def __init__(self, *args, **kwds): - GraphicsLayout.GraphicsLayout.__init__(self, *args, **kwds) - self.plots = [] - - def plot(self, data, **plotArgs): - """Plot the data from a MetaArray with each array column as a separate - :class:`~pyqtgraph.PlotItem`. - - Axis labels are automatically extracted from the array info. - - ``plotArgs`` are passed to :meth:`PlotItem.plot - `. - """ - #self.layout.clear() - - if hasattr(data, 'implements') and data.implements('MetaArray'): - if data.ndim != 2: - raise Exception("MultiPlot currently only accepts 2D MetaArray.") - ic = data.infoCopy() - ax = 0 - for i in [0, 1]: - if 'cols' in ic[i]: - ax = i - break - #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) - for i in range(data.shape[ax]): - pi = self.addPlot() - self.nextRow() - sl = [slice(None)] * 2 - sl[ax] = i - pi.plot(data[tuple(sl)], **plotArgs) - #self.layout.addItem(pi, i, 0) - self.plots.append((pi, i, 0)) - info = ic[ax]['cols'][i] - title = info.get('title', info.get('name', None)) - units = info.get('units', None) - pi.setLabel('left', text=title, units=units) - info = ic[1-ax] - title = info.get('title', info.get('name', None)) - units = info.get('units', None) - pi.setLabel('bottom', text=title, units=units) - else: - raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) - - def close(self): - for p in self.plots: - p[0].close() - self.plots = None - self.clear() diff --git a/pyqtgraph/graphicsItems/NonUniformImage.py b/pyqtgraph/graphicsItems/NonUniformImage.py deleted file mode 100644 index 2b69776..0000000 --- a/pyqtgraph/graphicsItems/NonUniformImage.py +++ /dev/null @@ -1,130 +0,0 @@ -from ..Qt import QtGui, QtCore -import numpy as np -from ..colormap import ColorMap -from .GraphicsObject import GraphicsObject -from .. import mkBrush, mkPen -from .. import functions as fn - - -class NonUniformImage(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - GraphicsObject displaying an image with non-uniform sample points. It's - commonly used to display 2-d or slices of higher dimensional data that - have a regular but non-uniform grid e.g. measurements or simulation results. - """ - - def __init__(self, x, y, z, border=None): - - GraphicsObject.__init__(self) - - # convert to numpy arrays - x = np.asarray(x, dtype=np.float64) - y = np.asarray(y, dtype=np.float64) - z = np.asarray(z, dtype=np.float64) - - if x.ndim != 1 or y.ndim != 1: - raise Exception("x and y must be 1-d arrays.") - - if np.any(np.diff(x) < 0) or np.any(np.diff(y) < 0): - raise Exception("The values in x and y must be monotonically increasing.") - - if len(z.shape) != 2 or z.shape != (x.size, y.size): - raise Exception("The length of x and y must match the shape of z.") - - # default colormap (black - white) - self.cmap = ColorMap(pos=[0.0, 1.0], color=[(0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0)]) - - self.data = (x, y, z) - self.lut = None - self.border = border - self.generatePicture() - - def setLookupTable(self, lut, autoLevel=False): - lut.sigLevelsChanged.connect(self.generatePicture) - lut.gradient.sigGradientChanged.connect(self.generatePicture) - self.lut = lut - - if autoLevel: - _, _, z = self.data - f = z[np.isfinite(z)] - lut.setLevels(f.min(), f.max()) - - self.generatePicture() - - def setColorMap(self, cmap): - self.cmap = cmap - self.generatePicture() - - def getHistogram(self, **kwds): - """Returns x and y arrays containing the histogram values for the current image. - For an explanation of the return format, see numpy.histogram(). - """ - - z = self.data[2] - z = z[np.isfinite(z)] - hist = np.histogram(z, **kwds) - - return hist[1][:-1], hist[0] - - def generatePicture(self): - - x, y, z = self.data - - self.picture = QtGui.QPicture() - p = QtGui.QPainter(self.picture) - p.setPen(mkPen(None)) - - # normalize - if self.lut is not None: - mn, mx = self.lut.getLevels() - else: - f = z[np.isfinite(z)] - mn = f.min() - mx = f.max() - - # draw the tiles - for i in range(x.size): - for j in range(y.size): - - value = z[i, j] - - if np.isneginf(value): - value = 0.0 - elif np.isposinf(value): - value = 1.0 - elif np.isnan(value): - continue # ignore NaN - else: - value = (value - mn) / (mx - mn) # normalize - - if self.lut: - color = self.lut.gradient.getColor(value) - else: - color = self.cmap.mapToQColor(value) - - p.setBrush(mkBrush(color)) - - # left, right, bottom, top - l = x[0] if i == 0 else (x[i - 1] + x[i]) / 2 - r = (x[i] + x[i + 1]) / 2 if i < x.size - 1 else x[-1] - b = y[0] if j == 0 else (y[j - 1] + y[j]) / 2 - t = (y[j] + y[j + 1]) / 2 if j < y.size - 1 else y[-1] - - p.drawRect(QtCore.QRectF(l, t, r - l, b - t)) - - if self.border is not None: - p.setPen(self.border) - p.setBrush(fn.mkBrush(None)) - p.drawRect(self.boundingRect()) - - p.end() - - self.update() - - def paint(self, p, *args): - p.drawPicture(0, 0, self.picture) - - def boundingRect(self): - return QtCore.QRectF(self.picture.boundingRect()) diff --git a/pyqtgraph/graphicsItems/PColorMeshItem.py b/pyqtgraph/graphicsItems/PColorMeshItem.py deleted file mode 100644 index a5d75f6..0000000 --- a/pyqtgraph/graphicsItems/PColorMeshItem.py +++ /dev/null @@ -1,260 +0,0 @@ -from __future__ import division - -from ..Qt import QtGui, QtCore -import numpy as np -from .. import functions as fn -from .. import debug as debug -from .GraphicsObject import GraphicsObject -from ..Point import Point -from .. import getConfigOption -from .GradientEditorItem import Gradients # List of colormaps -from ..colormap import ColorMap - -try: - from collections.abc import Callable -except ImportError: - # fallback for python < 3.3 - from collections import Callable - -__all__ = ['PColorMeshItem'] - - -class PColorMeshItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - """ - - - def __init__(self, *args, **kwargs): - """ - Create a pseudocolor plot with convex polygons. - - Call signature: - - ``PColorMeshItem([x, y,] z, **kwargs)`` - - x and y can be used to specify the corners of the quadrilaterals. - z must be used to specified to color of the quadrilaterals. - - Parameters - ---------- - x, y : np.ndarray, optional, default None - 2D array containing the coordinates of the polygons - z : np.ndarray - 2D array containing the value which will be maped into the polygons - colors. - If x and y is None, the polygons will be displaced on a grid - otherwise x and y will be used as polygons vertices coordinates as:: - - (x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1]) - +---------+ - | z[i, j] | - +---------+ - (x[i, j], y[i, j]) (x[i, j+1], y[i, j+1]) - - "ASCII from: ". - cmap : str, default 'viridis - Colormap used to map the z value to colors. - edgecolors : dict, default None - The color of the edges of the polygons. - Default None means no edges. - The dict may contains any arguments accepted by :func:`mkColor() `. - Example: - - ``mkPen(color='w', width=2)`` - - antialiasing : bool, default False - Whether to draw edgelines with antialiasing. - Note that if edgecolors is None, antialiasing is always False. - """ - - GraphicsObject.__init__(self) - - self.qpicture = None ## rendered picture for display - - self.axisOrder = getConfigOption('imageAxisOrder') - - if 'edgecolors' in kwargs.keys(): - self.edgecolors = kwargs['edgecolors'] - else: - self.edgecolors = None - - if 'antialiasing' in kwargs.keys(): - self.antialiasing = kwargs['antialiasing'] - else: - self.antialiasing = False - - if 'cmap' in kwargs.keys(): - if kwargs['cmap'] in Gradients.keys(): - self.cmap = kwargs['cmap'] - else: - raise NameError('Undefined colormap, should be one of the following: '+', '.join(['"'+i+'"' for i in Gradients.keys()])+'.') - else: - self.cmap = 'viridis' - - # If some data have been sent we directly display it - if len(args)>0: - self.setData(*args) - - - def _prepareData(self, args): - """ - Check the shape of the data. - Return a set of 2d array x, y, z ready to be used to draw the picture. - """ - - # User didn't specified data - if len(args)==0: - - self.x = None - self.y = None - self.z = None - - # User only specified z - elif len(args)==1: - # If x and y is None, the polygons will be displaced on a grid - x = np.arange(0, args[0].shape[0]+1, 1) - y = np.arange(0, args[0].shape[1]+1, 1) - self.x, self.y = np.meshgrid(x, y, indexing='ij') - self.z = args[0] - - # User specified x, y, z - elif len(args)==3: - - # Shape checking - if args[0].shape[0] != args[2].shape[0]+1 or args[0].shape[1] != args[2].shape[1]+1: - raise ValueError('The dimension of x should be one greater than the one of z') - - if args[1].shape[0] != args[2].shape[0]+1 or args[1].shape[1] != args[2].shape[1]+1: - raise ValueError('The dimension of y should be one greater than the one of z') - - self.x = args[0] - self.y = args[1] - self.z = args[2] - - else: - ValueError('Data must been sent as (z) or (x, y, z)') - - - def setData(self, *args): - """ - Set the data to be drawn. - - Parameters - ---------- - x, y : np.ndarray, optional, default None - 2D array containing the coordinates of the polygons - z : np.ndarray - 2D array containing the value which will be maped into the polygons - colors. - If x and y is None, the polygons will be displaced on a grid - otherwise x and y will be used as polygons vertices coordinates as:: - - (x[i+1, j], y[i+1, j]) (x[i+1, j+1], y[i+1, j+1]) - +---------+ - | z[i, j] | - +---------+ - (x[i, j], y[i, j]) (x[i, j+1], y[i, j+1]) - - "ASCII from: ". - """ - - # Prepare data - cd = self._prepareData(args) - - # Has the view bounds changed - shapeChanged = False - if self.qpicture is None: - shapeChanged = True - elif len(args)==1: - if args[0].shape[0] != self.x[:,1][-1] or args[0].shape[1] != self.y[0][-1]: - shapeChanged = True - elif len(args)==3: - if np.any(self.x != args[0]) or np.any(self.y != args[1]): - shapeChanged = True - - self.qpicture = QtGui.QPicture() - p = QtGui.QPainter(self.qpicture) - # We set the pen of all polygons once - if self.edgecolors is None: - p.setPen(fn.mkPen(QtGui.QColor(0, 0, 0, 0))) - else: - p.setPen(fn.mkPen(self.edgecolors)) - if self.antialiasing: - p.setRenderHint(QtGui.QPainter.Antialiasing) - - - ## Prepare colormap - # First we get the LookupTable - pos = [i[0] for i in Gradients[self.cmap]['ticks']] - color = [i[1] for i in Gradients[self.cmap]['ticks']] - cmap = ColorMap(pos, color) - lut = cmap.getLookupTable(0.0, 1.0, 256) - # Second we associate each z value, that we normalize, to the lut - norm = self.z - self.z.min() - norm = norm/norm.max() - norm = (norm*(len(lut)-1)).astype(int) - - # Go through all the data and draw the polygons accordingly - for xi in range(self.z.shape[0]): - for yi in range(self.z.shape[1]): - - # Set the color of the polygon first - c = lut[norm[xi][yi]] - p.setBrush(fn.mkBrush(QtGui.QColor(c[0], c[1], c[2]))) - - polygon = QtGui.QPolygonF( - [QtCore.QPointF(self.x[xi][yi], self.y[xi][yi]), - QtCore.QPointF(self.x[xi+1][yi], self.y[xi+1][yi]), - QtCore.QPointF(self.x[xi+1][yi+1], self.y[xi+1][yi+1]), - QtCore.QPointF(self.x[xi][yi+1], self.y[xi][yi+1])] - ) - - # DrawConvexPlygon is faster - p.drawConvexPolygon(polygon) - - - p.end() - self.update() - - self.prepareGeometryChange() - if shapeChanged: - self.informViewBoundsChanged() - - - - def paint(self, p, *args): - if self.z is None: - return - - p.drawPicture(0, 0, self.qpicture) - - - - def setBorder(self, b): - self.border = fn.mkPen(b) - self.update() - - - - def width(self): - if self.x is None: - return None - return np.max(self.x) - - - - def height(self): - if self.y is None: - return None - return np.max(self.y) - - - - - def boundingRect(self): - if self.qpicture is None: - return QtCore.QRectF(0., 0., 0., 0.) - return QtCore.QRectF(self.qpicture.boundingRect()) diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py deleted file mode 100644 index 3900381..0000000 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ /dev/null @@ -1,664 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui, QtWidgets -HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') - -import warnings -import numpy as np -from .GraphicsObject import GraphicsObject -from .. import functions as fn -from ..Point import Point -import struct, sys -from .. import getConfigOption -from .. import debug - -__all__ = ['PlotCurveItem'] -class PlotCurveItem(GraphicsObject): - """ - Class representing a single plot curve. Instances of this class are created - automatically as part of PlotDataItem; these rarely need to be instantiated - directly. - - Features: - - - Fast data update - - Fill under curve - - Mouse interaction - - ===================== =============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data being plotted has changed - sigClicked(self, ev) Emitted when the curve is clicked - ===================== =============================================== - """ - - sigPlotChanged = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - - def __init__(self, *args, **kargs): - """ - Forwards all arguments to :func:`setData `. - - Some extra arguments are accepted as well: - - ============== ======================================================= - **Arguments:** - parent The parent GraphicsObject (optional) - clickable If True, the item will emit sigClicked when it is - clicked on. Defaults to False. - ============== ======================================================= - """ - GraphicsObject.__init__(self, kargs.get('parent', None)) - self.clear() - - ## this is disastrous for performance. - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.metaData = {} - self.opts = { - 'shadowPen': None, - 'fillLevel': None, - 'fillOutline': False, - 'brush': None, - 'stepMode': None, - 'name': None, - 'antialias': getConfigOption('antialias'), - 'connect': 'all', - 'mouseWidth': 8, # width of shape responding to mouse click - 'compositionMode': None, - } - if 'pen' not in kargs: - self.opts['pen'] = fn.mkPen('w') - self.setClickable(kargs.get('clickable', False)) - self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def name(self): - return self.opts.get('name', None) - - def setClickable(self, s, width=None): - """Sets whether the item responds to mouse clicks. - - The *width* argument specifies the width in pixels orthogonal to the - curve that will respond to a mouse click. - """ - self.clickable = s - if width is not None: - self.opts['mouseWidth'] = width - self._mouseShape = None - self._boundingRect = None - - def setCompositionMode(self, mode): - """Change the composition mode of the item (see QPainter::CompositionMode - in the Qt documentation). This is useful when overlaying multiple items. - - ============================================ ============================================================ - **Most common arguments:** - QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it - is opaque. Otherwise, it uses the alpha channel to blend - the image with the background. - QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to - reflect the lightness or darkness of the background. - QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels - are added together. - QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background. - ============================================ ============================================================ - """ - self.opts['compositionMode'] = mode - self.update() - - def getData(self): - return self.xData, self.yData - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - ## Need this to run as fast as possible. - ## check cache first: - cache = self._boundsCache[ax] - if cache is not None and cache[0] == (frac, orthoRange): - return cache[1] - - (x, y) = self.getData() - if x is None or len(x) == 0: - return (None, None) - - if ax == 0: - d = x - d2 = y - elif ax == 1: - d = y - d2 = x - - ## If an orthogonal range is specified, mask the data now - if orthoRange is not None: - mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) - d = d[mask] - #d2 = d2[mask] - - if len(d) == 0: - return (None, None) - - ## Get min/max (or percentiles) of the requested data range - if frac >= 1.0: - # include complete data range - # first try faster nanmin/max function, then cut out infs if needed. - with warnings.catch_warnings(): - # All-NaN data is acceptable; Explicit numpy warning is not needed. - warnings.simplefilter("ignore") - b = (np.nanmin(d), np.nanmax(d)) - if any(np.isinf(b)): - mask = np.isfinite(d) - d = d[mask] - if len(d) == 0: - return (None, None) - b = (d.min(), d.max()) - - elif frac <= 0.0: - raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) - else: - # include a percentile of data range - mask = np.isfinite(d) - d = d[mask] - if len(d) == 0: - return (None, None) - b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) - - ## adjust for fill level - if ax == 1 and self.opts['fillLevel'] not in [None, 'enclosed']: - b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel'])) - - ## Add pen width only if it is non-cosmetic. - pen = self.opts['pen'] - spen = self.opts['shadowPen'] - if not pen.isCosmetic(): - b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072) - if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: - b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072) - - self._boundsCache[ax] = [(frac, orthoRange), b] - return b - - def pixelPadding(self): - pen = self.opts['pen'] - spen = self.opts['shadowPen'] - w = 0 - if pen.isCosmetic(): - w += pen.widthF()*0.7072 - if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: - w = max(w, spen.widthF()*0.7072) - if self.clickable: - w = max(w, self.opts['mouseWidth']//2 + 1) - return w - - def boundingRect(self): - if self._boundingRect is None: - (xmn, xmx) = self.dataBounds(ax=0) - if xmn is None or xmx is None: - return QtCore.QRectF() - (ymn, ymx) = self.dataBounds(ax=1) - if ymn is None or ymx is None: - return QtCore.QRectF() - - px = py = 0.0 - pxPad = self.pixelPadding() - if pxPad > 0: - # determine length of pixel in local x, y directions - px, py = self.pixelVectors() - try: - px = 0 if px is None else px.length() - except OverflowError: - px = 0 - try: - py = 0 if py is None else py.length() - except OverflowError: - py = 0 - - # return bounds expanded by pixel size - px *= pxPad - py *= pxPad - #px += self._maxSpotWidth * 0.5 - #py += self._maxSpotWidth * 0.5 - self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) - - return self._boundingRect - - def viewTransformChanged(self): - self.invalidateBounds() - self.prepareGeometryChange() - - #def boundingRect(self): - #if self._boundingRect is None: - #(x, y) = self.getData() - #if x is None or y is None or len(x) == 0 or len(y) == 0: - #return QtCore.QRectF() - - - #if self.opts['shadowPen'] is not None: - #lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1) - #else: - #lineWidth = (self.opts['pen'].width()+1) - - - #pixels = self.pixelVectors() - #if pixels == (None, None): - #pixels = [Point(0,0), Point(0,0)] - - #xmin = x.min() - #xmax = x.max() - #ymin = y.min() - #ymax = y.max() - - #if self.opts['fillLevel'] is not None: - #ymin = min(ymin, self.opts['fillLevel']) - #ymax = max(ymax, self.opts['fillLevel']) - - #xmin -= pixels[0].x() * lineWidth - #xmax += pixels[0].x() * lineWidth - #ymin -= abs(pixels[1].y()) * lineWidth - #ymax += abs(pixels[1].y()) * lineWidth - - #self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) - #return self._boundingRect - - - def invalidateBounds(self): - self._boundingRect = None - self._boundsCache = [None, None] - - def setPen(self, *args, **kargs): - """Set the pen used to draw the curve.""" - self.opts['pen'] = fn.mkPen(*args, **kargs) - self.invalidateBounds() - self.update() - - def setShadowPen(self, *args, **kargs): - """Set the shadow pen used to draw behind the primary pen. - This pen must have a larger width than the primary - pen to be visible. - """ - self.opts['shadowPen'] = fn.mkPen(*args, **kargs) - self.invalidateBounds() - self.update() - - def setBrush(self, *args, **kargs): - """Set the brush used when filling the area under the curve""" - self.opts['brush'] = fn.mkBrush(*args, **kargs) - self.invalidateBounds() - self.update() - - def setFillLevel(self, level): - """Set the level filled to when filling under the curve""" - self.opts['fillLevel'] = level - self.fillPath = None - self.invalidateBounds() - self.update() - - def setData(self, *args, **kargs): - """ - =============== ======================================================== - **Arguments:** - x, y (numpy arrays) Data to show - pen Pen to use when drawing. Any single argument accepted by - :func:`mkPen ` is allowed. - shadowPen Pen for drawing behind the primary pen. Usually this - is used to emphasize the curve by providing a - high-contrast border. Any single argument accepted by - :func:`mkPen ` is allowed. - fillLevel (float or None) Fill the area 'under' the curve to - *fillLevel* - fillOutline (bool) If True, an outline surrounding the *fillLevel* - area is drawn. - brush QBrush to use when filling. Any single argument accepted - by :func:`mkBrush ` is allowed. - antialias (bool) Whether to use antialiasing when drawing. This - is disabled by default because it decreases performance. - stepMode (str or None) If "center", a step is drawn using the x - values as boundaries and the given y values are - associated to the mid-points between the boundaries of - each step. This is commonly used when drawing - histograms. Note that in this case, len(x) == len(y) + 1 - If "left" or "right", the step is drawn assuming that - the y value is associated to the left or right boundary, - respectively. In this case len(x) == len(y) - If not passed or an empty string or None is passed, the - step mode is not enabled. - Passing True is a deprecated equivalent to "center". - connect Argument specifying how vertexes should be connected - by line segments. Default is "all", indicating full - connection. "pairs" causes only even-numbered segments - to be drawn. "finite" causes segments to be omitted if - they are attached to nan or inf values. For any other - connectivity, specify an array of boolean values. - compositionMode See :func:`setCompositionMode - `. - =============== ======================================================== - - If non-keyword arguments are used, they will be interpreted as - setData(y) for a single argument and setData(x, y) for two - arguments. - - - """ - self.updateData(*args, **kargs) - - def updateData(self, *args, **kargs): - profiler = debug.Profiler() - - if 'compositionMode' in kargs: - self.setCompositionMode(kargs['compositionMode']) - - if len(args) == 1: - kargs['y'] = args[0] - elif len(args) == 2: - kargs['x'] = args[0] - kargs['y'] = args[1] - - if 'y' not in kargs or kargs['y'] is None: - kargs['y'] = np.array([]) - if 'x' not in kargs or kargs['x'] is None: - kargs['x'] = np.arange(len(kargs['y'])) - - for k in ['x', 'y']: - data = kargs[k] - if isinstance(data, list): - data = np.array(data) - kargs[k] = data - if not isinstance(data, np.ndarray) or data.ndim > 1: - raise Exception("Plot data must be 1D ndarray.") - if data.dtype.kind == 'c': - raise Exception("Can not plot complex data types.") - - profiler("data checks") - - #self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly - ## Test this bug with test_PlotWidget and zoom in on the animated plot - self.yData = kargs['y'].view(np.ndarray) - self.xData = kargs['x'].view(np.ndarray) - - self.invalidateBounds() - self.prepareGeometryChange() - self.informViewBoundsChanged() - - profiler('copy') - - if 'stepMode' in kargs: - self.opts['stepMode'] = kargs['stepMode'] - - if self.opts['stepMode'] in ("center", True): ## check against True for backwards compatibility - if self.opts['stepMode'] is True: - import warnings - warnings.warn('stepMode=True is deprecated, use stepMode="center" instead', DeprecationWarning, stacklevel=3) - if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots - raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape)) - else: - if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots - raise Exception("X and Y arrays must be the same shape--got %s and %s." % (self.xData.shape, self.yData.shape)) - - self.path = None - self.fillPath = None - self._mouseShape = None - #self.xDisp = self.yDisp = None - - if 'name' in kargs: - self.opts['name'] = kargs['name'] - if 'connect' in kargs: - self.opts['connect'] = kargs['connect'] - if 'pen' in kargs: - self.setPen(kargs['pen']) - if 'shadowPen' in kargs and kargs['shadowPen'] is not None: - self.setShadowPen(kargs['shadowPen']) - if 'fillLevel' in kargs and kargs['fillLevel'] is not None: - self.setFillLevel(kargs['fillLevel']) - if 'fillOutline' in kargs: - self.opts['fillOutline'] = kargs['fillOutline'] - if 'brush' in kargs and kargs['brush'] is not None: - self.setBrush(kargs['brush']) - if 'antialias' in kargs: - self.opts['antialias'] = kargs['antialias'] - - profiler('set') - self.update() - profiler('update') - self.sigPlotChanged.emit(self) - profiler('emit') - - def generatePath(self, x, y): - stepMode = self.opts['stepMode'] - if stepMode: - ## each value in the x/y arrays generates 2 points. - if stepMode == "right": - x2 = np.empty((len(x) + 1, 2), dtype=x.dtype) - x2[:-1] = x[:, np.newaxis] - x2[-1] = x2[-2] - elif stepMode == "left": - x2 = np.empty((len(x) + 1, 2), dtype=x.dtype) - x2[1:] = x[:, np.newaxis] - x2[0] = x2[1] - elif stepMode in ("center", True): ## support True for back-compat - x2 = np.empty((len(x),2), dtype=x.dtype) - x2[:] = x[:, np.newaxis] - else: - raise ValueError("Unsupported stepMode %s" % stepMode) - if self.opts['fillLevel'] is None: - x = x2.reshape(x2.size)[1:-1] - y2 = np.empty((len(y),2), dtype=y.dtype) - y2[:] = y[:,np.newaxis] - y = y2.reshape(y2.size) - else: - ## If we have a fill level, add two extra points at either end - x = x2.reshape(x2.size) - y2 = np.empty((len(y)+2,2), dtype=y.dtype) - y2[1:-1] = y[:,np.newaxis] - y = y2.reshape(y2.size)[1:-1] - y[0] = self.opts['fillLevel'] - y[-1] = self.opts['fillLevel'] - - path = fn.arrayToQPath(x, y, connect=self.opts['connect']) - - return path - - - def getPath(self): - if self.path is None: - x,y = self.getData() - if x is None or len(x) == 0 or y is None or len(y) == 0: - self.path = QtGui.QPainterPath() - else: - self.path = self.generatePath(*self.getData()) - self.fillPath = None - self._mouseShape = None - - return self.path - - @debug.warnOnException ## raising an exception here causes crash - def paint(self, p, opt, widget): - profiler = debug.Profiler() - if self.xData is None or len(self.xData) == 0: - return - - if getConfigOption('enableExperimental'): - if HAVE_OPENGL and isinstance(widget, QtWidgets.QOpenGLWidget): - self.paintGL(p, opt, widget) - return - - x = None - y = None - path = self.getPath() - profiler('generate path') - - if self._exportOpts is not False: - aa = self._exportOpts.get('antialias', True) - else: - aa = self.opts['antialias'] - - p.setRenderHint(p.Antialiasing, aa) - - cmode = self.opts['compositionMode'] - if cmode is not None: - p.setCompositionMode(cmode) - - if self.opts['brush'] is not None and self.opts['fillLevel'] is not None: - if self.fillPath is None: - if x is None: - x,y = self.getData() - p2 = QtGui.QPainterPath(self.path) - if self.opts['fillLevel'] != 'enclosed': - p2.lineTo(x[-1], self.opts['fillLevel']) - p2.lineTo(x[0], self.opts['fillLevel']) - p2.lineTo(x[0], y[0]) - p2.closeSubpath() - self.fillPath = p2 - - profiler('generate fill path') - p.fillPath(self.fillPath, self.opts['brush']) - profiler('draw fill path') - - # Avoid constructing a shadow pen if it's not used. - if self.opts.get('shadowPen') is not None: - if isinstance(self.opts.get('shadowPen'), QtGui.QPen): - sp = self.opts['shadowPen'] - else: - sp = fn.mkPen(self.opts['shadowPen']) - - if sp.style() != QtCore.Qt.NoPen: - p.setPen(sp) - p.drawPath(path) - - if isinstance(self.opts.get('pen'), QtGui.QPen): - cp = self.opts['pen'] - else: - cp = fn.mkPen(self.opts['pen']) - - p.setPen(cp) - if self.opts['fillOutline'] and self.fillPath is not None: - p.drawPath(self.fillPath) - else: - p.drawPath(path) - profiler('drawPath') - - def paintGL(self, p, opt, widget): - p.beginNativePainting() - import OpenGL.GL as gl - - ## set clipping viewport - view = self.getViewBox() - if view is not None: - rect = view.mapRectToItem(self, view.boundingRect()) - #gl.glViewport(int(rect.x()), int(rect.y()), int(rect.width()), int(rect.height())) - - #gl.glTranslate(-rect.x(), -rect.y(), 0) - - gl.glEnable(gl.GL_STENCIL_TEST) - gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) # disable drawing to frame buffer - gl.glDepthMask(gl.GL_FALSE) # disable drawing to depth buffer - gl.glStencilFunc(gl.GL_NEVER, 1, 0xFF) - gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) - - ## draw stencil pattern - gl.glStencilMask(0xFF) - gl.glClear(gl.GL_STENCIL_BUFFER_BIT) - gl.glBegin(gl.GL_TRIANGLES) - gl.glVertex2f(rect.x(), rect.y()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()) - gl.glVertex2f(rect.x(), rect.y()+rect.height()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()+rect.height()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()) - gl.glVertex2f(rect.x(), rect.y()+rect.height()) - gl.glEnd() - - gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) - gl.glDepthMask(gl.GL_TRUE) - gl.glStencilMask(0x00) - gl.glStencilFunc(gl.GL_EQUAL, 1, 0xFF) - - try: - x, y = self.getData() - pos = np.empty((len(x), 2)) - pos[:,0] = x - pos[:,1] = y - gl.glEnableClientState(gl.GL_VERTEX_ARRAY) - try: - gl.glVertexPointerf(pos) - pen = fn.mkPen(self.opts['pen']) - color = pen.color() - gl.glColor4f(color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) - width = pen.width() - if pen.isCosmetic() and width < 1: - width = 1 - gl.glPointSize(width) - gl.glLineWidth(width) - gl.glEnable(gl.GL_LINE_SMOOTH) - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) - gl.glDrawArrays(gl.GL_LINE_STRIP, 0, int(pos.size / pos.shape[-1])) - finally: - gl.glDisableClientState(gl.GL_VERTEX_ARRAY) - finally: - p.endNativePainting() - - def clear(self): - self.xData = None ## raw values - self.yData = None - self.xDisp = None ## display values (after log / fft) - self.yDisp = None - self.path = None - self.fillPath = None - self._mouseShape = None - self._mouseBounds = None - self._boundsCache = [None, None] - #del self.xData, self.yData, self.xDisp, self.yDisp, self.path - - def mouseShape(self): - """ - Return a QPainterPath representing the clickable shape of the curve - - """ - if self._mouseShape is None: - view = self.getViewBox() - if view is None: - return QtGui.QPainterPath() - stroker = QtGui.QPainterPathStroker() - path = self.getPath() - path = self.mapToItem(view, path) - stroker.setWidth(self.opts['mouseWidth']) - mousePath = stroker.createStroke(path) - self._mouseShape = self.mapFromItem(view, mousePath) - return self._mouseShape - - def mouseClickEvent(self, ev): - if not self.clickable or ev.button() != QtCore.Qt.LeftButton: - return - if self.mouseShape().contains(ev.pos()): - ev.accept() - self.sigClicked.emit(self, ev) - - - -class ROIPlotItem(PlotCurveItem): - """Plot curve that monitors an ROI and image for changes to automatically replot.""" - def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): - self.roi = roi - self.roiData = data - self.roiImg = img - self.axes = axes - self.xVals = xVals - PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) - #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) - roi.sigRegionChanged.connect(self.roiChangedEvent) - #self.roiChangedEvent() - - def getRoiData(self): - d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) - if d is None: - return - while d.ndim > 1: - d = d.mean(axis=1) - return d - - def roiChangedEvent(self): - d = self.getRoiData() - self.updateData(d, self.xVals) diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py deleted file mode 100644 index 73eaedb..0000000 --- a/pyqtgraph/graphicsItems/PlotDataItem.py +++ /dev/null @@ -1,1079 +0,0 @@ -# -*- coding: utf-8 -*- -import warnings -import numpy as np -from .. import metaarray as metaarray -from ..Qt import QtCore -from .GraphicsObject import GraphicsObject -from .PlotCurveItem import PlotCurveItem -from .ScatterPlotItem import ScatterPlotItem -from .. import functions as fn -from .. import debug as debug -from .. import getConfigOption - - -class PlotDataItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - GraphicsItem for displaying plot curves, scatter plots, or both. - While it is possible to use :class:`PlotCurveItem ` or - :class:`ScatterPlotItem ` individually, this class - provides a unified interface to both. Instances of :class:`PlotDataItem` are - usually created by plot() methods such as :func:`pyqtgraph.plot` and - :func:`PlotItem.plot() `. - - ================================== ============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data in this item is updated. - sigClicked(self, ev) Emitted when the item is clicked. - sigPointsClicked(self, points, ev) Emitted when a plot point is clicked - Sends the list of points under the mouse. - sigPointsHovered(self, points, ev) Emitted when a plot point is hovered over. - Sends the list of points under the mouse. - ================================== ============================================== - """ - - sigPlotChanged = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - sigPointsClicked = QtCore.Signal(object, object, object) - sigPointsHovered = QtCore.Signal(object, object, object) - - def __init__(self, *args, **kargs): - """ - There are many different ways to create a PlotDataItem: - - **Data initialization arguments:** (x,y data only) - - =================================== ====================================== - PlotDataItem(xValues, yValues) x and y values may be any sequence - (including ndarray) of real numbers - PlotDataItem(yValues) y values only -- x will be - automatically set to range(len(y)) - PlotDataItem(x=xValues, y=yValues) x and y given by keyword arguments - PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where - ``x=data[:,0]`` and ``y=data[:,1]`` - =================================== ====================================== - - **Data initialization arguments:** (x,y data AND may include spot style) - - - ============================ ========================================= - PlotDataItem(recarray) numpy array with ``dtype=[('x', float), - ('y', float), ...]`` - PlotDataItem(list-of-dicts) ``[{'x': x, 'y': y, ...}, ...]`` - PlotDataItem(dict-of-lists) ``{'x': [...], 'y': [...], ...}`` - PlotDataItem(MetaArray) 1D array of Y values with X sepecified as - axis values OR 2D array with a column 'y' - and extra columns as needed. - ============================ ========================================= - - **Line style keyword arguments:** - - ============ ============================================================================== - connect Specifies how / whether vertexes should be connected. See - :func:`arrayToQPath() ` - pen Pen to use for drawing line between points. - Default is solid grey, 1px width. Use None to disable line drawing. - May be any single argument accepted by :func:`mkPen() ` - shadowPen Pen for secondary line to draw behind the primary line. disabled by default. - May be any single argument accepted by :func:`mkPen() ` - fillLevel Fill the area between the curve and fillLevel - - fillOutline (bool) If True, an outline surrounding the *fillLevel* area is drawn. - fillBrush Fill to use when fillLevel is specified. - May be any single argument accepted by :func:`mkBrush() ` - stepMode (str or None) If "center", a step is drawn using the x - values as boundaries and the given y values are - associated to the mid-points between the boundaries of - each step. This is commonly used when drawing - histograms. Note that in this case, len(x) == len(y) + 1 - If "left" or "right", the step is drawn assuming that - the y value is associated to the left or right boundary, - respectively. In this case len(x) == len(y) - If not passed or an empty string or None is passed, the - step mode is not enabled. - Passing True is a deprecated equivalent to "center". - (added in version 0.9.9) - - ============ ============================================================================== - - **Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information) - - ============ ===================================================== - symbol Symbol to use for drawing points OR list of symbols, - one per point. Default is no symbol. - Options are o, s, t, d, +, or any QPainterPath - symbolPen Outline pen for drawing points OR list of pens, one - per point. May be any single argument accepted by - :func:`mkPen() ` - symbolBrush Brush for filling points OR list of brushes, one per - point. May be any single argument accepted by - :func:`mkBrush() ` - symbolSize Diameter of symbols OR list of diameters. - pxMode (bool) If True, then symbolSize is specified in - pixels. If False, then symbolSize is - specified in data coordinates. - ============ ===================================================== - - **Optimization keyword arguments:** - - ================= ===================================================================== - antialias (bool) By default, antialiasing is disabled to improve performance. - Note that in some cases (in particluar, when pxMode=True), points - will be rendered antialiased even if this is set to False. - decimate deprecated. - downsample (int) Reduce the number of samples displayed by this value - downsampleMethod 'subsample': Downsample by taking the first of N samples. - This method is fastest and least accurate. - 'mean': Downsample by taking the mean of N samples. - 'peak': Downsample by drawing a saw wave that follows the min - and max of the original data. This method produces the best - visual representation of the data but is slower. - autoDownsample (bool) If True, resample the data before plotting to avoid plotting - multiple line segments per pixel. This can improve performance when - viewing very high-density data, but increases the initial overhead - and memory usage. - clipToView (bool) If True, only plot data that is visible within the X range of - the containing ViewBox. This can improve performance when plotting - very large data sets where only a fraction of the data is visible - at any time. - dynamicRangeLimit (float or None) Limit off-screen positions of data points at large - magnification to avoids display errors. Disabled if None. - identical *deprecated* - ================= ===================================================================== - - **Meta-info keyword arguments:** - - ========== ================================================ - name name of dataset. This would appear in a legend - ========== ================================================ - """ - GraphicsObject.__init__(self) - self.setFlag(self.ItemHasNoContents) - self.xData = None - self.yData = None - self.xDisp = None - self.yDisp = None - #self.dataMask = None - #self.curves = [] - #self.scatters = [] - self.curve = PlotCurveItem() - self.scatter = ScatterPlotItem() - self.curve.setParentItem(self) - self.scatter.setParentItem(self) - - self.curve.sigClicked.connect(self.curveClicked) - self.scatter.sigClicked.connect(self.scatterClicked) - self.scatter.sigHovered.connect(self.scatterHovered) - - self._viewRangeWasChanged = False - self._styleWasChanged = True # force initial update - - self._dataRect = None - self._drlLastClip = (0.0, 0.0) # holds last clipping points of dynamic range limiter - #self.clear() - self.opts = { - 'connect': 'all', - - 'fftMode': False, - 'logMode': [False, False], - 'derivativeMode': False, - 'phasemapMode': False, - 'alphaHint': 1.0, - 'alphaMode': False, - - 'pen': (200,200,200), - 'shadowPen': None, - 'fillLevel': None, - 'fillOutline': False, - 'fillBrush': None, - 'stepMode': None, - - 'symbol': None, - 'symbolSize': 10, - 'symbolPen': (200,200,200), - 'symbolBrush': (50, 50, 150), - 'pxMode': True, - - 'antialias': getConfigOption('antialias'), - 'pointMode': None, - - 'downsample': 1, - 'autoDownsample': False, - 'downsampleMethod': 'peak', - 'autoDownsampleFactor': 5., # draw ~5 samples per pixel - 'clipToView': False, - 'dynamicRangeLimit': 1e6, - 'dynamicRangeHyst': 3.0, - - 'data': None, - } - self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def name(self): - return self.opts.get('name', None) - - def boundingRect(self): - return QtCore.QRectF() ## let child items handle this - - def setPos(self, x, y): - GraphicsObject.setPos(self, x, y) - # to update viewRect: - self.viewTransformChanged() - # to update displayed point sets, e.g. when clipping (which uses viewRect): - self.viewRangeChanged() - - def setAlpha(self, alpha, auto): - if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto: - return - self.opts['alphaHint'] = alpha - self.opts['alphaMode'] = auto - self.setOpacity(alpha) - #self.update() - - def setFftMode(self, mode): - if self.opts['fftMode'] == mode: - return - self.opts['fftMode'] = mode - self.xDisp = self.yDisp = None - self.updateItems() - self.informViewBoundsChanged() - - def setLogMode(self, xMode, yMode): - """ - To enable log scaling for y<0 and y>0, the following formula is used: - - scaled = sign(y) * log10(abs(y) + eps) - - where eps is the smallest unit of y.dtype. - This allows for handling of 0. values, scaling of large values, - as well as the typical log scaling of values in the range -1 < x < 1. - Note that for values within this range, the signs are inverted. - """ - if self.opts['logMode'] == [xMode, yMode]: - return - self.opts['logMode'] = [xMode, yMode] - self.xDisp = self.yDisp = None - self.updateItems() - self.informViewBoundsChanged() - - - def setDerivativeMode(self, mode): - if self.opts['derivativeMode'] == mode: - return - self.opts['derivativeMode'] = mode - self.xDisp = self.yDisp = None - self.updateItems() - self.informViewBoundsChanged() - - def setPhasemapMode(self, mode): - if self.opts['phasemapMode'] == mode: - return - self.opts['phasemapMode'] = mode - self.xDisp = self.yDisp = None - self.updateItems() - self.informViewBoundsChanged() - - def setPointMode(self, mode): - if self.opts['pointMode'] == mode: - return - self.opts['pointMode'] = mode - self.update() - - def setPen(self, *args, **kargs): - """ - | Sets the pen used to draw lines between points. - | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` - """ - pen = fn.mkPen(*args, **kargs) - self.opts['pen'] = pen - #self.curve.setPen(pen) - #for c in self.curves: - #c.setPen(pen) - #self.update() - self.updateItems() - - def setShadowPen(self, *args, **kargs): - """ - | Sets the shadow pen used to draw lines between points (this is for enhancing contrast or - emphacizing data). - | This line is drawn behind the primary pen (see :func:`setPen() `) - and should generally be assigned greater width than the primary pen. - | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` - """ - pen = fn.mkPen(*args, **kargs) - self.opts['shadowPen'] = pen - #for c in self.curves: - #c.setPen(pen) - #self.update() - self.updateItems() - - def setFillBrush(self, *args, **kargs): - brush = fn.mkBrush(*args, **kargs) - if self.opts['fillBrush'] == brush: - return - self.opts['fillBrush'] = brush - self.updateItems() - - def setBrush(self, *args, **kargs): - return self.setFillBrush(*args, **kargs) - - def setFillLevel(self, level): - if self.opts['fillLevel'] == level: - return - self.opts['fillLevel'] = level - self.updateItems() - - def setSymbol(self, symbol): - if self.opts['symbol'] == symbol: - return - self.opts['symbol'] = symbol - #self.scatter.setSymbol(symbol) - self.updateItems() - - def setSymbolPen(self, *args, **kargs): - pen = fn.mkPen(*args, **kargs) - if self.opts['symbolPen'] == pen: - return - self.opts['symbolPen'] = pen - #self.scatter.setSymbolPen(pen) - self.updateItems() - - def setSymbolBrush(self, *args, **kargs): - brush = fn.mkBrush(*args, **kargs) - if self.opts['symbolBrush'] == brush: - return - self.opts['symbolBrush'] = brush - #self.scatter.setSymbolBrush(brush) - self.updateItems() - - - def setSymbolSize(self, size): - if self.opts['symbolSize'] == size: - return - self.opts['symbolSize'] = size - #self.scatter.setSymbolSize(symbolSize) - self.updateItems() - - def setDownsampling(self, ds=None, auto=None, method=None): - """ - Set the downsampling mode of this item. Downsampling reduces the number - of samples drawn to increase performance. - - ============== ================================================================= - **Arguments:** - ds (int) Reduce visible plot samples by this factor. To disable, - set ds=1. - auto (bool) If True, automatically pick *ds* based on visible range - mode 'subsample': Downsample by taking the first of N samples. - This method is fastest and least accurate. - 'mean': Downsample by taking the mean of N samples. - 'peak': Downsample by drawing a saw wave that follows the min - and max of the original data. This method produces the best - visual representation of the data but is slower. - ============== ================================================================= - """ - changed = False - if ds is not None: - if self.opts['downsample'] != ds: - changed = True - self.opts['downsample'] = ds - - if auto is not None and self.opts['autoDownsample'] != auto: - self.opts['autoDownsample'] = auto - changed = True - - if method is not None: - if self.opts['downsampleMethod'] != method: - changed = True - self.opts['downsampleMethod'] = method - - if changed: - self.xDisp = self.yDisp = None - self.updateItems() - - def setClipToView(self, clip): - if self.opts['clipToView'] == clip: - return - self.opts['clipToView'] = clip - self.xDisp = self.yDisp = None - self.updateItems() - - def setDynamicRangeLimit(self, limit=1e06, hysteresis=3.): - """ - Limit the off-screen positions of data points at large magnification - This avoids errors with plots not displaying because their visibility is incorrectly determined. The default setting repositions far-off points to be within +-1E+06 times the viewport height. - - =============== ================================================================ - **Arguments:** - limit (float or None) Any data outside the range of limit * hysteresis - will be constrained to the limit value limit. - All values are relative to the viewport height. - 'None' disables the check for a minimal increase in performance. - Default is 1E+06. - - hysteresis (float) Hysteresis factor that controls how much change - in zoom level (vertical height) is allowed before recalculating - Default is 3.0 - =============== ================================================================ - """ - if hysteresis < 1.0: - hysteresis = 1.0 - self.opts['dynamicRangeHyst'] = hysteresis - - if limit == self.opts['dynamicRangeLimit']: - return # avoid update if there is no change - self.opts['dynamicRangeLimit'] = limit # can be None - self.xDisp = self.yDisp = None - self.updateItems() - - def setData(self, *args, **kargs): - """ - Clear any data displayed by this item and display new data. - See :func:`__init__() ` for details; it accepts the same arguments. - """ - #self.clear() - if kargs.get("stepMode", None) is True: - warnings.warn( - 'stepMode=True is deprecated, use stepMode="center" instead', - DeprecationWarning, stacklevel=3 - ) - if 'decimate' in kargs.keys(): - warnings.warn( - 'decimate kwarg has been deprecated, it has no effect', - DeprecationWarning, stacklevel=2 - ) - - if 'identical' in kargs.keys(): - warnings.warn( - 'identical kwarg has been deprecated, it has no effect', - DeprecationWarning, stacklevel=2 - ) - profiler = debug.Profiler() - y = None - x = None - if len(args) == 1: - data = args[0] - dt = dataType(data) - if dt == 'empty': - pass - elif dt == 'listOfValues': - y = np.array(data) - elif dt == 'Nx2array': - x = data[:,0] - y = data[:,1] - elif dt == 'recarray' or dt == 'dictOfLists': - if 'x' in data: - x = np.array(data['x']) - if 'y' in data: - y = np.array(data['y']) - elif dt == 'listOfDicts': - if 'x' in data[0]: - x = np.array([d.get('x',None) for d in data]) - if 'y' in data[0]: - y = np.array([d.get('y',None) for d in data]) - for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']: - if k in data: - kargs[k] = [d.get(k, None) for d in data] - elif dt == 'MetaArray': - y = data.view(np.ndarray) - x = data.xvals(0).view(np.ndarray) - else: - raise Exception('Invalid data type %s' % type(data)) - - elif len(args) == 2: - seq = ('listOfValues', 'MetaArray', 'empty') - dtyp = dataType(args[0]), dataType(args[1]) - if dtyp[0] not in seq or dtyp[1] not in seq: - raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) - if not isinstance(args[0], np.ndarray): - #x = np.array(args[0]) - if dtyp[0] == 'MetaArray': - x = args[0].asarray() - else: - x = np.array(args[0]) - else: - x = args[0].view(np.ndarray) - if not isinstance(args[1], np.ndarray): - #y = np.array(args[1]) - if dtyp[1] == 'MetaArray': - y = args[1].asarray() - else: - y = np.array(args[1]) - else: - y = args[1].view(np.ndarray) - - if 'x' in kargs: - x = kargs['x'] - if dataType(x) == 'MetaArray': - x = x.asarray() - if 'y' in kargs: - y = kargs['y'] - if dataType(y) == 'MetaArray': - y = y.asarray() - - profiler('interpret data') - ## pull in all style arguments. - ## Use self.opts to fill in anything not present in kargs. - - if 'name' in kargs: - self.opts['name'] = kargs['name'] - self._styleWasChanged = True - - if 'connect' in kargs: - self.opts['connect'] = kargs['connect'] - self._styleWasChanged = True - - ## if symbol pen/brush are given with no previously set symbol, then assume symbol is 'o' - if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs): - if self.opts['symbol'] is None: - kargs['symbol'] = 'o' - - if 'brush' in kargs: - kargs['fillBrush'] = kargs['brush'] - - for k in list(self.opts.keys()): - if k in kargs: - self.opts[k] = kargs[k] - self._styleWasChanged = True - - #curveArgs = {} - #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: - #if k in kargs: - #self.opts[k] = kargs[k] - #curveArgs[k] = self.opts[k] - - #scatterArgs = {} - #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: - #if k in kargs: - #self.opts[k] = kargs[k] - #scatterArgs[v] = self.opts[k] - - if y is None or len(y) == 0: # empty data is represented as None - self.yData = None - else: # actual data is represented by ndarray - if not isinstance(y, np.ndarray): - y = np.array(y) - self.yData = y.view(np.ndarray) - if x is None: - x = np.arange(len(y)) - - if x is None or len(x)==0: # empty data is represented as None - self.xData = None - else: # actual data is represented by ndarray - if not isinstance(x, np.ndarray): - x = np.array(x) - self.xData = x.view(np.ndarray) # one last check to make sure there are no MetaArrays getting by - self._dataRect = None - self.xDisp = None - self.yDisp = None - profiler('set data') - - self.updateItems( update_style = self._styleWasChanged ) - self._styleWasChanged = False # items have been updated - profiler('update items') - - self.informViewBoundsChanged() - #view = self.getViewBox() - #if view is not None: - #view.itemBoundsChanged(self) ## inform view so it can update its range if it wants - - self.sigPlotChanged.emit(self) - profiler('emit') - - def updateItems(self, update_style=False): - curveArgs = {} - scatterArgs = {} - if update_style: # repeat style arguments only when changed - for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode')]: - if k in self.opts: - curveArgs[v] = self.opts[k] - - for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]: - if k in self.opts: - scatterArgs[v] = self.opts[k] - - x,y = self.getData() - #scatterArgs['mask'] = self.dataMask - - if self.opts['pen'] is not None or (self.opts['fillBrush'] is not None and self.opts['fillLevel'] is not None): # draw if visible... - self.curve.setData(x=x, y=y, **curveArgs) - self.curve.show() - else: # ...hide if not. - self.curve.hide() - - if self.opts['symbol'] is not None: # draw if visible... - ## check against `True` too for backwards compatibility - if self.opts.get('stepMode', False) in ("center", True): - x = 0.5 * (x[:-1] + x[1:]) - self.scatter.setData(x=x, y=y, **scatterArgs) - self.scatter.show() - else: # ...hide if not. - self.scatter.hide() - - - def getData(self): - if self.xData is None: - return (None, None) - - if self.xDisp is None or self._viewRangeWasChanged: - x = self.xData - y = self.yData - - if self.opts['fftMode']: - x,y = self._fourierTransform(x, y) - # Ignore the first bin for fft data if we have a logx scale - if self.opts['logMode'][0]: - x=x[1:] - y=y[1:] - - if self.opts['derivativeMode']: # plot dV/dt - y = np.diff(self.yData)/np.diff(self.xData) - x = x[:-1] - if self.opts['phasemapMode']: # plot dV/dt vs V - x = self.yData[:-1] - y = np.diff(self.yData)/np.diff(self.xData) - - with np.errstate(divide='ignore'): - if self.opts['logMode'][0]: - x = np.log10(x) - if self.opts['logMode'][1]: - if np.issubdtype(y.dtype, np.floating): - eps = np.finfo(y.dtype).eps - else: - eps = 1 - y = np.sign(y) * np.log10(np.abs(y)+eps) - - ds = self.opts['downsample'] - if not isinstance(ds, int): - ds = 1 - - if self.opts['autoDownsample']: - # this option presumes that x-values have uniform spacing - range = self.viewRect() - if range is not None and len(x) > 1: - dx = float(x[-1]-x[0]) / (len(x)-1) - if dx != 0.0: - x0 = (range.left()-x[0]) / dx - x1 = (range.right()-x[0]) / dx - width = self.getViewBox().width() - if width != 0.0: - ds = int(max(1, int((x1-x0) / (width*self.opts['autoDownsampleFactor'])))) - ## downsampling is expensive; delay until after clipping. - - if self.opts['clipToView']: - view = self.getViewBox() - if view is None or not view.autoRangeEnabled()[0]: - # this option presumes that x-values are in increasing order - range = self.viewRect() - if range is not None and len(x) > 1: - # clip to visible region extended by downsampling value, assuming - # uniform spacing of x-values, has O(1) performance - dx = float(x[-1]-x[0]) / (len(x)-1) - # workaround for slowdown from numpy deprecation issues in 1.17 to 1.20+ - # x0 = np.clip(int((range.left()-x[0])/dx) - 1*ds, 0, len(x)-1) - # x1 = np.clip(int((range.right()-x[0])/dx) + 2*ds, 0, len(x)-1) - x0 = fn.clip_array(int((range.left()-x[0])/dx) - 1*ds, 0, len(x)-1) - x1 = fn.clip_array(int((range.right()-x[0])/dx) + 2*ds, 0, len(x)-1) - - # if data has been clipped too strongly (in case of non-uniform - # spacing of x-values), refine the clipping region as required - # worst case performance: O(log(n)) - # best case performance: O(1) - if x[x0] > range.left(): - x0 = np.searchsorted(x, range.left()) - 1*ds - x0 = fn.clip_array(x0, 0, len(x)) # workaround - # x0 = np.clip(x0, 0, len(x)) - if x[x1] < range.right(): - x1 = np.searchsorted(x, range.right()) + 2*ds - x1 = fn.clip_array(x1, 0, len(x)) - # x1 = np.clip(x1, 0, len(x)) - x = x[x0:x1] - y = y[x0:x1] - - if ds > 1: - if self.opts['downsampleMethod'] == 'subsample': - x = x[::ds] - y = y[::ds] - elif self.opts['downsampleMethod'] == 'mean': - n = len(x) // ds - x = x[:n*ds:ds] - y = y[:n*ds].reshape(n,ds).mean(axis=1) - elif self.opts['downsampleMethod'] == 'peak': - n = len(x) // ds - x1 = np.empty((n,2)) - x1[:] = x[:n*ds:ds,np.newaxis] - x = x1.reshape(n*2) - y1 = np.empty((n,2)) - y2 = y[:n*ds].reshape((n, ds)) - y1[:,0] = y2.max(axis=1) - y1[:,1] = y2.min(axis=1) - y = y1.reshape(n*2) - - if self.opts['dynamicRangeLimit'] is not None: - view_range = self.viewRect() - if view_range is not None: - data_range = self.dataRect() - if data_range is not None: - view_height = view_range.height() - limit = self.opts['dynamicRangeLimit'] - hyst = self.opts['dynamicRangeHyst'] - # never clip data if it fits into +/- (extended) limit * view height - if ( # note that "bottom" is the larger number, and "top" is the smaller one. - not data_range.bottom() < view_range.top() # never clip if all data is too small to see - and not data_range.top() > view_range.bottom() # never clip if all data is too large to see - and data_range.height() > 2 * hyst * limit * view_height - ): - # check if cached display data can be reused: - if self.yDisp is not None: # top is minimum value, bottom is maximum value - # how many multiples of the current view height does the clipped plot extend to the top and bottom? - top_exc =-(self._drlLastClip[0]-view_range.bottom()) / view_height - bot_exc = (self._drlLastClip[1]-view_range.top() ) / view_height - # print(top_exc, bot_exc, hyst) - if ( top_exc >= limit / hyst and top_exc <= limit * hyst - and bot_exc >= limit / hyst and bot_exc <= limit * hyst ): - # restore cached values - x = self.xDisp - y = self.yDisp - else: - min_val = view_range.bottom() - limit * view_height - max_val = view_range.top() + limit * view_height - if( min_val >= self._drlLastClip[0] - and max_val <= self._drlLastClip[1] ): - # if we need to clip further, we can work in-place on the output buffer - # print('in-place:', end='') - # workaround for slowdown from numpy deprecation issues in 1.17 to 1.20+ : - # np.clip(self.yDisp, out=self.yDisp, a_min=min_val, a_max=max_val) - fn.clip_array(self.yDisp, min_val, max_val, out=self.yDisp) - x = self.xDisp - y = self.yDisp - else: - # otherwise we need to recopy from the full data - # print('alloc:', end='') - # workaround for slowdown from numpy deprecation issues in 1.17 to 1.20+ : - # y = np.clip(y, a_min=min_val, a_max=max_val) - y = fn.clip_array(y, min_val, max_val) - # print('{:.1e}<->{:.1e}'.format( min_val, max_val )) - self._drlLastClip = (min_val, max_val) - - self.xDisp = x - self.yDisp = y - self._viewRangeWasChanged = False - return self.xDisp, self.yDisp - - def dataRect(self): - """ - Returns a bounding rectangle (as QRectF) for the full set of data. - Will return None if there is no data or if all values (x or y) are NaN. - """ - if self._dataRect is not None: - return self._dataRect - if self.xData is None or self.yData is None: - return None - if len(self.xData) == 0: # avoid crash if empty data is represented by [] instead of None - return None - with warnings.catch_warnings(): - # All-NaN data is handled by returning None; Explicit numpy warning is not needed. - warnings.simplefilter("ignore") - ymin = np.nanmin(self.yData) - if np.isnan( ymin ): - return None # most likely case for all-NaN data - xmin = np.nanmin(self.xData) - if np.isnan( xmin ): - return None # less likely case for all-NaN data - ymax = np.nanmax(self.yData) - xmax = np.nanmax(self.xData) - self._dataRect = QtCore.QRectF( - QtCore.QPointF(xmin,ymin), - QtCore.QPointF(xmax,ymax) ) - return self._dataRect - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - """ - Returns the range occupied by the data (along a specific axis) in this item. - This method is called by ViewBox when auto-scaling. - - =============== ============================================================= - **Arguments:** - ax (0 or 1) the axis for which to return this item's data range - frac (float 0.0-1.0) Specifies what fraction of the total data - range to return. By default, the entire range is returned. - This allows the ViewBox to ignore large spikes in the data - when auto-scaling. - orthoRange ([min,max] or None) Specifies that only the data within the - given range (orthogonal to *ax*) should me measured when - returning the data range. (For example, a ViewBox might ask - what is the y-range of all data with x-values between min - and max) - =============== ============================================================= - """ - - range = [None, None] - if self.curve.isVisible(): - range = self.curve.dataBounds(ax, frac, orthoRange) - elif self.scatter.isVisible(): - r2 = self.scatter.dataBounds(ax, frac, orthoRange) - range = [ - r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])), - r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1])) - ] - return range - - def pixelPadding(self): - """ - Return the size in pixels that this item may draw beyond the values returned by dataBounds(). - This method is called by ViewBox when auto-scaling. - """ - pad = 0 - if self.curve.isVisible(): - pad = max(pad, self.curve.pixelPadding()) - elif self.scatter.isVisible(): - pad = max(pad, self.scatter.pixelPadding()) - return pad - - - def clear(self): - #for i in self.curves+self.scatters: - #if i.scene() is not None: - #i.scene().removeItem(i) - #self.curves = [] - #self.scatters = [] - self.xData = None - self.yData = None - self.xDisp = None - self.yDisp = None - self._dataRect = None - self.curve.clear() - self.scatter.clear() - - def appendData(self, *args, **kargs): - pass - - def curveClicked(self, curve, ev): - self.sigClicked.emit(self, ev) - - def scatterClicked(self, plt, points, ev): - self.sigClicked.emit(self, ev) - self.sigPointsClicked.emit(self, points, ev) - - def scatterHovered(self, plt, points, ev): - self.sigPointsHovered.emit(self, points, ev) - - def viewRangeChanged(self): - # view range has changed; re-plot if needed - self._viewRangeWasChanged = True - if( self.opts['clipToView'] - or self.opts['autoDownsample'] - ): - self.xDisp = self.yDisp = None - self.updateItems() - elif self.opts['dynamicRangeLimit'] is not None: - # do not discard cached display data - self.updateItems() - - def _fourierTransform(self, x, y): - ## Perform fourier transform. If x values are not sampled uniformly, - ## then use np.interp to resample before taking fft. - dx = np.diff(x) - uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) - if not uniform: - x2 = np.linspace(x[0], x[-1], len(x)) - y = np.interp(x2, x, y) - x = x2 - n = y.size - f = np.fft.rfft(y) / n - d = float(x[-1]-x[0]) / (len(x)-1) - x = np.fft.rfftfreq(n, d) - y = np.abs(f) - return x, y - -def dataType(obj): - if hasattr(obj, '__len__') and len(obj) == 0: - return 'empty' - if isinstance(obj, dict): - return 'dictOfLists' - elif isSequence(obj): - first = obj[0] - - if (hasattr(obj, 'implements') and obj.implements('MetaArray')): - return 'MetaArray' - elif isinstance(obj, np.ndarray): - if obj.ndim == 1: - if obj.dtype.names is None: - return 'listOfValues' - else: - return 'recarray' - elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: - return 'Nx2array' - else: - raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) - elif isinstance(first, dict): - return 'listOfDicts' - else: - return 'listOfValues' - - -def isSequence(obj): - return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray')) - - - -#class TableData: - #""" - #Class for presenting multiple forms of tabular data through a consistent interface. - #May contain: - #- numpy record array - #- list-of-dicts (all dicts are _not_ required to have the same keys) - #- dict-of-lists - #- dict (single record) - #Note: if all the values in this record are lists, it will be interpreted as multiple records - - #Data can be accessed and modified by column, by row, or by value - #data[columnName] - #data[rowId] - #data[columnName, rowId] = value - #data[columnName] = [value, value, ...] - #data[rowId] = {columnName: value, ...} - #""" - - #def __init__(self, data): - #self.data = data - #if isinstance(data, np.ndarray): - #self.mode = 'array' - #elif isinstance(data, list): - #self.mode = 'list' - #elif isinstance(data, dict): - #types = set(map(type, data.values())) - ### dict may be a dict-of-lists or a single record - #types -= set([list, np.ndarray]) ## if dict contains any non-sequence values, it is probably a single record. - #if len(types) != 0: - #self.data = [self.data] - #self.mode = 'list' - #else: - #self.mode = 'dict' - #elif isinstance(data, TableData): - #self.data = data.data - #self.mode = data.mode - #else: - #raise TypeError(type(data)) - - #for fn in ['__getitem__', '__setitem__']: - #setattr(self, fn, getattr(self, '_TableData'+fn+self.mode)) - - #def originalData(self): - #return self.data - - #def toArray(self): - #if self.mode == 'array': - #return self.data - #if len(self) < 1: - ##return np.array([]) ## need to return empty array *with correct columns*, but this is very difficult, so just return None - #return None - #rec1 = self[0] - #dtype = functions.suggestRecordDType(rec1) - ##print rec1, dtype - #arr = np.empty(len(self), dtype=dtype) - #arr[0] = tuple(rec1.values()) - #for i in xrange(1, len(self)): - #arr[i] = tuple(self[i].values()) - #return arr - - #def __getitem__array(self, arg): - #if isinstance(arg, tuple): - #return self.data[arg[0]][arg[1]] - #else: - #return self.data[arg] - - #def __getitem__list(self, arg): - #if isinstance(arg, basestring): - #return [d.get(arg, None) for d in self.data] - #elif isinstance(arg, int): - #return self.data[arg] - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #return self.data[arg[0]][arg[1]] - #else: - #raise TypeError(type(arg)) - - #def __getitem__dict(self, arg): - #if isinstance(arg, basestring): - #return self.data[arg] - #elif isinstance(arg, int): - #return dict([(k, v[arg]) for k, v in self.data.items()]) - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #return self.data[arg[1]][arg[0]] - #else: - #raise TypeError(type(arg)) - - #def __setitem__array(self, arg, val): - #if isinstance(arg, tuple): - #self.data[arg[0]][arg[1]] = val - #else: - #self.data[arg] = val - - #def __setitem__list(self, arg, val): - #if isinstance(arg, basestring): - #if len(val) != len(self.data): - #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data))) - #for i, rec in enumerate(self.data): - #rec[arg] = val[i] - #elif isinstance(arg, int): - #self.data[arg] = val - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #self.data[arg[0]][arg[1]] = val - #else: - #raise TypeError(type(arg)) - - #def __setitem__dict(self, arg, val): - #if isinstance(arg, basestring): - #if len(val) != len(self.data[arg]): - #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data[arg]))) - #self.data[arg] = val - #elif isinstance(arg, int): - #for k in self.data: - #self.data[k][arg] = val[k] - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #self.data[arg[1]][arg[0]] = val - #else: - #raise TypeError(type(arg)) - - #def _orderArgs(self, args): - ### return args in (int, str) order - #if isinstance(args[0], basestring): - #return (args[1], args[0]) - #else: - #return args - - #def __iter__(self): - #for i in xrange(len(self)): - #yield self[i] - - #def __len__(self): - #if self.mode == 'array' or self.mode == 'list': - #return len(self.data) - #else: - #return max(map(len, self.data.values())) - - #def columnNames(self): - #"""returns column names in no particular order""" - #if self.mode == 'array': - #return self.data.dtype.names - #elif self.mode == 'list': - #names = set() - #for row in self.data: - #names.update(row.keys()) - #return list(names) - #elif self.mode == 'dict': - #return self.data.keys() - - #def keys(self): - #return self.columnNames() diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py deleted file mode 100644 index a2affcf..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py +++ /dev/null @@ -1,1249 +0,0 @@ -# -*- coding: utf-8 -*- -import importlib -import os -import warnings -import weakref - -import numpy as np - -from ..AxisItem import AxisItem -from ..ButtonItem import ButtonItem -from ..GraphicsWidget import GraphicsWidget -from ..InfiniteLine import InfiniteLine -from ..LabelItem import LabelItem -from ..LegendItem import LegendItem -from ..PlotDataItem import PlotDataItem -from ..ViewBox import ViewBox -from ... import functions as fn -from ... import icons, PlotCurveItem, ScatterPlotItem -from ...Qt import QtGui, QtCore, QT_LIB -from ...WidgetGroup import WidgetGroup -from ...python2_3 import basestring -from ...widgets.FileDialog import FileDialog - -translate = QtCore.QCoreApplication.translate - -ui_template = importlib.import_module( - f'.plotConfigTemplate_{QT_LIB.lower()}', package=__package__) - -__all__ = ['PlotItem'] - - -class PlotItem(GraphicsWidget): - """GraphicsWidget implementing a standard 2D plotting area with axes. - - **Bases:** :class:`GraphicsWidget ` - - This class provides the ViewBox-plus-axes that appear when using - :func:`pg.plot() `, :class:`PlotWidget `, - and :func:`GraphicsLayoutWidget.addPlot() `. - - It's main functionality is: - - - Manage placement of ViewBox, AxisItems, and LabelItems - - Create and manage a list of PlotDataItems displayed inside the ViewBox - - Implement a context menu with commonly used display and analysis options - - Use :func:`plot() ` to create a new PlotDataItem and - add it to the view. Use :func:`addItem() ` to - add any QGraphicsItem to the view. - - This class wraps several methods from its internal ViewBox: - :func:`setXRange `, - :func:`setYRange `, - :func:`setRange `, - :func:`autoRange `, - :func:`setXLink `, - :func:`setYLink `, - :func:`setAutoPan `, - :func:`setAutoVisible `, - :func:`setLimits `, - :func:`viewRect `, - :func:`viewRange `, - :func:`setMouseEnabled `, - :func:`enableAutoRange `, - :func:`disableAutoRange `, - :func:`setAspectLocked `, - :func:`invertY `, - :func:`invertX `, - :func:`register `, - :func:`unregister ` - - The ViewBox itself can be accessed by calling :func:`getViewBox() ` - - ==================== ======================================================================= - **Signals:** - sigYRangeChanged wrapped from :class:`ViewBox ` - sigXRangeChanged wrapped from :class:`ViewBox ` - sigRangeChanged wrapped from :class:`ViewBox ` - ==================== ======================================================================= - """ - - sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed - sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed - sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed - - lastFileDir = None - - def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs): - """ - Create a new PlotItem. All arguments are optional. - Any extra keyword arguments are passed to :func:`PlotItem.plot() `. - - ============== ========================================================================================== - **Arguments:** - *title* Title to display at the top of the item. Html is allowed. - *labels* A dictionary specifying the axis labels to display:: - - {'left': (args), 'bottom': (args), ...} - - The name of each axis and the corresponding arguments are passed to - :func:`PlotItem.setLabel() ` - Optionally, PlotItem my also be initialized with the keyword arguments left, - right, top, or bottom to achieve the same effect. - *name* Registers a name for this view so that others may link to it - *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox. - *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items - for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') - and the values must be instances of AxisItem (or at least compatible with AxisItem). - ============== ========================================================================================== - """ - - GraphicsWidget.__init__(self, parent) - - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - - ## Set up control buttons - path = os.path.dirname(__file__) - self.autoBtn = ButtonItem(icons.getGraphPixmap('auto'), 14, self) - self.autoBtn.mode = 'auto' - self.autoBtn.clicked.connect(self.autoBtnClicked) - self.buttonsHidden = False ## whether the user has requested buttons to be hidden - self.mouseHovering = False - - self.layout = QtGui.QGraphicsGridLayout() - self.layout.setContentsMargins(1,1,1,1) - self.setLayout(self.layout) - self.layout.setHorizontalSpacing(0) - self.layout.setVerticalSpacing(0) - - if viewBox is None: - viewBox = ViewBox(parent=self, enableMenu=enableMenu) - self.vb = viewBox - self.vb.sigStateChanged.connect(self.viewStateChanged) - - # Enable or disable plotItem menu - self.setMenuEnabled(enableMenu, None) - - if name is not None: - self.vb.register(name) - self.vb.sigRangeChanged.connect(self.sigRangeChanged) - self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) - self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) - - self.layout.addItem(self.vb, 2, 1) - self.alpha = 1.0 - self.autoAlpha = True - self.spectrumMode = False - - self.legend = None - - # Initialize axis items - self.axes = {} - self.setAxisItems(axisItems) - - self.titleLabel = LabelItem('', size='11pt', parent=self) - self.layout.addItem(self.titleLabel, 0, 1) - self.setTitle(None) ## hide - - for i in range(4): - self.layout.setRowPreferredHeight(i, 0) - self.layout.setRowMinimumHeight(i, 0) - self.layout.setRowSpacing(i, 0) - self.layout.setRowStretchFactor(i, 1) - - for i in range(3): - self.layout.setColumnPreferredWidth(i, 0) - self.layout.setColumnMinimumWidth(i, 0) - self.layout.setColumnSpacing(i, 0) - self.layout.setColumnStretchFactor(i, 1) - self.layout.setRowStretchFactor(2, 100) - self.layout.setColumnStretchFactor(1, 100) - - - self.items = [] - self.curves = [] - self.itemMeta = weakref.WeakKeyDictionary() - self.dataItems = [] - self.paramList = {} - self.avgCurves = {} - - ### Set up context menu - - w = QtGui.QWidget() - self.ctrl = c = ui_template.Ui_Form() - c.setupUi(w) - dv = QtGui.QDoubleValidator(self) - - menuItems = [ - (translate("PlotItem", 'Transforms'), c.transformGroup), - (translate("PlotItem", 'Downsample'), c.decimateGroup), - (translate("PlotItem", 'Average'), c.averageGroup), - (translate("PlotItem", 'Alpha'), c.alphaGroup), - (translate("PlotItem", 'Grid'), c.gridGroup), - (translate("PlotItem", 'Points'), c.pointsGroup), - ] - - - self.ctrlMenu = QtGui.QMenu() - - self.ctrlMenu.setTitle(translate("PlotItem", 'Plot Options')) - self.subMenus = [] - for name, grp in menuItems: - sm = QtGui.QMenu(name) - act = QtGui.QWidgetAction(self) - act.setDefaultWidget(grp) - sm.addAction(act) - self.subMenus.append(sm) - self.ctrlMenu.addMenu(sm) - - self.stateGroup = WidgetGroup() - for name, w in menuItems: - self.stateGroup.autoAdd(w) - - self.fileDialog = None - - c.alphaGroup.toggled.connect(self.updateAlpha) - c.alphaSlider.valueChanged.connect(self.updateAlpha) - c.autoAlphaCheck.toggled.connect(self.updateAlpha) - - c.xGridCheck.toggled.connect(self.updateGrid) - c.yGridCheck.toggled.connect(self.updateGrid) - c.gridAlphaSlider.valueChanged.connect(self.updateGrid) - - c.fftCheck.toggled.connect(self.updateSpectrumMode) - c.logXCheck.toggled.connect(self.updateLogMode) - c.logYCheck.toggled.connect(self.updateLogMode) - c.derivativeCheck.toggled.connect(self.updateDerivativeMode) - c.phasemapCheck.toggled.connect(self.updatePhasemapMode) - - c.downsampleSpin.valueChanged.connect(self.updateDownsampling) - c.downsampleCheck.toggled.connect(self.updateDownsampling) - c.autoDownsampleCheck.toggled.connect(self.updateDownsampling) - c.subsampleRadio.toggled.connect(self.updateDownsampling) - c.meanRadio.toggled.connect(self.updateDownsampling) - c.clipToViewCheck.toggled.connect(self.updateDownsampling) - - self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) - self.ctrl.averageGroup.toggled.connect(self.avgToggled) - - self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) - self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) - - if labels is None: - labels = {} - for label in list(self.axes.keys()): - if label in kargs: - labels[label] = kargs[label] - del kargs[label] - for k in labels: - if isinstance(labels[k], basestring): - labels[k] = (labels[k],) - self.setLabel(k, *labels[k]) - - if title is not None: - self.setTitle(title) - - if len(kargs) > 0: - self.plot(**kargs) - - def implements(self, interface=None): - return interface in ['ViewBoxWrapper'] - - def getViewBox(self): - """Return the :class:`ViewBox ` contained within.""" - return self.vb - - ## Wrap a few methods from viewBox. - #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive - #because we had a reference to an instance method (creating wrapper methods at runtime instead). - for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE: - 'setAutoVisible', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please - 'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring - 'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well. - - def _create_method(name): - def method(self, *args, **kwargs): - return getattr(self.vb, name)(*args, **kwargs) - method.__name__ = name - return method - - locals()[m] = _create_method(m) - - del _create_method - - def setAxisItems(self, axisItems=None): - """ - Place axis items as given by `axisItems`. Initializes non-existing axis items. - - ============== ========================================================================================== - **Arguments:** - *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items - for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') - and the values must be instances of AxisItem (or at least compatible with AxisItem). - ============== ========================================================================================== - """ - - - if axisItems is None: - axisItems = {} - - # Array containing visible axis items - # Also containing potentially hidden axes, but they are not touched so it does not matter - visibleAxes = ['left', 'bottom'] - visibleAxes.extend(axisItems.keys()) # Note that it does not matter that this adds - # some values to visibleAxes a second time - - for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): - if k in self.axes: - if k not in axisItems: - continue # Nothing to do here - - # Remove old axis - oldAxis = self.axes[k]['item'] - self.layout.removeItem(oldAxis) - oldAxis.scene().removeItem(oldAxis) - oldAxis.unlinkFromView() - - # Create new axis - if k in axisItems: - axis = axisItems[k] - if axis.scene() is not None: - if k not in self.axes or axis != self.axes[k]["item"]: - raise RuntimeError( - "Can't add an axis to multiple plots. Shared axes" - " can be achieved with multiple AxisItem instances" - " and set[X/Y]Link.") - else: - axis = AxisItem(orientation=k, parent=self) - - # Set up new axis - axis.linkToView(self.vb) - self.axes[k] = {'item': axis, 'pos': pos} - self.layout.addItem(axis, *pos) - axis.setZValue(-1000) - axis.setFlag(axis.ItemNegativeZStacksBehindParent) - - axisVisible = k in visibleAxes - self.showAxis(k, axisVisible) - - def setLogMode(self, x=None, y=None): - """ - Set log scaling for x and/or y axes. - This informs PlotDataItems to transform logarithmically and switches - the axes to use log ticking. - - Note that *no other items* in the scene will be affected by - this; there is (currently) no generic way to redisplay a GraphicsItem - with log coordinates. - - """ - if x is not None: - self.ctrl.logXCheck.setChecked(x) - if y is not None: - self.ctrl.logYCheck.setChecked(y) - - def showGrid(self, x=None, y=None, alpha=None): - """ - Show or hide the grid for either axis. - - ============== ===================================== - **Arguments:** - x (bool) Whether to show the X grid - y (bool) Whether to show the Y grid - alpha (0.0-1.0) Opacity of the grid - ============== ===================================== - """ - if x is None and y is None and alpha is None: - raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid() - - if x is not None: - self.ctrl.xGridCheck.setChecked(x) - if y is not None: - self.ctrl.yGridCheck.setChecked(y) - if alpha is not None: - v = np.clip(alpha, 0, 1)*self.ctrl.gridAlphaSlider.maximum() - self.ctrl.gridAlphaSlider.setValue(v) - - def close(self): - ## Most of this crap is needed to avoid PySide trouble. - ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) - ## the solution is to manually remove all widgets before scene.clear() is called - if self.ctrlMenu is None: ## already shut down - return - self.ctrlMenu.setParent(None) - self.ctrlMenu = None - - self.autoBtn.setParent(None) - self.autoBtn = None - - for k in self.axes: - i = self.axes[k]['item'] - i.close() - - self.axes = None - self.scene().removeItem(self.vb) - self.vb = None - - def registerPlot(self, name): ## for backward compatibility - self.vb.register(name) - - def updateGrid(self, *args): - alpha = self.ctrl.gridAlphaSlider.value() - x = alpha if self.ctrl.xGridCheck.isChecked() else False - y = alpha if self.ctrl.yGridCheck.isChecked() else False - self.getAxis('top').setGrid(x) - self.getAxis('bottom').setGrid(x) - self.getAxis('left').setGrid(y) - self.getAxis('right').setGrid(y) - - def viewGeometry(self): - """Return the screen geometry of the viewbox""" - v = self.scene().views()[0] - b = self.vb.mapRectToScene(self.vb.boundingRect()) - wr = v.mapFromScene(b).boundingRect() - pos = v.mapToGlobal(v.pos()) - wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) - return wr - - def avgToggled(self, b): - if b: - self.recomputeAverages() - for k in self.avgCurves: - self.avgCurves[k][1].setVisible(b) - - def avgParamListClicked(self, item): - name = str(item.text()) - self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) - self.recomputeAverages() - - def recomputeAverages(self): - if not self.ctrl.averageGroup.isChecked(): - return - for k in self.avgCurves: - self.removeItem(self.avgCurves[k][1]) - self.avgCurves = {} - for c in self.curves: - self.addAvgCurve(c) - self.replot() - - def addAvgCurve(self, curve): - ## Add a single curve into the pool of curves averaged together - - ## If there are plot parameters, then we need to determine which to average together. - remKeys = [] - addKeys = [] - if self.ctrl.avgParamList.count() > 0: - - ### First determine the key of the curve to which this new data should be averaged - for i in range(self.ctrl.avgParamList.count()): - item = self.ctrl.avgParamList.item(i) - if item.checkState() == QtCore.Qt.Checked: - remKeys.append(str(item.text())) - else: - addKeys.append(str(item.text())) - - if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. - return - - p = self.itemMeta.get(curve,{}).copy() - for k in p: - if type(k) is tuple: - p['.'.join(k)] = p[k] - del p[k] - for rk in remKeys: - if rk in p: - del p[rk] - for ak in addKeys: - if ak not in p: - p[ak] = None - key = tuple(p.items()) - - ### Create a new curve if needed - if key not in self.avgCurves: - plot = PlotDataItem() - plot.setPen(fn.mkPen([0, 200, 0])) - plot.setShadowPen(fn.mkPen([0, 0, 0, 100], width=3)) - plot.setAlpha(1.0, False) - plot.setZValue(100) - self.addItem(plot, skipAverage=True) - self.avgCurves[key] = [0, plot] - self.avgCurves[key][0] += 1 - (n, plot) = self.avgCurves[key] - - ### Average data together - (x, y) = curve.getData() - stepMode = curve.opts['stepMode'] - if plot.yData is not None and y.shape == plot.yData.shape: - # note that if shapes do not match, then the average resets. - newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) - plot.setData(plot.xData, newData, stepMode=stepMode) - else: - plot.setData(x, y, stepMode=stepMode) - - def autoBtnClicked(self): - if self.autoBtn.mode == 'auto': - self.enableAutoRange() - self.autoBtn.hide() - else: - self.disableAutoRange() - - def viewStateChanged(self): - self.updateButtons() - - def enableAutoScale(self): - """ - Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. - """ - warnings.warn( - 'PlotItem.enableAutoScale is deprecated, and will be removed in 0.13' - 'Use PlotItem.enableAutoRange(axis, enable) instead', - DeprecationWarning, stacklevel=2 - ) - self.vb.enableAutoRange(self.vb.XYAxes) - - def addItem(self, item, *args, **kargs): - """ - Add a graphics item to the view box. - If the item has plot data (PlotDataItem, PlotCurveItem, ScatterPlotItem), it may - be included in analysis performed by the PlotItem. - """ - if item in self.items: - warnings.warn('Item already added to PlotItem, ignoring.') - return - self.items.append(item) - vbargs = {} - if 'ignoreBounds' in kargs: - vbargs['ignoreBounds'] = kargs['ignoreBounds'] - self.vb.addItem(item, *args, **vbargs) - name = None - if hasattr(item, 'implements') and item.implements('plotData'): - name = item.name() - self.dataItems.append(item) - #self.plotChanged() - - params = kargs.get('params', {}) - self.itemMeta[item] = params - #item.setMeta(params) - self.curves.append(item) - #self.addItem(c) - - if hasattr(item, 'setLogMode'): - item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked()) - - if isinstance(item, PlotDataItem): - ## configure curve for this plot - (alpha, auto) = self.alphaState() - item.setAlpha(alpha, auto) - item.setFftMode(self.ctrl.fftCheck.isChecked()) - item.setDownsampling(*self.downsampleMode()) - item.setClipToView(self.clipToViewMode()) - item.setPointMode(self.pointMode()) - - ## Hide older plots if needed - self.updateDecimation() - - ## Add to average if needed - self.updateParamList() - if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: - self.addAvgCurve(item) - - #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) - #item.sigPlotChanged.connect(self.plotChanged) - #self.plotChanged() - #name = kargs.get('name', getattr(item, 'opts', {}).get('name', None)) - if name is not None and hasattr(self, 'legend') and self.legend is not None: - self.legend.addItem(item, name=name) - - def addDataItem(self, item, *args): - warnings.warn( - 'PlotItem.addDataItem is deprecated and will be removed in 0.13. ' - 'Use PlotItem.addItem instead', - DeprecationWarning, stacklevel=2 - ) - self.addItem(item, *args) - - def listDataItems(self): - """Return a list of all data items (PlotDataItem, PlotCurveItem, ScatterPlotItem, etc) - contained in this PlotItem.""" - return self.dataItems[:] - - def addCurve(self, c, params=None): - warnings.warn( - 'PlotItem.addCurve is deprecated and will be removed in 0.13. ' - 'Use PlotItem.addItem instead.', - DeprecationWarning, stacklevel=2 - ) - - self.addItem(c, params) - - def addLine(self, x=None, y=None, z=None, **kwds): - """ - Create an InfiniteLine and add to the plot. - - If *x* is specified, - the line will be vertical. If *y* is specified, the line will be - horizontal. All extra keyword arguments are passed to - :func:`InfiniteLine.__init__() `. - Returns the item created. - """ - kwds['pos'] = kwds.get('pos', x if x is not None else y) - kwds['angle'] = kwds.get('angle', 0 if x is None else 90) - line = InfiniteLine(**kwds) - self.addItem(line) - if z is not None: - line.setZValue(z) - return line - - def removeItem(self, item): - """ - Remove an item from the internal ViewBox. - """ - if not item in self.items: - return - self.items.remove(item) - if item in self.dataItems: - self.dataItems.remove(item) - - self.vb.removeItem(item) - - if item in self.curves: - self.curves.remove(item) - self.updateDecimation() - self.updateParamList() - - if self.legend is not None: - self.legend.removeItem(item) - - def clear(self): - """ - Remove all items from the ViewBox. - """ - for i in self.items[:]: - self.removeItem(i) - self.avgCurves = {} - - def clearPlots(self): - for i in self.curves[:]: - self.removeItem(i) - self.avgCurves = {} - - def plot(self, *args, **kargs): - """ - Add and return a new plot. - See :func:`PlotDataItem.__init__ ` for data arguments - - Extra allowed arguments are: - clear - clear all plots before displaying new data - params - meta-parameters to associate with this data - """ - clear = kargs.get('clear', False) - params = kargs.get('params', None) - - if clear: - self.clear() - - item = PlotDataItem(*args, **kargs) - - if params is None: - params = {} - self.addItem(item, params=params) - - return item - - def addLegend(self, offset=(30, 30), **kwargs): - """ - Create a new :class:`~pyqtgraph.LegendItem` and anchor it over the - internal ViewBox. Plots will be automatically displayed in the legend - if they are created with the 'name' argument. - - If a LegendItem has already been created using this method, that - item will be returned rather than creating a new one. - - Accepts the same arguments as :meth:`~pyqtgraph.LegendItem`. - """ - - if self.legend is None: - self.legend = LegendItem(offset=offset, **kwargs) - self.legend.setParentItem(self.vb) - return self.legend - - def scatterPlot(self, *args, **kargs): - if 'pen' in kargs: - kargs['symbolPen'] = kargs['pen'] - kargs['pen'] = None - - if 'brush' in kargs: - kargs['symbolBrush'] = kargs['brush'] - del kargs['brush'] - - if 'size' in kargs: - kargs['symbolSize'] = kargs['size'] - del kargs['size'] - - return self.plot(*args, **kargs) - - def replot(self): - self.update() - - def updateParamList(self): - self.ctrl.avgParamList.clear() - ## Check to see that each parameter for each curve is present in the list - for c in self.curves: - for p in list(self.itemMeta.get(c, {}).keys()): - if type(p) is tuple: - p = '.'.join(p) - - ## If the parameter is not in the list, add it. - matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) - if len(matches) == 0: - i = QtGui.QListWidgetItem(p) - if p in self.paramList and self.paramList[p] is True: - i.setCheckState(QtCore.Qt.Checked) - else: - i.setCheckState(QtCore.Qt.Unchecked) - self.ctrl.avgParamList.addItem(i) - else: - i = matches[0] - - self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) - - def writeSvgCurves(self, fileName=None): - if fileName is None: - self._chooseFilenameDialog(handler=self.writeSvg) - return - - if isinstance(fileName, tuple): - raise Exception("Not implemented yet..") - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - rect = self.vb.viewRect() - xRange = rect.left(), rect.right() - - svg = "" - - dx = max(rect.right(),0) - min(rect.left(),0) - ymn = min(rect.top(), rect.bottom()) - ymx = max(rect.top(), rect.bottom()) - dy = max(ymx,0) - min(ymn,0) - sx = 1. - sy = 1. - while dx*sx < 10: - sx *= 1000 - while dy*sy < 10: - sy *= 1000 - sy *= -1 - - with open(fileName, 'w') as fh: - # fh.write('\n' % (rect.left() * sx, - # rect.top() * sx, - # rect.width() * sy, - # rect.height()*sy)) - fh.write('\n') - fh.write('\n' % ( - rect.left() * sx, rect.right() * sx)) - fh.write('\n' % ( - rect.top() * sy, rect.bottom() * sy)) - - for item in self.curves: - if isinstance(item, PlotCurveItem): - color = fn.colorStr(item.pen.color()) - opacity = item.pen.color().alpha() / 255. - color = color[:6] - x, y = item.getData() - mask = (x > xRange[0]) * (x < xRange[1]) - mask[:-1] += mask[1:] - m2 = mask.copy() - mask[1:] += m2[:-1] - x = x[mask] - y = y[mask] - - x *= sx - y *= sy - - # fh.write('\n' % ( - # color, )) - fh.write('') - # fh.write("") - - for item in self.dataItems: - if isinstance(item, ScatterPlotItem): - pRect = item.boundingRect() - vRect = pRect.intersected(rect) - - for point in item.points(): - pos = point.pos() - if not rect.contains(pos): - continue - color = fn.colorStr(point.brush.color()) - opacity = point.brush.color().alpha() / 255. - color = color[:6] - x = pos.x() * sx - y = pos.y() * sy - - fh.write('\n' % ( - x, y, color, opacity)) - - fh.write("\n") - - def writeSvg(self, fileName=None): - if fileName is None: - self._chooseFilenameDialog(handler=self.writeSvg) - return - - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - from ...exporters import SVGExporter - ex = SVGExporter(self) - ex.export(fileName) - - def writeImage(self, fileName=None): - if fileName is None: - self._chooseFilenameDialog(handler=self.writeImage) - return - - from ...exporters import ImageExporter - ex = ImageExporter(self) - ex.export(fileName) - - def writeCsv(self, fileName=None): - if fileName is None: - self._chooseFilenameDialog(handler=self.writeCsv) - return - - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - data = [c.getData() for c in self.curves] - with open(fileName, 'w') as fd: - i = 0 - while True: - done = True - for d in data: - if i < len(d[0]): - fd.write('%g,%g,' % (d[0][i], d[1][i])) - done = False - else: - fd.write(' , ,') - fd.write('\n') - if done: - break - i += 1 - - def saveState(self): - state = self.stateGroup.state() - state['paramList'] = self.paramList.copy() - state['view'] = self.vb.getState() - return state - - def restoreState(self, state): - if 'paramList' in state: - self.paramList = state['paramList'].copy() - - self.stateGroup.setState(state) - self.updateSpectrumMode() - self.updateDownsampling() - self.updateAlpha() - self.updateDecimation() - - if 'powerSpectrumGroup' in state: - state['fftCheck'] = state['powerSpectrumGroup'] - if 'gridGroup' in state: - state['xGridCheck'] = state['gridGroup'] - state['yGridCheck'] = state['gridGroup'] - - self.stateGroup.setState(state) - self.updateParamList() - - if 'view' not in state: - r = [[float(state['xMinText']), float(state['xMaxText'])], [float(state['yMinText']), float(state['yMaxText'])]] - state['view'] = { - 'autoRange': [state['xAutoRadio'], state['yAutoRadio']], - 'linkedViews': [state['xLinkCombo'], state['yLinkCombo']], - 'targetRange': r, - 'viewRange': r, - } - self.vb.setState(state['view']) - - def widgetGroupInterface(self): - return (None, PlotItem.saveState, PlotItem.restoreState) - - def updateSpectrumMode(self, b=None): - if b is None: - b = self.ctrl.fftCheck.isChecked() - for c in self.curves: - c.setFftMode(b) - self.enableAutoRange() - self.recomputeAverages() - - def updateLogMode(self): - x = self.ctrl.logXCheck.isChecked() - y = self.ctrl.logYCheck.isChecked() - for i in self.items: - if hasattr(i, 'setLogMode'): - i.setLogMode(x,y) - self.getAxis('bottom').setLogMode(x) - self.getAxis('top').setLogMode(x) - self.getAxis('left').setLogMode(y) - self.getAxis('right').setLogMode(y) - self.enableAutoRange() - self.recomputeAverages() - - def updateDerivativeMode(self): - d = self.ctrl.derivativeCheck.isChecked() - for i in self.items: - if hasattr(i, 'setDerivativeMode'): - i.setDerivativeMode(d) - self.enableAutoRange() - self.recomputeAverages() - - def updatePhasemapMode(self): - d = self.ctrl.phasemapCheck.isChecked() - for i in self.items: - if hasattr(i, 'setPhasemapMode'): - i.setPhasemapMode(d) - self.enableAutoRange() - self.recomputeAverages() - - - def setDownsampling(self, ds=None, auto=None, mode=None): - """Change the default downsampling mode for all PlotDataItems managed by this plot. - - =============== ================================================================= - **Arguments:** - ds (int) Reduce visible plot samples by this factor, or - (bool) To enable/disable downsampling without changing the value. - auto (bool) If True, automatically pick *ds* based on visible range - mode 'subsample': Downsample by taking the first of N samples. - This method is fastest and least accurate. - 'mean': Downsample by taking the mean of N samples. - 'peak': Downsample by drawing a saw wave that follows the min - and max of the original data. This method produces the best - visual representation of the data but is slower. - =============== ================================================================= - """ - if ds is not None: - if ds is False: - self.ctrl.downsampleCheck.setChecked(False) - elif ds is True: - self.ctrl.downsampleCheck.setChecked(True) - else: - self.ctrl.downsampleCheck.setChecked(True) - self.ctrl.downsampleSpin.setValue(ds) - - if auto is not None: - if auto and ds is not False: - self.ctrl.downsampleCheck.setChecked(True) - self.ctrl.autoDownsampleCheck.setChecked(auto) - - if mode is not None: - if mode == 'subsample': - self.ctrl.subsampleRadio.setChecked(True) - elif mode == 'mean': - self.ctrl.meanRadio.setChecked(True) - elif mode == 'peak': - self.ctrl.peakRadio.setChecked(True) - else: - raise ValueError("mode argument must be 'subsample', 'mean', or 'peak'.") - - def updateDownsampling(self): - ds, auto, method = self.downsampleMode() - clip = self.ctrl.clipToViewCheck.isChecked() - for c in self.curves: - c.setDownsampling(ds, auto, method) - c.setClipToView(clip) - self.recomputeAverages() - - def downsampleMode(self): - if self.ctrl.downsampleCheck.isChecked(): - ds = self.ctrl.downsampleSpin.value() - else: - ds = 1 - - auto = self.ctrl.downsampleCheck.isChecked() and self.ctrl.autoDownsampleCheck.isChecked() - - if self.ctrl.subsampleRadio.isChecked(): - method = 'subsample' - elif self.ctrl.meanRadio.isChecked(): - method = 'mean' - elif self.ctrl.peakRadio.isChecked(): - method = 'peak' - - return ds, auto, method - - def setClipToView(self, clip): - """Set the default clip-to-view mode for all PlotDataItems managed by this plot. - If *clip* is True, then PlotDataItems will attempt to draw only points within the visible - range of the ViewBox.""" - self.ctrl.clipToViewCheck.setChecked(clip) - - def clipToViewMode(self): - return self.ctrl.clipToViewCheck.isChecked() - - def updateDecimation(self): - if self.ctrl.maxTracesCheck.isChecked(): - numCurves = self.ctrl.maxTracesSpin.value() - else: - numCurves = -1 - - curves = self.curves[:] - split = len(curves) - numCurves - for curve in curves[split:]: - if numCurves != -1: - if self.ctrl.forgetTracesCheck.isChecked(): - curve.clear() - self.removeItem(curve) - else: - curve.hide() - - def updateAlpha(self, *args): - (alpha, auto) = self.alphaState() - for c in self.curves: - c.setAlpha(alpha**2, auto) - - def alphaState(self): - enabled = self.ctrl.alphaGroup.isChecked() - auto = self.ctrl.autoAlphaCheck.isChecked() - alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() - if auto: - alpha = 1.0 ## should be 1/number of overlapping plots - if not enabled: - auto = False - alpha = 1.0 - return (alpha, auto) - - def pointMode(self): - if self.ctrl.pointsGroup.isChecked(): - if self.ctrl.autoPointsCheck.isChecked(): - mode = None - else: - mode = True - else: - mode = False - return mode - - def resizeEvent(self, ev): - if self.autoBtn is None: ## already closed down - return - btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) - y = self.size().height() - btnRect.height() - self.autoBtn.setPos(0, y) - - def getMenu(self): - return self.ctrlMenu - - def getContextMenus(self, event): - ## called when another item is displaying its context menu; we get to add extras to the end of the menu. - if self.menuEnabled(): - return self.ctrlMenu - else: - return None - - def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'): - """ - Enable or disable the context menu for this PlotItem. - By default, the ViewBox's context menu will also be affected. - (use enableViewBoxMenu=None to leave the ViewBox unchanged) - """ - self._menuEnabled = enableMenu - if enableViewBoxMenu is None: - return - if enableViewBoxMenu == 'same': - enableViewBoxMenu = enableMenu - self.vb.setMenuEnabled(enableViewBoxMenu) - - def menuEnabled(self): - return self._menuEnabled - - def hoverEvent(self, ev): - if ev.enter: - self.mouseHovering = True - if ev.exit: - self.mouseHovering = False - - self.updateButtons() - - def getLabel(self, key): - pass - - def _checkScaleKey(self, key): - if key not in self.axes: - raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys())))) - - def getScale(self, key): - return self.getAxis(key) - - def getAxis(self, name): - """Return the specified AxisItem. - *name* should be 'left', 'bottom', 'top', or 'right'.""" - self._checkScaleKey(name) - return self.axes[name]['item'] - - def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): - """ - Set the label for an axis. Basic HTML formatting is allowed. - - ============== ================================================================= - **Arguments:** - axis must be one of 'left', 'bottom', 'right', or 'top' - text text to display along the axis. HTML allowed. - units units to display after the title. If units are given, - then an SI prefix will be automatically appended - and the axis values will be scaled accordingly. - (ie, use 'V' instead of 'mV'; 'm' will be added automatically) - ============== ================================================================= - """ - self.getAxis(axis).setLabel(text=text, units=units, **args) - self.showAxis(axis) - - def setLabels(self, **kwds): - """ - Convenience function allowing multiple labels and/or title to be set in one call. - Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. - Values may be strings or a tuple of arguments to pass to setLabel. - """ - for k,v in kwds.items(): - if k == 'title': - self.setTitle(v) - else: - if isinstance(v, basestring): - v = (v,) - self.setLabel(k, *v) - - def showLabel(self, axis, show=True): - """ - Show or hide one of the plot's axis labels (the axis itself will be unaffected). - axis must be one of 'left', 'bottom', 'right', or 'top' - """ - self.getScale(axis).showLabel(show) - - def setTitle(self, title=None, **args): - """ - Set the title of the plot. Basic HTML formatting is allowed. - If title is None, then the title will be hidden. - """ - if title is None: - self.titleLabel.setVisible(False) - self.layout.setRowFixedHeight(0, 0) - self.titleLabel.setMaximumHeight(0) - else: - self.titleLabel.setMaximumHeight(30) - self.layout.setRowFixedHeight(0, 30) - self.titleLabel.setVisible(True) - self.titleLabel.setText(title, **args) - - def showAxis(self, axis, show=True): - """ - Show or hide one of the plot's axes. - axis must be one of 'left', 'bottom', 'right', or 'top' - """ - s = self.getScale(axis) - p = self.axes[axis]['pos'] - if show: - s.show() - else: - s.hide() - - def hideAxis(self, axis): - """Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')""" - self.showAxis(axis, False) - - def showScale(self, *args, **kargs): - warnings.warn( - 'PlotItem.showScale has been deprecated and will be removed in 0.13. ' - 'Use PlotItem.showAxis() instead', - DeprecationWarning, stacklevel=2 - ) - return self.showAxis(*args, **kargs) - - def hideButtons(self): - """Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem""" - #self.ctrlBtn.hide() - self.buttonsHidden = True - self.updateButtons() - - def showButtons(self): - """Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem""" - #self.ctrlBtn.hide() - self.buttonsHidden = False - self.updateButtons() - - def updateButtons(self): - try: - if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()): - self.autoBtn.show() - else: - self.autoBtn.hide() - except RuntimeError: - pass # this can happen if the plot has been deleted. - - def _plotArray(self, arr, x=None, **kargs): - if arr.ndim != 1: - raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) - if x is None: - x = np.arange(arr.shape[0]) - if x.ndim != 1: - raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) - c = PlotCurveItem(arr, x=x, **kargs) - return c - - def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): - inf = arr.infoCopy() - if arr.ndim != 1: - raise Exception('can only automatically plot 1 dimensional arrays.') - ## create curve - try: - xv = arr.xvals(0) - except: - if x is None: - xv = np.arange(arr.shape[0]) - else: - xv = x - c = PlotCurveItem(**kargs) - c.setData(x=xv, y=arr.view(np.ndarray)) - - if autoLabel: - name = arr._info[0].get('name', None) - units = arr._info[0].get('units', None) - self.setLabel('bottom', text=name, units=units) - - name = arr._info[1].get('name', None) - units = arr._info[1].get('units', None) - self.setLabel('left', text=name, units=units) - - return c - - def setExportMode(self, export, opts=None): - GraphicsWidget.setExportMode(self, export, opts) - self.updateButtons() - - def _chooseFilenameDialog(self, handler): - self.fileDialog = FileDialog() - if PlotItem.lastFileDir is not None: - self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - self.fileDialog.fileSelected.connect(handler) diff --git a/pyqtgraph/graphicsItems/PlotItem/__init__.py b/pyqtgraph/graphicsItems/PlotItem/__init__.py deleted file mode 100644 index d797978..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .PlotItem import PlotItem diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-36.pyc deleted file mode 100644 index 22758a6..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-37.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-37.pyc deleted file mode 100644 index 517aa72..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/PlotItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 00e5824..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 9e087bf..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index db49c34..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index c890eaa..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside2.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index 375a837..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index 243e5d0..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-37.pyc b/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-37.pyc deleted file mode 100644 index b9ac8b7..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/__pycache__/plotConfigTemplate_pyside6.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py deleted file mode 100644 index 1df7581..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt5.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'plotConfigTemplate.ui' -# -# Created by: PyQt5 UI code generator 5.12.3 -# -# WARNING! All changes made in this file will be lost! - - -from PyQt5 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(481, 840) - self.averageGroup = QtWidgets.QGroupBox(Form) - self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName("averageGroup") - self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName("gridLayout_5") - self.avgParamList = QtWidgets.QListWidget(self.averageGroup) - self.avgParamList.setObjectName("avgParamList") - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.decimateGroup = QtWidgets.QFrame(Form) - self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) - self.decimateGroup.setObjectName("decimateGroup") - self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") - self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName("clipToViewCheck") - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName("maxTracesCheck") - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.peakRadio.setChecked(True) - self.peakRadio.setObjectName("peakRadio") - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName("maxTracesSpin") - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName("forgetTracesCheck") - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName("meanRadio") - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName("subsampleRadio") - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setChecked(True) - self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) - self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName("downsampleSpin") - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - self.transformGroup = QtWidgets.QFrame(Form) - self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) - self.transformGroup.setObjectName("transformGroup") - self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logYCheck.setObjectName("logYCheck") - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logXCheck.setObjectName("logXCheck") - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) - self.fftCheck.setObjectName("fftCheck") - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) - self.derivativeCheck.setObjectName("derivativeCheck") - self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) - self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) - self.phasemapCheck.setObjectName("phasemapCheck") - self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) - self.pointsGroup = QtWidgets.QGroupBox(Form) - self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName("pointsGroup") - self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName("verticalLayout_5") - self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName("autoPointsCheck") - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.gridGroup = QtWidgets.QFrame(Form) - self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) - self.gridGroup.setObjectName("gridGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName("gridLayout_2") - self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName("xGridCheck") - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName("yGridCheck") - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 128) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName("gridAlphaSlider") - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - self.label = QtWidgets.QLabel(self.gridGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - self.alphaGroup = QtWidgets.QGroupBox(Form) - self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName("alphaGroup") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName("horizontalLayout") - self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName("autoAlphaCheck") - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName("alphaSlider") - self.horizontalLayout.addWidget(self.alphaSlider) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) - self.averageGroup.setTitle(_translate("Form", "Average")) - self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) - self.clipToViewCheck.setText(_translate("Form", "Clip to View")) - self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) - self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) - self.downsampleCheck.setText(_translate("Form", "Downsample")) - self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) - self.peakRadio.setText(_translate("Form", "Peak")) - self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) - self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) - self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) - self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) - self.meanRadio.setText(_translate("Form", "Mean")) - self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) - self.subsampleRadio.setText(_translate("Form", "Subsample")) - self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) - self.autoDownsampleCheck.setText(_translate("Form", "Auto")) - self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) - self.downsampleSpin.setSuffix(_translate("Form", "x")) - self.logYCheck.setText(_translate("Form", "Log Y")) - self.logXCheck.setText(_translate("Form", "Log X")) - self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) - self.derivativeCheck.setText(_translate("Form", "dy/dx")) - self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) - self.pointsGroup.setTitle(_translate("Form", "Points")) - self.autoPointsCheck.setText(_translate("Form", "Auto")) - self.xGridCheck.setText(_translate("Form", "Show X Grid")) - self.yGridCheck.setText(_translate("Form", "Show Y Grid")) - self.label.setText(_translate("Form", "Opacity")) - self.alphaGroup.setTitle(_translate("Form", "Alpha")) - self.autoAlphaCheck.setText(_translate("Form", "Auto")) diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py deleted file mode 100644 index 213f4db..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py +++ /dev/null @@ -1,178 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\graphicsItems\PlotItem\plotConfigTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(481, 840) - self.averageGroup = QtWidgets.QGroupBox(Form) - self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName("averageGroup") - self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName("gridLayout_5") - self.avgParamList = QtWidgets.QListWidget(self.averageGroup) - self.avgParamList.setObjectName("avgParamList") - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.decimateGroup = QtWidgets.QFrame(Form) - self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) - self.decimateGroup.setObjectName("decimateGroup") - self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") - self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName("clipToViewCheck") - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName("maxTracesCheck") - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.peakRadio.setChecked(True) - self.peakRadio.setObjectName("peakRadio") - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName("maxTracesSpin") - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName("forgetTracesCheck") - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName("meanRadio") - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName("subsampleRadio") - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setChecked(True) - self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) - self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName("downsampleSpin") - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - self.transformGroup = QtWidgets.QFrame(Form) - self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) - self.transformGroup.setObjectName("transformGroup") - self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logYCheck.setObjectName("logYCheck") - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logXCheck.setObjectName("logXCheck") - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) - self.fftCheck.setObjectName("fftCheck") - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) - self.derivativeCheck.setObjectName("derivativeCheck") - self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) - self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) - self.phasemapCheck.setObjectName("phasemapCheck") - self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) - self.pointsGroup = QtWidgets.QGroupBox(Form) - self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName("pointsGroup") - self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName("verticalLayout_5") - self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName("autoPointsCheck") - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.gridGroup = QtWidgets.QFrame(Form) - self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) - self.gridGroup.setObjectName("gridGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName("gridLayout_2") - self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName("xGridCheck") - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName("yGridCheck") - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 128) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Orientations.Horizontal) - self.gridAlphaSlider.setObjectName("gridAlphaSlider") - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - self.label = QtWidgets.QLabel(self.gridGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - self.alphaGroup = QtWidgets.QGroupBox(Form) - self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName("alphaGroup") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName("horizontalLayout") - self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName("autoAlphaCheck") - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Orientations.Horizontal) - self.alphaSlider.setObjectName("alphaSlider") - self.horizontalLayout.addWidget(self.alphaSlider) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) - self.averageGroup.setTitle(_translate("Form", "Average")) - self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) - self.clipToViewCheck.setText(_translate("Form", "Clip to View")) - self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) - self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) - self.downsampleCheck.setText(_translate("Form", "Downsample")) - self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) - self.peakRadio.setText(_translate("Form", "Peak")) - self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) - self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) - self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) - self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) - self.meanRadio.setText(_translate("Form", "Mean")) - self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) - self.subsampleRadio.setText(_translate("Form", "Subsample")) - self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) - self.autoDownsampleCheck.setText(_translate("Form", "Auto")) - self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) - self.downsampleSpin.setSuffix(_translate("Form", "x")) - self.logYCheck.setText(_translate("Form", "Log Y")) - self.logXCheck.setText(_translate("Form", "Log X")) - self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) - self.derivativeCheck.setText(_translate("Form", "dy/dx")) - self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) - self.pointsGroup.setTitle(_translate("Form", "Points")) - self.autoPointsCheck.setText(_translate("Form", "Auto")) - self.xGridCheck.setText(_translate("Form", "Show X Grid")) - self.yGridCheck.setText(_translate("Form", "Show Y Grid")) - self.label.setText(_translate("Form", "Opacity")) - self.alphaGroup.setTitle(_translate("Form", "Alpha")) - self.autoAlphaCheck.setText(_translate("Form", "Auto")) diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside2.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside2.py deleted file mode 100644 index d5c59ac..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside2.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'plotConfigTemplate.ui', -# licensing of 'plotConfigTemplate.ui' applies. -# -# Created: Sun Jan 31 22:10:16 2021 -# by: pyside2-uic running on PySide2 5.12.6 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(481, 840) - self.averageGroup = QtWidgets.QGroupBox(Form) - self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName("averageGroup") - self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName("gridLayout_5") - self.avgParamList = QtWidgets.QListWidget(self.averageGroup) - self.avgParamList.setObjectName("avgParamList") - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.decimateGroup = QtWidgets.QFrame(Form) - self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) - self.decimateGroup.setObjectName("decimateGroup") - self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") - self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName("clipToViewCheck") - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName("maxTracesCheck") - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.peakRadio.setChecked(True) - self.peakRadio.setObjectName("peakRadio") - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName("maxTracesSpin") - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName("forgetTracesCheck") - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName("meanRadio") - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName("subsampleRadio") - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setChecked(True) - self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) - self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName("downsampleSpin") - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - self.transformGroup = QtWidgets.QFrame(Form) - self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) - self.transformGroup.setObjectName("transformGroup") - self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logYCheck.setObjectName("logYCheck") - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) - self.logXCheck.setObjectName("logXCheck") - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) - self.fftCheck.setObjectName("fftCheck") - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) - self.derivativeCheck.setObjectName("derivativeCheck") - self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) - self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) - self.phasemapCheck.setObjectName("phasemapCheck") - self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) - self.pointsGroup = QtWidgets.QGroupBox(Form) - self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName("pointsGroup") - self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName("verticalLayout_5") - self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName("autoPointsCheck") - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.gridGroup = QtWidgets.QFrame(Form) - self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) - self.gridGroup.setObjectName("gridGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName("gridLayout_2") - self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName("xGridCheck") - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName("yGridCheck") - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 128) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName("gridAlphaSlider") - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - self.label = QtWidgets.QLabel(self.gridGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - self.alphaGroup = QtWidgets.QGroupBox(Form) - self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName("alphaGroup") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName("horizontalLayout") - self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName("autoAlphaCheck") - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName("alphaSlider") - self.horizontalLayout.addWidget(self.alphaSlider) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "PyQtGraph", None, -1)) - self.averageGroup.setToolTip(QtWidgets.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, -1)) - self.averageGroup.setTitle(QtWidgets.QApplication.translate("Form", "Average", None, -1)) - self.clipToViewCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, -1)) - self.clipToViewCheck.setText(QtWidgets.QApplication.translate("Form", "Clip to View", None, -1)) - self.maxTracesCheck.setToolTip(QtWidgets.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, -1)) - self.maxTracesCheck.setText(QtWidgets.QApplication.translate("Form", "Max Traces:", None, -1)) - self.downsampleCheck.setText(QtWidgets.QApplication.translate("Form", "Downsample", None, -1)) - self.peakRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, -1)) - self.peakRadio.setText(QtWidgets.QApplication.translate("Form", "Peak", None, -1)) - self.maxTracesSpin.setToolTip(QtWidgets.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, -1)) - self.forgetTracesCheck.setToolTip(QtWidgets.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, -1)) - self.forgetTracesCheck.setText(QtWidgets.QApplication.translate("Form", "Forget hidden traces", None, -1)) - self.meanRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, -1)) - self.meanRadio.setText(QtWidgets.QApplication.translate("Form", "Mean", None, -1)) - self.subsampleRadio.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, -1)) - self.subsampleRadio.setText(QtWidgets.QApplication.translate("Form", "Subsample", None, -1)) - self.autoDownsampleCheck.setToolTip(QtWidgets.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, -1)) - self.autoDownsampleCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) - self.downsampleSpin.setToolTip(QtWidgets.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, -1)) - self.downsampleSpin.setSuffix(QtWidgets.QApplication.translate("Form", "x", None, -1)) - self.logYCheck.setText(QtWidgets.QApplication.translate("Form", "Log Y", None, -1)) - self.logXCheck.setText(QtWidgets.QApplication.translate("Form", "Log X", None, -1)) - self.fftCheck.setText(QtWidgets.QApplication.translate("Form", "Power Spectrum (FFT)", None, -1)) - self.derivativeCheck.setText(QtWidgets.QApplication.translate("Form", "dy/dx", None, -1)) - self.phasemapCheck.setText(QtWidgets.QApplication.translate("Form", "Y vs. Y\'", None, -1)) - self.pointsGroup.setTitle(QtWidgets.QApplication.translate("Form", "Points", None, -1)) - self.autoPointsCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) - self.xGridCheck.setText(QtWidgets.QApplication.translate("Form", "Show X Grid", None, -1)) - self.yGridCheck.setText(QtWidgets.QApplication.translate("Form", "Show Y Grid", None, -1)) - self.label.setText(QtWidgets.QApplication.translate("Form", "Opacity", None, -1)) - self.alphaGroup.setTitle(QtWidgets.QApplication.translate("Form", "Alpha", None, -1)) - self.autoAlphaCheck.setText(QtWidgets.QApplication.translate("Form", "Auto", None, -1)) - diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside6.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside6.py deleted file mode 100644 index 84abbdb..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside6.py +++ /dev/null @@ -1,254 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'plotConfigTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(481, 840) - self.averageGroup = QGroupBox(Form) - self.averageGroup.setObjectName(u"averageGroup") - self.averageGroup.setGeometry(QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.gridLayout_5 = QGridLayout(self.averageGroup) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.gridLayout_5.setObjectName(u"gridLayout_5") - self.avgParamList = QListWidget(self.averageGroup) - self.avgParamList.setObjectName(u"avgParamList") - - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - - self.decimateGroup = QFrame(Form) - self.decimateGroup.setObjectName(u"decimateGroup") - self.decimateGroup.setGeometry(QRect(10, 140, 191, 171)) - self.gridLayout_4 = QGridLayout(self.decimateGroup) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.clipToViewCheck = QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName(u"clipToViewCheck") - - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - - self.maxTracesCheck = QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName(u"maxTracesCheck") - - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - - self.downsampleCheck = QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName(u"downsampleCheck") - - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - - self.peakRadio = QRadioButton(self.decimateGroup) - self.peakRadio.setObjectName(u"peakRadio") - self.peakRadio.setChecked(True) - - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - - self.maxTracesSpin = QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName(u"maxTracesSpin") - - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - - self.forgetTracesCheck = QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName(u"forgetTracesCheck") - - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - - self.meanRadio = QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName(u"meanRadio") - - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - - self.subsampleRadio = QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName(u"subsampleRadio") - - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - - self.autoDownsampleCheck = QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setObjectName(u"autoDownsampleCheck") - self.autoDownsampleCheck.setChecked(True) - - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - - self.horizontalSpacer = QSpacerItem(30, 20, QSizePolicy.Maximum, QSizePolicy.Minimum) - - self.gridLayout_4.addItem(self.horizontalSpacer, 2, 0, 1, 1) - - self.downsampleSpin = QSpinBox(self.decimateGroup) - self.downsampleSpin.setObjectName(u"downsampleSpin") - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setValue(1) - - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - - self.transformGroup = QFrame(Form) - self.transformGroup.setObjectName(u"transformGroup") - self.transformGroup.setGeometry(QRect(10, 10, 171, 101)) - self.gridLayout = QGridLayout(self.transformGroup) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName(u"gridLayout") - self.logYCheck = QCheckBox(self.transformGroup) - self.logYCheck.setObjectName(u"logYCheck") - - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - - self.logXCheck = QCheckBox(self.transformGroup) - self.logXCheck.setObjectName(u"logXCheck") - - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - - self.fftCheck = QCheckBox(self.transformGroup) - self.fftCheck.setObjectName(u"fftCheck") - - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - - self.derivativeCheck = QCheckBox(self.transformGroup) - self.derivativeCheck.setObjectName(u"derivativeCheck") - - self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) - - self.phasemapCheck = QCheckBox(self.transformGroup) - self.phasemapCheck.setObjectName(u"phasemapCheck") - - self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) - - self.pointsGroup = QGroupBox(Form) - self.pointsGroup.setObjectName(u"pointsGroup") - self.pointsGroup.setGeometry(QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.verticalLayout_5 = QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName(u"verticalLayout_5") - self.autoPointsCheck = QCheckBox(self.pointsGroup) - self.autoPointsCheck.setObjectName(u"autoPointsCheck") - self.autoPointsCheck.setChecked(True) - - self.verticalLayout_5.addWidget(self.autoPointsCheck) - - self.gridGroup = QFrame(Form) - self.gridGroup.setObjectName(u"gridGroup") - self.gridGroup.setGeometry(QRect(10, 460, 221, 81)) - self.gridLayout_2 = QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.xGridCheck = QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName(u"xGridCheck") - - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - - self.yGridCheck = QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName(u"yGridCheck") - - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - - self.gridAlphaSlider = QSlider(self.gridGroup) - self.gridAlphaSlider.setObjectName(u"gridAlphaSlider") - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setValue(128) - self.gridAlphaSlider.setOrientation(Qt.Horizontal) - - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - - self.label = QLabel(self.gridGroup) - self.label.setObjectName(u"label") - - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - - self.alphaGroup = QGroupBox(Form) - self.alphaGroup.setObjectName(u"alphaGroup") - self.alphaGroup.setGeometry(QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.horizontalLayout = QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.autoAlphaCheck = QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setObjectName(u"autoAlphaCheck") - self.autoAlphaCheck.setChecked(False) - - self.horizontalLayout.addWidget(self.autoAlphaCheck) - - self.alphaSlider = QSlider(self.alphaGroup) - self.alphaSlider.setObjectName(u"alphaSlider") - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setValue(1000) - self.alphaSlider.setOrientation(Qt.Horizontal) - - self.horizontalLayout.addWidget(self.alphaSlider) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) -#if QT_CONFIG(tooltip) - self.averageGroup.setToolTip(QCoreApplication.translate("Form", u"Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None)) -#endif // QT_CONFIG(tooltip) - self.averageGroup.setTitle(QCoreApplication.translate("Form", u"Average", None)) -#if QT_CONFIG(tooltip) - self.clipToViewCheck.setToolTip(QCoreApplication.translate("Form", u"Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None)) -#endif // QT_CONFIG(tooltip) - self.clipToViewCheck.setText(QCoreApplication.translate("Form", u"Clip to View", None)) -#if QT_CONFIG(tooltip) - self.maxTracesCheck.setToolTip(QCoreApplication.translate("Form", u"If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None)) -#endif // QT_CONFIG(tooltip) - self.maxTracesCheck.setText(QCoreApplication.translate("Form", u"Max Traces:", None)) - self.downsampleCheck.setText(QCoreApplication.translate("Form", u"Downsample", None)) -#if QT_CONFIG(tooltip) - self.peakRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None)) -#endif // QT_CONFIG(tooltip) - self.peakRadio.setText(QCoreApplication.translate("Form", u"Peak", None)) -#if QT_CONFIG(tooltip) - self.maxTracesSpin.setToolTip(QCoreApplication.translate("Form", u"If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.forgetTracesCheck.setToolTip(QCoreApplication.translate("Form", u"If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None)) -#endif // QT_CONFIG(tooltip) - self.forgetTracesCheck.setText(QCoreApplication.translate("Form", u"Forget hidden traces", None)) -#if QT_CONFIG(tooltip) - self.meanRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by taking the mean of N samples.", None)) -#endif // QT_CONFIG(tooltip) - self.meanRadio.setText(QCoreApplication.translate("Form", u"Mean", None)) -#if QT_CONFIG(tooltip) - self.subsampleRadio.setToolTip(QCoreApplication.translate("Form", u"Downsample by taking the first of N samples. This method is fastest and least accurate.", None)) -#endif // QT_CONFIG(tooltip) - self.subsampleRadio.setText(QCoreApplication.translate("Form", u"Subsample", None)) -#if QT_CONFIG(tooltip) - self.autoDownsampleCheck.setToolTip(QCoreApplication.translate("Form", u"Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None)) -#endif // QT_CONFIG(tooltip) - self.autoDownsampleCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) -#if QT_CONFIG(tooltip) - self.downsampleSpin.setToolTip(QCoreApplication.translate("Form", u"Downsample data before plotting. (plot every Nth sample)", None)) -#endif // QT_CONFIG(tooltip) - self.downsampleSpin.setSuffix(QCoreApplication.translate("Form", u"x", None)) - self.logYCheck.setText(QCoreApplication.translate("Form", u"Log Y", None)) - self.logXCheck.setText(QCoreApplication.translate("Form", u"Log X", None)) - self.fftCheck.setText(QCoreApplication.translate("Form", u"Power Spectrum (FFT)", None)) - self.derivativeCheck.setText(QCoreApplication.translate("Form", u"dy/dx", None)) - self.phasemapCheck.setText(QCoreApplication.translate("Form", u"Y vs. Y'", None)) - self.pointsGroup.setTitle(QCoreApplication.translate("Form", u"Points", None)) - self.autoPointsCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) - self.xGridCheck.setText(QCoreApplication.translate("Form", u"Show X Grid", None)) - self.yGridCheck.setText(QCoreApplication.translate("Form", u"Show Y Grid", None)) - self.label.setText(QCoreApplication.translate("Form", u"Opacity", None)) - self.alphaGroup.setTitle(QCoreApplication.translate("Form", u"Alpha", None)) - self.autoAlphaCheck.setText(QCoreApplication.translate("Form", u"Auto", None)) - # retranslateUi - diff --git a/pyqtgraph/graphicsItems/PlotItem/tests/__init__.py b/pyqtgraph/graphicsItems/PlotItem/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 0eb1d69..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/test_PlotItem.cpython-36.pyc b/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/test_PlotItem.cpython-36.pyc deleted file mode 100644 index dd22b42..0000000 Binary files a/pyqtgraph/graphicsItems/PlotItem/tests/__pycache__/test_PlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/PlotItem/tests/test_PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/tests/test_PlotItem.py deleted file mode 100644 index 83d2531..0000000 --- a/pyqtgraph/graphicsItems/PlotItem/tests/test_PlotItem.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import pyqtgraph as pg - -app = pg.mkQApp() - - -@pytest.mark.parametrize('orientation', ['left', 'right', 'top', 'bottom']) -def test_PlotItem_shared_axis_items(orientation): - """Adding an AxisItem to multiple plots raises RuntimeError""" - ax1 = pg.AxisItem(orientation) - ax2 = pg.AxisItem(orientation) - - layout = pg.GraphicsLayoutWidget() - - pi1 = layout.addPlot(axisItems={orientation: ax1}) - - pi2 = layout.addPlot() - # left or bottom replaces, right or top adds new - pi2.setAxisItems({orientation: ax2}) - - with pytest.raises(RuntimeError): - pi2.setAxisItems({orientation: ax1}) - - -def test_plotitem_menu_initialize(): - """Test the menu initialization of the plotitem""" - item = pg.PlotItem() - assert item.menuEnabled() is True - viewbox = item.vb - assert viewbox is not None - assert viewbox.menu is not None - assert viewbox.menuEnabled() is True - - item = pg.PlotItem(enableMenu=False) - assert item.menuEnabled() is False - viewbox = item.vb - assert viewbox is not None - assert viewbox.menu is None - assert viewbox.menuEnabled() is False - - viewbox = pg.ViewBox() - item = pg.PlotItem(viewBox=viewbox, enableMenu=False) - assert item.menuEnabled() is False - viewbox = item.vb - assert viewbox is not None - assert viewbox.menu is not None - assert viewbox.menuEnabled() is True - - viewbox = pg.ViewBox(enableMenu=False) - item = pg.PlotItem(viewBox=viewbox) - assert item.menuEnabled() is True - viewbox = item.vb - assert viewbox is not None - assert viewbox.menu is None - assert viewbox.menuEnabled() is False diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py deleted file mode 100644 index 99b2882..0000000 --- a/pyqtgraph/graphicsItems/ROI.py +++ /dev/null @@ -1,2393 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ROI.py - Interactive graphics items for GraphicsView (ROI widgets) -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -Implements a series of graphics items which display movable/scalable/rotatable shapes -for use as region-of-interest markers. ROI class automatically handles extraction -of array data from ImageItems. - -The ROI class is meant to serve as the base for more specific types; see several examples -of how to build an ROI at the bottom of the file. -""" - -from ..Qt import QtCore, QtGui -import numpy as np -#from numpy.linalg import norm -from ..Point import * -from ..SRTTransform import SRTTransform -from math import cos, sin -from .. import functions as fn -from .GraphicsObject import GraphicsObject -from .UIGraphicsItem import UIGraphicsItem -from .. import getConfigOption -import warnings - -translate = QtCore.QCoreApplication.translate - -__all__ = [ - 'ROI', - 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', - 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', - 'CrosshairROI','TriangleROI' -] - - -def rectStr(r): - return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) - -class ROI(GraphicsObject): - """ - Generic region-of-interest widget. - - Can be used for implementing many types of selection box with - rotate/translate/scale handles. - ROIs can be customized to have a variety of shapes (by subclassing or using - any of the built-in subclasses) and any combination of draggable handles - that allow the user to manipulate the ROI. - - Default mouse interaction: - - * Left drag moves the ROI - * Left drag + Ctrl moves the ROI with position snapping - * Left drag + Alt rotates the ROI - * Left drag + Alt + Ctrl rotates the ROI with angle snapping - * Left drag + Shift scales the ROI - * Left drag + Shift + Ctrl scales the ROI with size snapping - - In addition to the above interaction modes, it is possible to attach any - number of handles to the ROI that can be dragged to change the ROI in - various ways (see the ROI.add____Handle methods). - - - ================ =========================================================== - **Arguments** - pos (length-2 sequence) Indicates the position of the ROI's - origin. For most ROIs, this is the lower-left corner of - its bounding rectangle. - size (length-2 sequence) Indicates the width and height of the - ROI. - angle (float) The rotation of the ROI in degrees. Default is 0. - invertible (bool) If True, the user may resize the ROI to have - negative width or height (assuming the ROI has scale - handles). Default is False. - maxBounds (QRect, QRectF, or None) Specifies boundaries that the ROI - cannot be dragged outside of by the user. Default is None. - snapSize (float) The spacing of snap positions used when *scaleSnap* - or *translateSnap* are enabled. Default is 1.0. - scaleSnap (bool) If True, the width and height of the ROI are forced - to be integer multiples of *snapSize* when being resized - by the user. Default is False. - translateSnap (bool) If True, the x and y positions of the ROI are forced - to be integer multiples of *snapSize* when being resized - by the user. Default is False. - rotateSnap (bool) If True, the ROI angle is forced to a multiple of - the ROI's snap angle (default is 15 degrees) when rotated - by the user. Default is False. - parent (QGraphicsItem) The graphics item parent of this ROI. It - is generally not necessary to specify the parent. - pen (QPen or argument to pg.mkPen) The pen to use when drawing - the shape of the ROI. - hoverPen (QPen or argument to mkPen) The pen to use while the - mouse is hovering over the ROI shape. - handlePen (QPen or argument to mkPen) The pen to use when drawing - the ROI handles. - handleHoverPen (QPen or argument to mkPen) The pen to use while the mouse - is hovering over an ROI handle. - movable (bool) If True, the ROI can be moved by dragging anywhere - inside the ROI. Default is True. - rotatable (bool) If True, the ROI can be rotated by mouse drag + ALT - resizable (bool) If True, the ROI can be resized by mouse drag + - SHIFT - removable (bool) If True, the ROI will be given a context menu with - an option to remove the ROI. The ROI emits - sigRemoveRequested when this menu action is selected. - Default is False. - ================ =========================================================== - - - - ======================= ==================================================== - **Signals** - sigRegionChangeFinished Emitted when the user stops dragging the ROI (or - one of its handles) or if the ROI is changed - programatically. - sigRegionChangeStarted Emitted when the user starts dragging the ROI (or - one of its handles). - sigRegionChanged Emitted any time the position of the ROI changes, - including while it is being dragged by the user. - sigHoverEvent Emitted when the mouse hovers over the ROI. - sigClicked Emitted when the user clicks on the ROI. - Note that clicking is disabled by default to prevent - stealing clicks from objects behind the ROI. To - enable clicking, call - roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton). - See QtGui.QGraphicsItem documentation for more - details. - sigRemoveRequested Emitted when the user selects 'remove' from the - ROI's context menu (if available). - ======================= ==================================================== - """ - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - sigHoverEvent = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - sigRemoveRequested = QtCore.Signal(object) - - def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, - maxBounds=None, snapSize=1.0, scaleSnap=False, - translateSnap=False, rotateSnap=False, parent=None, pen=None, - hoverPen=None, handlePen=None, handleHoverPen=None, - movable=True, rotatable=True, resizable=True, removable=False): - GraphicsObject.__init__(self, parent) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - pos = Point(pos) - size = Point(size) - self.aspectLocked = False - self.translatable = movable - self.rotatable = rotatable - self.resizable = resizable - self.removable = removable - self.menu = None - - self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. - self.mouseHovering = False - - if pen is None: - pen = (255, 255, 255) - self.setPen(pen) - if hoverPen is None: - hoverPen = (255, 255, 0) - self.hoverPen = fn.mkPen(hoverPen) - if handlePen is None: - handlePen = (150, 255, 255) - self.handlePen = fn.mkPen(handlePen) - if handleHoverPen is None: - handleHoverPen = (255, 255, 0) - self.handleHoverPen = handleHoverPen - - self.handles = [] - self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration - self.lastState = None - self.setPos(pos) - self.setAngle(angle) - self.setSize(size) - self.setZValue(10) - self.isMoving = False - - self.handleSize = 5 - self.invertible = invertible - self.maxBounds = maxBounds - - self.snapSize = snapSize - self.translateSnap = translateSnap - self.rotateSnap = rotateSnap - self.rotateSnapAngle = 15.0 - self.scaleSnap = scaleSnap - self.scaleSnapSize = snapSize - - # Implement mouse handling in a separate class to allow easier customization - self.mouseDragHandler = MouseDragHandler(self) - - def getState(self): - return self.stateCopy() - - def stateCopy(self): - sc = {} - sc['pos'] = Point(self.state['pos']) - sc['size'] = Point(self.state['size']) - sc['angle'] = self.state['angle'] - return sc - - def saveState(self): - """Return the state of the widget in a format suitable for storing to - disk. (Points are converted to tuple) - - Combined with setState(), this allows ROIs to be easily saved and - restored.""" - state = {} - state['pos'] = tuple(self.state['pos']) - state['size'] = tuple(self.state['size']) - state['angle'] = self.state['angle'] - return state - - def setState(self, state, update=True): - """ - Set the state of the ROI from a structure generated by saveState() or - getState(). - """ - self.setPos(state['pos'], update=False) - self.setSize(state['size'], update=False) - self.setAngle(state['angle'], update=update) - - def setZValue(self, z): - QtGui.QGraphicsItem.setZValue(self, z) - for h in self.handles: - h['item'].setZValue(z+1) - - def parentBounds(self): - """ - Return the bounding rectangle of this ROI in the coordinate system - of its parent. - """ - return self.mapToParent(self.boundingRect()).boundingRect() - - def setPen(self, *args, **kwargs): - """ - Set the pen to use when drawing the ROI shape. - For arguments, see :func:`mkPen `. - """ - self.pen = fn.mkPen(*args, **kwargs) - self.currentPen = self.pen - self.update() - - def size(self): - """Return the size (w,h) of the ROI.""" - return self.getState()['size'] - - def pos(self): - """Return the position (x,y) of the ROI's origin. - For most ROIs, this will be the lower-left corner.""" - return self.getState()['pos'] - - def angle(self): - """Return the angle of the ROI in degrees.""" - return self.getState()['angle'] - - def setPos(self, pos, y=None, update=True, finish=True): - """Set the position of the ROI (in the parent's coordinate system). - - Accepts either separate (x, y) arguments or a single :class:`Point` or - ``QPointF`` argument. - - By default, this method causes both ``sigRegionChanged`` and - ``sigRegionChangeFinished`` to be emitted. If *finish* is False, then - ``sigRegionChangeFinished`` will not be emitted. You can then use - stateChangeFinished() to cause the signal to be emitted after a series - of state changes. - - If *update* is False, the state change will be remembered but not processed and no signals - will be emitted. You can then use stateChanged() to complete the state change. This allows - multiple change functions to be called sequentially while minimizing processing overhead - and repeated signals. Setting ``update=False`` also forces ``finish=False``. - """ - if update not in (True, False): - raise TypeError("update argument must be bool") - - if y is None: - pos = Point(pos) - else: - # avoid ambiguity where update is provided as a positional argument - if isinstance(y, bool): - raise TypeError("Positional arguments to setPos() must be numerical.") - pos = Point(pos, y) - - self.state['pos'] = pos - QtGui.QGraphicsItem.setPos(self, pos) - if update: - self.stateChanged(finish=finish) - - def setSize(self, size, center=None, centerLocal=None, snap=False, update=True, finish=True): - """ - Set the ROI's size. - - =============== ========================================================================== - **Arguments** - size (Point | QPointF | sequence) The final size of the ROI - center (None | Point) Optional center point around which the ROI is scaled, - expressed as [0-1, 0-1] over the size of the ROI. - centerLocal (None | Point) Same as *center*, but the position is expressed in the - local coordinate system of the ROI - snap (bool) If True, the final size is snapped to the nearest increment (see - ROI.scaleSnapSize) - update (bool) See setPos() - finish (bool) See setPos() - =============== ========================================================================== - """ - if update not in (True, False): - raise TypeError("update argument must be bool") - size = Point(size) - if snap: - size[0] = round(size[0] / self.scaleSnapSize) * self.scaleSnapSize - size[1] = round(size[1] / self.scaleSnapSize) * self.scaleSnapSize - - if centerLocal is not None: - oldSize = Point(self.state['size']) - oldSize[0] = 1 if oldSize[0] == 0 else oldSize[0] - oldSize[1] = 1 if oldSize[1] == 0 else oldSize[1] - center = Point(centerLocal) / oldSize - - if center is not None: - center = Point(center) - c = self.mapToParent(Point(center) * self.state['size']) - c1 = self.mapToParent(Point(center) * size) - newPos = self.state['pos'] + c - c1 - self.setPos(newPos, update=False, finish=False) - - self.prepareGeometryChange() - self.state['size'] = size - if update: - self.stateChanged(finish=finish) - - def setAngle(self, angle, center=None, centerLocal=None, snap=False, update=True, finish=True): - """ - Set the ROI's rotation angle. - - =============== ========================================================================== - **Arguments** - angle (float) The final ROI angle in degrees - center (None | Point) Optional center point around which the ROI is rotated, - expressed as [0-1, 0-1] over the size of the ROI. - centerLocal (None | Point) Same as *center*, but the position is expressed in the - local coordinate system of the ROI - snap (bool) If True, the final ROI angle is snapped to the nearest increment - (default is 15 degrees; see ROI.rotateSnapAngle) - update (bool) See setPos() - finish (bool) See setPos() - =============== ========================================================================== - """ - if update not in (True, False): - raise TypeError("update argument must be bool") - - if snap is True: - angle = round(angle / self.rotateSnapAngle) * self.rotateSnapAngle - - self.state['angle'] = angle - tr = QtGui.QTransform() # note: only rotation is contained in the transform - tr.rotate(angle) - if center is not None: - centerLocal = Point(center) * self.state['size'] - if centerLocal is not None: - centerLocal = Point(centerLocal) - # rotate to new angle, keeping a specific point anchored as the center of rotation - cc = self.mapToParent(centerLocal) - (tr.map(centerLocal) + self.state['pos']) - self.translate(cc, update=False) - - self.setTransform(tr) - if update: - self.stateChanged(finish=finish) - - def scale(self, s, center=None, centerLocal=None, snap=False, update=True, finish=True): - """ - Resize the ROI by scaling relative to *center*. - See setPos() for an explanation of the *update* and *finish* arguments. - """ - newSize = self.state['size'] * s - self.setSize(newSize, center=center, centerLocal=centerLocal, snap=snap, update=update, finish=finish) - - def translate(self, *args, **kargs): - """ - Move the ROI to a new position. - Accepts either (x, y, snap) or ([x,y], snap) as arguments - If the ROI is bounded and the move would exceed boundaries, then the ROI - is moved to the nearest acceptable position instead. - - *snap* can be: - - =============== ========================================================================== - None (default) use self.translateSnap and self.snapSize to determine whether/how to snap - False do not snap - Point(w,h) snap to rectangular grid with spacing (w,h) - True snap using self.snapSize (and ignoring self.translateSnap) - =============== ========================================================================== - - Also accepts *update* and *finish* arguments (see setPos() for a description of these). - """ - - if len(args) == 1: - pt = args[0] - else: - pt = args - - newState = self.stateCopy() - newState['pos'] = newState['pos'] + pt - - snap = kargs.get('snap', None) - if snap is None: - snap = self.translateSnap - if snap is not False: - newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) - - if self.maxBounds is not None: - r = self.stateRect(newState) - d = Point(0,0) - if self.maxBounds.left() > r.left(): - d[0] = self.maxBounds.left() - r.left() - elif self.maxBounds.right() < r.right(): - d[0] = self.maxBounds.right() - r.right() - if self.maxBounds.top() > r.top(): - d[1] = self.maxBounds.top() - r.top() - elif self.maxBounds.bottom() < r.bottom(): - d[1] = self.maxBounds.bottom() - r.bottom() - newState['pos'] += d - - update = kargs.get('update', True) - finish = kargs.get('finish', True) - self.setPos(newState['pos'], update=update, finish=finish) - - def rotate(self, angle, center=None, snap=False, update=True, finish=True): - """ - Rotate the ROI by *angle* degrees. - - =============== ========================================================================== - **Arguments** - angle (float) The angle in degrees to rotate - center (None | Point) Optional center point around which the ROI is rotated, in - the local coordinate system of the ROI - snap (bool) If True, the final ROI angle is snapped to the nearest increment - (default is 15 degrees; see ROI.rotateSnapAngle) - update (bool) See setPos() - finish (bool) See setPos() - =============== ========================================================================== - """ - self.setAngle(self.angle()+angle, center=center, snap=snap, update=update, finish=finish) - - def handleMoveStarted(self): - self.preMoveState = self.getState() - self.sigRegionChangeStarted.emit(self) - - def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None): - """ - Add a new translation handle to the ROI. Dragging the handle will move - the entire ROI without changing its angle or shape. - - Note that, by default, ROIs may be moved by dragging anywhere inside the - ROI. However, for larger ROIs it may be desirable to disable this and - instead provide one or more translation handles. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - pos = Point(pos) - return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index) - - def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None): - """ - Add a new free handle to the ROI. Dragging free handles has no effect - on the position or shape of the ROI. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - if pos is not None: - pos = Point(pos) - return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index) - - def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None): - """ - Add a new scale handle to the ROI. Dragging a scale handle allows the - user to change the height and/or width of the ROI. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - center (length-2 sequence) The center point around which - scaling takes place. If the center point has the - same x or y value as the handle position, then - scaling will be disabled for that axis. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - pos = Point(pos) - center = Point(center) - info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} - if pos.x() == center.x(): - info['xoff'] = True - if pos.y() == center.y(): - info['yoff'] = True - return self.addHandle(info, index=index) - - def addRotateHandle(self, pos, center, item=None, name=None, index=None): - """ - Add a new rotation handle to the ROI. Dragging a rotation handle allows - the user to change the angle of the ROI. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - center (length-2 sequence) The center point around which - rotation takes place. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None): - """ - Add a new scale+rotation handle to the ROI. When dragging a handle of - this type, the user can simultaneously rotate the ROI around an - arbitrary center point as well as scale the ROI by dragging the handle - toward or away from the center point. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - center (length-2 sequence) The center point around which - scaling and rotation take place. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - pos = Point(pos) - center = Point(center) - if pos[0] == center[0] and pos[1] == center[1]: - raise Exception("Scale/rotate handles cannot be at their center point.") - return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None): - """ - Add a new rotation+free handle to the ROI. When dragging a handle of - this type, the user can rotate the ROI around an - arbitrary center point, while moving toward or away from the center - point has no effect on the shape of the ROI. - - =================== ==================================================== - **Arguments** - pos (length-2 sequence) The position of the handle - relative to the shape of the ROI. A value of (0,0) - indicates the origin, whereas (1, 1) indicates the - upper-right corner, regardless of the ROI's size. - center (length-2 sequence) The center point around which - rotation takes place. - item The Handle instance to add. If None, a new handle - will be created. - name The name of this handle (optional). Handles are - identified by name when calling - getLocalHandlePositions and getSceneHandlePositions. - =================== ==================================================== - """ - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addHandle(self, info, index=None): - ## If a Handle was not supplied, create it now - if 'item' not in info or info['item'] is None: - h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, - hoverPen=self.handleHoverPen, parent=self) - info['item'] = h - else: - h = info['item'] - if info['pos'] is None: - info['pos'] = h.pos() - h.setPos(info['pos'] * self.state['size']) - - ## connect the handle to this ROI - #iid = len(self.handles) - h.connectROI(self) - if index is None: - self.handles.append(info) - else: - self.handles.insert(index, info) - - h.setZValue(self.zValue()+1) - self.stateChanged() - return h - - def indexOfHandle(self, handle): - """ - Return the index of *handle* in the list of this ROI's handles. - """ - if isinstance(handle, Handle): - index = [i for i, info in enumerate(self.handles) if info['item'] is handle] - if len(index) == 0: - raise Exception("Cannot return handle index; not attached to this ROI") - return index[0] - else: - return handle - - def removeHandle(self, handle): - """Remove a handle from this ROI. Argument may be either a Handle - instance or the integer index of the handle.""" - index = self.indexOfHandle(handle) - - handle = self.handles[index]['item'] - self.handles.pop(index) - handle.disconnectROI(self) - if len(handle.rois) == 0: - self.scene().removeItem(handle) - self.stateChanged() - - def replaceHandle(self, oldHandle, newHandle): - """Replace one handle in the ROI for another. This is useful when - connecting multiple ROIs together. - - *oldHandle* may be a Handle instance or the index of a handle to be - replaced.""" - index = self.indexOfHandle(oldHandle) - info = self.handles[index] - self.removeHandle(index) - info['item'] = newHandle - info['pos'] = newHandle.pos() - self.addHandle(info, index=index) - - def checkRemoveHandle(self, handle): - ## This is used when displaying a Handle's context menu to determine - ## whether removing is allowed. - ## Subclasses may wish to override this to disable the menu entry. - ## Note: by default, handles are not user-removable even if this method returns True. - return True - - def getLocalHandlePositions(self, index=None): - """Returns the position of handles in the ROI's coordinate system. - - The format returned is a list of (name, pos) tuples. - """ - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['pos'])) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['pos']) - - def getSceneHandlePositions(self, index=None): - """Returns the position of handles in the scene coordinate system. - - The format returned is a list of (name, pos) tuples. - """ - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['item'].scenePos())) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) - - def getHandles(self): - """ - Return a list of this ROI's Handles. - """ - return [h['item'] for h in self.handles] - - def mapSceneToParent(self, pt): - return self.mapToParent(self.mapFromScene(pt)) - - def setSelected(self, s): - QtGui.QGraphicsItem.setSelected(self, s) - #print "select", self, s - if s: - for h in self.handles: - h['item'].show() - else: - for h in self.handles: - h['item'].hide() - - def hoverEvent(self, ev): - hover = False - if not ev.isExit(): - if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton): - hover=True - - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MiddleButton]: - if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): - hover=True - if self.contextMenuEnabled(): - ev.acceptClicks(QtCore.Qt.RightButton) - - if hover: - self.setMouseHover(True) - ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. - ev.acceptClicks(QtCore.Qt.RightButton) - ev.acceptClicks(QtCore.Qt.MiddleButton) - self.sigHoverEvent.emit(self) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the ROI that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - self._updateHoverColor() - - def _updateHoverColor(self): - pen = self._makePen() - if self.currentPen != pen: - self.currentPen = pen - self.update() - - def _makePen(self): - # Generate the pen color for this ROI based on its current state. - if self.mouseHovering: - return self.hoverPen - else: - return self.pen - - def contextMenuEnabled(self): - return self.removable - - def raiseContextMenu(self, ev): - if not self.contextMenuEnabled(): - return - menu = self.getMenu() - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def getMenu(self): - if self.menu is None: - self.menu = QtGui.QMenu() - self.menu.setTitle(translate("ROI", "ROI")) - remAct = QtGui.QAction(translate("ROI", "Remove ROI"), self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - # ROI menu may be requested when showing the handle context menu, so - # return the menu but disable it if the ROI isn't removable - self.menu.setEnabled(self.contextMenuEnabled()) - return self.menu - - def removeClicked(self): - ## Send remove event only after we have exited the menu event handler - QtCore.QTimer.singleShot(0, self._emitRemoveRequest) - - def _emitRemoveRequest(self): - self.sigRemoveRequested.emit(self) - - def mouseDragEvent(self, ev): - self.mouseDragHandler.mouseDragEvent(ev) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton and self.isMoving: - ev.accept() - self.cancelMove() - if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled(): - self.raiseContextMenu(ev) - ev.accept() - elif ev.button() & self.acceptedMouseButtons(): - ev.accept() - self.sigClicked.emit(self, ev) - else: - ev.ignore() - - def _moveStarted(self): - self.isMoving = True - self.preMoveState = self.getState() - self.sigRegionChangeStarted.emit(self) - - def _moveFinished(self): - if self.isMoving: - self.stateChangeFinished() - self.isMoving = False - - def cancelMove(self): - self.isMoving = False - self.setState(self.preMoveState) - - def checkPointMove(self, handle, pos, modifiers): - """When handles move, they must ask the ROI if the move is acceptable. - By default, this always returns True. Subclasses may wish override. - """ - return True - - def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifiers(0), finish=True, coords='parent'): - ## called by Handles when they are moved. - ## pos is the new position of the handle in scene coords, as requested by the handle. - - newState = self.stateCopy() - index = self.indexOfHandle(handle) - h = self.handles[index] - p0 = self.mapToParent(h['pos'] * self.state['size']) - p1 = Point(pos) - - if coords == 'parent': - pass - elif coords == 'scene': - p1 = self.mapSceneToParent(p1) - else: - raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.") - - ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) - if 'center' in h: - c = h['center'] - cs = c * self.state['size'] - lp0 = self.mapFromParent(p0) - cs - lp1 = self.mapFromParent(p1) - cs - - if h['type'] == 't': - snap = True if (modifiers & QtCore.Qt.ControlModifier) else None - self.translate(p1-p0, snap=snap, update=False) - - elif h['type'] == 'f': - newPos = self.mapFromParent(p1) - h['item'].setPos(newPos) - h['pos'] = newPos - self.freeHandleMoved = True - - elif h['type'] == 's': - ## If a handle and its center have the same x or y value, we can't scale across that axis. - if h['center'][0] == h['pos'][0]: - lp1[0] = 0 - if h['center'][1] == h['pos'][1]: - lp1[1] = 0 - - ## snap - if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): - lp1[0] = round(lp1[0] / self.scaleSnapSize) * self.scaleSnapSize - lp1[1] = round(lp1[1] / self.scaleSnapSize) * self.scaleSnapSize - - ## preserve aspect ratio (this can override snapping) - if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): - #arv = Point(self.preMoveState['size']) - - lp1 = lp1.proj(lp0) - - ## determine scale factors and new size of ROI - hs = h['pos'] - c - if hs[0] == 0: - hs[0] = 1 - if hs[1] == 0: - hs[1] = 1 - newSize = lp1 / hs - - ## Perform some corrections and limit checks - if newSize[0] == 0: - newSize[0] = newState['size'][0] - if newSize[1] == 0: - newSize[1] = newState['size'][1] - if not self.invertible: - if newSize[0] < 0: - newSize[0] = newState['size'][0] - if newSize[1] < 0: - newSize[1] = newState['size'][1] - if self.aspectLocked: - newSize[0] = newSize[1] - - ## Move ROI so the center point occupies the same scene location after the scale - s0 = c * self.state['size'] - s1 = c * newSize - cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) - - ## update state, do more boundary checks - newState['size'] = newSize - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - - self.setPos(newState['pos'], update=False) - self.setSize(newState['size'], update=False) - - elif h['type'] in ['r', 'rf']: - if h['type'] == 'rf': - self.freeHandleMoved = True - - if not self.rotatable: - return - ## If the handle is directly over its center point, we can't compute an angle. - try: - if lp1.length() == 0 or lp0.length() == 0: - return - except OverflowError: - return - - ## determine new rotation angle, constrained if necessary - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: ## this should never happen.. - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - ang = round(ang / self.rotateSnapAngle) * self.rotateSnapAngle - - ## create rotation transform - tr = QtGui.QTransform() - tr.rotate(ang) - - ## move ROI so that center point remains stationary after rotate - cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - - ## check boundaries, update - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - self.setPos(newState['pos'], update=False) - self.setAngle(ang, update=False) - - ## If this is a free-rotate handle, its distance from the center may change. - - if h['type'] == 'rf': - h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle - h['pos'] = self.mapFromParent(p1) - - elif h['type'] == 'sr': - try: - if lp1.length() == 0 or lp0.length() == 0: - return - except OverflowError: - return - - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - ang = round(ang / self.rotateSnapAngle) * self.rotateSnapAngle - - if self.aspectLocked or h['center'][0] != h['pos'][0]: - newState['size'][0] = self.state['size'][0] * lp1.length() / lp0.length() - if self.scaleSnap: # use CTRL only for angular snap here. - newState['size'][0] = round(newState['size'][0] / self.snapSize) * self.snapSize - - if self.aspectLocked or h['center'][1] != h['pos'][1]: - newState['size'][1] = self.state['size'][1] * lp1.length() / lp0.length() - if self.scaleSnap: # use CTRL only for angular snap here. - newState['size'][1] = round(newState['size'][1] / self.snapSize) * self.snapSize - - if newState['size'][0] == 0: - newState['size'][0] = 1 - if newState['size'][1] == 0: - newState['size'][1] = 1 - - c1 = c * newState['size'] - tr = QtGui.QTransform() - tr.rotate(ang) - - cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - - self.setState(newState, update=False) - - self.stateChanged(finish=finish) - - def stateChanged(self, finish=True): - """Process changes to the state of the ROI. - If there are any changes, then the positions of handles are updated accordingly - and sigRegionChanged is emitted. If finish is True, then - sigRegionChangeFinished will also be emitted.""" - - changed = False - if self.lastState is None: - changed = True - else: - state = self.getState() - for k in list(state.keys()): - if state[k] != self.lastState[k]: - changed = True - - self.prepareGeometryChange() - if changed: - ## Move all handles to match the current configuration of the ROI - for h in self.handles: - if h['item'] in self.childItems(): - p = h['pos'] - h['item'].setPos(h['pos'] * self.state['size']) - - self.update() - self.sigRegionChanged.emit(self) - elif self.freeHandleMoved: - self.sigRegionChanged.emit(self) - - self.freeHandleMoved = False - self.lastState = self.getState() - - if finish: - self.stateChangeFinished() - self.informViewBoundsChanged() - - def stateChangeFinished(self): - self.sigRegionChangeFinished.emit(self) - - def stateRect(self, state): - r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) - tr = QtGui.QTransform() - tr.rotate(-state['angle']) - r = tr.mapRect(r) - return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) - - def getSnapPosition(self, pos, snap=None): - ## Given that pos has been requested, return the nearest snap-to position - ## optionally, snap may be passed in to specify a rectangular snap grid. - ## override this function for more interesting snap functionality.. - - if snap is None or snap is True: - if self.snapSize is None: - return pos - snap = Point(self.snapSize, self.snapSize) - - return Point( - round(pos[0] / snap[0]) * snap[0], - round(pos[1] / snap[1]) * snap[1] - ) - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() - - def paint(self, p, opt, widget): - # Note: don't use self.boundingRect here, because subclasses may need to redefine it. - r = QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() - - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - p.translate(r.left(), r.top()) - p.scale(r.width(), r.height()) - p.drawRect(0, 0, 1, 1) - - def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): - """Return a tuple of slice objects that can be used to slice the region - from *data* that is covered by the bounding rectangle of this ROI. - Also returns the transform that maps the ROI into data coordinates. - - If returnSlice is set to False, the function returns a pair of tuples with the values that would have - been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop)) - - If the slice cannot be computed (usually because the scene/transforms are not properly - constructed yet), then the method returns None. - """ - ## Determine shape of array along ROI axes - dShape = (data.shape[axes[0]], data.shape[axes[1]]) - - ## Determine transform that maps ROI bounding box to image coordinates - try: - tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) - except np.linalg.linalg.LinAlgError: - return None - - ## Modify transform to scale from image coords to data coords - axisOrder = img.axisOrder - if axisOrder == 'row-major': - tr.scale(float(dShape[1]) / img.width(), float(dShape[0]) / img.height()) - else: - tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) - - ## Transform ROI bounds into data bounds - dataBounds = tr.mapRect(self.boundingRect()) - - ## Intersect transformed ROI bounds with data bounds - if axisOrder == 'row-major': - intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[1], dShape[0])) - else: - intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[0], dShape[1])) - - ## Determine index values to use when referencing the array. - bounds = ( - (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), - (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) - ) - if axisOrder == 'row-major': - bounds = bounds[::-1] - - if returnSlice: - ## Create slice objects - sl = [slice(None)] * data.ndim - sl[axes[0]] = slice(*bounds[0]) - sl[axes[1]] = slice(*bounds[1]) - return tuple(sl), tr - else: - return bounds, tr - - def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): - r"""Use the position and orientation of this ROI relative to an imageItem - to pull a slice from an array. - - =================== ==================================================== - **Arguments** - data The array to slice from. Note that this array does - *not* have to be the same data that is represented - in *img*. - img (ImageItem or other suitable QGraphicsItem) - Used to determine the relationship between the - ROI and the boundaries of *data*. - axes (length-2 tuple) Specifies the axes in *data* that - correspond to the (x, y) axes of *img*. If the - image's axis order is set to - 'row-major', then the axes are instead specified in - (y, x) order. - returnMappedCoords (bool) If True, the array slice is returned along - with a corresponding array of coordinates that were - used to extract data from the original array. - \**kwds All keyword arguments are passed to - :func:`affineSlice `. - =================== ==================================================== - - This method uses :func:`affineSlice ` to generate - the slice from *data* and uses :func:`getAffineSliceParams ` - to determine the parameters to pass to :func:`affineSlice `. - - If *returnMappedCoords* is True, then the method returns a tuple (result, coords) - such that coords is the set of coordinates used to interpolate values from the original - data, mapped into the parent coordinate system of the image. This is useful, when slicing - data from images that have been transformed, for determining the location of each value - in the sliced data. - - All extra keyword arguments are passed to :func:`affineSlice `. - """ - # this is a hidden argument for internal use - fromBR = kwds.pop('fromBoundingRect', False) - - # Automaticaly compute missing parameters - _shape, _vectors, _origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR) - - # Replace them with user defined parameters if defined - shape = kwds.pop('shape', _shape) - vectors = kwds.pop('vectors', _vectors) - origin = kwds.pop('origin', _origin) - - if not returnMappedCoords: - rgn = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) - return rgn - else: - kwds['returnCoords'] = True - result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) - - ### map coordinates and return - mapped = fn.transformCoordinates(img.transform(), coords) - return result, mapped - - def _getArrayRegionForArbitraryShape(self, data, img, axes=(0,1), **kwds): - """ - Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion`, masked by - the shape of the ROI. Values outside the ROI shape are set to 0. - - See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the - arguments. - - Note: ``returnMappedCoords`` is not yet supported for this ROI type. - """ - br = self.boundingRect() - if br.width() > 1000: - raise Exception() - sliced = ROI.getArrayRegion(self, data, img, axes=axes, fromBoundingRect=True, **kwds) - - if img.axisOrder == "col-major": - mask = self.renderShapeMask(sliced.shape[axes[0]], sliced.shape[axes[1]]) - else: - mask = self.renderShapeMask(sliced.shape[axes[1]], sliced.shape[axes[0]]) - mask = mask.T - - # reshape mask to ensure it is applied to the correct data axes - shape = [1] * data.ndim - shape[axes[0]] = sliced.shape[axes[0]] - shape[axes[1]] = sliced.shape[axes[1]] - mask = mask.reshape(shape) - - return sliced * mask - - def getAffineSliceParams(self, data, img, axes=(0,1), fromBoundingRect=False): - """ - Returns the parameters needed to use :func:`affineSlice ` - (shape, vectors, origin) to extract a subset of *data* using this ROI - and *img* to specify the subset. - - If *fromBoundingRect* is True, then the ROI's bounding rectangle is used - rather than the shape of the ROI. - - See :func:`getArrayRegion ` for more information. - """ - if self.scene() is not img.scene(): - raise Exception("ROI and target item must be members of the same scene.") - - origin = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 0))) - - ## vx and vy point in the directions of the slice axes, but must be scaled properly - vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin - vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin - - lvx = np.sqrt(vx.x()**2 + vx.y()**2) - lvy = np.sqrt(vy.x()**2 + vy.y()**2) - ##img.width is number of pixels, not width of item. - ##need pxWidth and pxHeight instead of pxLen ? - sx = 1.0 / lvx - sy = 1.0 / lvy - - vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) - if fromBoundingRect is True: - shape = self.boundingRect().width(), self.boundingRect().height() - origin = img.mapToData(self.mapToItem(img, self.boundingRect().topLeft())) - origin = (origin.x(), origin.y()) - else: - shape = self.state['size'] - origin = (origin.x(), origin.y()) - - shape = [abs(shape[0]/sx), abs(shape[1]/sy)] - - if img.axisOrder == 'row-major': - # transpose output - vectors = vectors[::-1] - shape = shape[::-1] - - return shape, vectors, origin - - def renderShapeMask(self, width, height): - """Return an array of 0.0-1.0 into which the shape of the item has been drawn. - - This can be used to mask array selections. - """ - if width == 0 or height == 0: - return np.empty((width, height), dtype=float) - - im = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32) - im.fill(0x0) - p = QtGui.QPainter(im) - p.setPen(fn.mkPen(None)) - p.setBrush(fn.mkBrush('w')) - shape = self.shape() - bounds = shape.boundingRect() - p.scale(im.width() / bounds.width(), im.height() / bounds.height()) - p.translate(-bounds.topLeft()) - p.drawPath(shape) - p.end() - mask = fn.imageToArray(im, transpose=True)[:,:,0].astype(float) / 255. - return mask - - def getGlobalTransform(self, relativeTo=None): - """Return global transformation (rotation angle+translation) required to move - from relative state to current state. If relative state isn't specified, - then we use the state of the ROI when mouse is pressed.""" - if relativeTo == None: - relativeTo = self.preMoveState - st = self.getState() - - ## this is only allowed because we will be comparing the two - relativeTo['scale'] = relativeTo['size'] - st['scale'] = st['size'] - - t1 = SRTTransform(relativeTo) - t2 = SRTTransform(st) - return t2/t1 - - def applyGlobalTransform(self, tr): - st = self.getState() - - st['scale'] = st['size'] - st = SRTTransform(st) - st = (st * tr).saveState() - st['size'] = st['scale'] - self.setState(st) - - -class Handle(UIGraphicsItem): - """ - Handle represents a single user-interactable point attached to an ROI. They - are usually created by a call to one of the ROI.add___Handle() methods. - - Handles are represented as a square, diamond, or circle, and are drawn with - fixed pixel size regardless of the scaling of the view they are displayed in. - - Handles may be dragged to change the position, size, orientation, or other - properties of the ROI they are attached to. - """ - types = { ## defines number of sides, start angle for each handle type - 't': (4, np.pi/4), - 'f': (4, np.pi/4), - 's': (4, 0), - 'r': (12, 0), - 'sr': (12, 0), - 'rf': (12, 0), - } - - sigClicked = QtCore.Signal(object, object) # self, event - sigRemoveRequested = QtCore.Signal(object) # self - - def __init__(self, radius, typ=None, pen=(200, 200, 220), - hoverPen=(255, 255, 0), parent=None, deletable=False): - self.rois = [] - self.radius = radius - self.typ = typ - self.pen = fn.mkPen(pen) - self.hoverPen = fn.mkPen(hoverPen) - self.currentPen = self.pen - self.pen.setWidth(0) - self.pen.setCosmetic(True) - self.isMoving = False - self.sides, self.startAng = self.types[typ] - self.buildPath() - self._shape = None - self.menu = self.buildMenu() - - UIGraphicsItem.__init__(self, parent=parent) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - self.deletable = deletable - if deletable: - self.setAcceptedMouseButtons(QtCore.Qt.RightButton) - self.setZValue(11) - - def connectROI(self, roi): - ### roi is the "parent" roi, i is the index of the handle in roi.handles - self.rois.append(roi) - - def disconnectROI(self, roi): - self.rois.remove(roi) - - def setDeletable(self, b): - self.deletable = b - if b: - self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.RightButton) - else: - self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.RightButton) - - def removeClicked(self): - self.sigRemoveRequested.emit(self) - - def hoverEvent(self, ev): - hover = False - if not ev.isExit(): - if ev.acceptDrags(QtCore.Qt.LeftButton): - hover=True - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MiddleButton]: - if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): - hover=True - - if hover: - self.currentPen = self.hoverPen - else: - self.currentPen = self.pen - self.update() - - def mouseClickEvent(self, ev): - ## right-click cancels drag - if ev.button() == QtCore.Qt.RightButton and self.isMoving: - self.isMoving = False ## prevents any further motion - self.movePoint(self.startPos, finish=True) - ev.accept() - elif ev.button() & self.acceptedMouseButtons(): - ev.accept() - if ev.button() == QtCore.Qt.RightButton and self.deletable: - self.raiseContextMenu(ev) - self.sigClicked.emit(self, ev) - else: - ev.ignore() - - def buildMenu(self): - menu = QtGui.QMenu() - menu.setTitle(translate("ROI", "Handle")) - self.removeAction = menu.addAction(translate("ROI", "Remove handle"), self.removeClicked) - return menu - - def getMenu(self): - return self.menu - - def raiseContextMenu(self, ev): - menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) - - ## Make sure it is still ok to remove this handle - removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois]) - self.removeAction.setEnabled(removeAllowed) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - return - ev.accept() - - ## Inform ROIs that a drag is happening - ## note: the ROI is informed that the handle has moved using ROI.movePoint - ## this is for other (more nefarious) purposes. - #for r in self.roi: - #r[0].pointDragEvent(r[1], ev) - - if ev.isFinish(): - if self.isMoving: - for r in self.rois: - r.stateChangeFinished() - self.isMoving = False - self.currentPen = self.pen - self.update() - elif ev.isStart(): - for r in self.rois: - r.handleMoveStarted() - self.isMoving = True - self.startPos = self.scenePos() - self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() - self.currentPen = self.hoverPen - - if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. - pos = ev.scenePos() + self.cursorOffset - self.currentPen = self.hoverPen - self.movePoint(pos, ev.modifiers(), finish=False) - - def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifiers(0), finish=True): - for r in self.rois: - if not r.checkPointMove(self, pos, modifiers): - return - #print "point moved; inform %d ROIs" % len(self.roi) - # A handle can be used by multiple ROIs; tell each to update its handle position - for r in self.rois: - r.movePoint(self, pos, modifiers, finish=finish, coords='scene') - - def buildPath(self): - size = self.radius - self.path = QtGui.QPainterPath() - ang = self.startAng - dt = 2*np.pi / self.sides - for i in range(0, self.sides+1): - x = size * cos(ang) - y = size * sin(ang) - ang += dt - if i == 0: - self.path.moveTo(x, y) - else: - self.path.lineTo(x, y) - - def paint(self, p, opt, widget): - p.setRenderHints(p.Antialiasing, True) - p.setPen(self.currentPen) - - p.drawPath(self.shape()) - - def shape(self): - if self._shape is None: - s = self.generateShape() - if s is None: - return self.path - self._shape = s - self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape. - return self._shape - - def boundingRect(self): - s1 = self.shape() - return self.shape().boundingRect() - - def generateShape(self): - dt = self.deviceTransform() - - if dt is None: - self._shape = self.path - return None - - v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) - va = np.arctan2(v.y(), v.x()) - - dti = fn.invertQTransform(dt) - devPos = dt.map(QtCore.QPointF(0,0)) - tr = QtGui.QTransform() - tr.translate(devPos.x(), devPos.y()) - tr.rotate(va * 180. / 3.1415926) - - return dti.map(tr.map(self.path)) - - def viewTransformChanged(self): - GraphicsObject.viewTransformChanged(self) - self._shape = None ## invalidate shape, recompute later if requested. - self.update() - - -class MouseDragHandler(object): - """Implements default mouse drag behavior for ROI (not for ROI handles). - """ - def __init__(self, roi): - self.roi = roi - self.dragMode = None - self.startState = None - self.snapModifier = QtCore.Qt.ControlModifier - self.translateModifier = QtCore.Qt.NoModifier - self.rotateModifier = QtCore.Qt.AltModifier - self.scaleModifier = QtCore.Qt.ShiftModifier - self.rotateSpeed = 0.5 - self.scaleSpeed = 1.01 - - def mouseDragEvent(self, ev): - roi = self.roi - - if ev.isStart(): - if ev.button() == QtCore.Qt.LeftButton: - roi.setSelected(True) - mods = ev.modifiers() & ~self.snapModifier - if roi.translatable and mods == self.translateModifier: - self.dragMode = 'translate' - elif roi.rotatable and mods == self.rotateModifier: - self.dragMode = 'rotate' - elif roi.resizable and mods == self.scaleModifier: - self.dragMode = 'scale' - else: - self.dragMode = None - - if self.dragMode is not None: - roi._moveStarted() - self.startPos = roi.mapToParent(ev.buttonDownPos()) - self.startState = roi.saveState() - self.cursorOffset = roi.pos() - self.startPos - ev.accept() - else: - ev.ignore() - else: - self.dragMode = None - ev.ignore() - - - if ev.isFinish() and self.dragMode is not None: - roi._moveFinished() - return - - # roi.isMoving becomes False if the move was cancelled by right-click - if not roi.isMoving or self.dragMode is None: - return - - snap = True if (ev.modifiers() & self.snapModifier) else None - pos = roi.mapToParent(ev.pos()) - if self.dragMode == 'translate': - newPos = pos + self.cursorOffset - roi.translate(newPos - roi.pos(), snap=snap, finish=False) - elif self.dragMode == 'rotate': - diff = self.rotateSpeed * (ev.scenePos() - ev.buttonDownScenePos()).x() - angle = self.startState['angle'] - diff - roi.setAngle(angle, centerLocal=ev.buttonDownPos(), snap=snap, finish=False) - elif self.dragMode == 'scale': - diff = self.scaleSpeed ** -(ev.scenePos() - ev.buttonDownScenePos()).y() - roi.setSize(Point(self.startState['size']) * diff, centerLocal=ev.buttonDownPos(), snap=snap, finish=False) - - -class TestROI(ROI): - def __init__(self, pos, size, **args): - ROI.__init__(self, pos, size, **args) - self.addTranslateHandle([0.5, 0.5]) - self.addScaleHandle([1, 1], [0, 0]) - self.addScaleHandle([0, 0], [1, 1]) - self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - self.addRotateHandle([1, 0], [0, 0]) - self.addRotateHandle([0, 1], [1, 1]) - - -class RectROI(ROI): - r""" - Rectangular ROI subclass with a single scale handle at the top-right corner. - - ============== ============================================================= - **Arguments** - pos (length-2 sequence) The position of the ROI origin. - See ROI(). - size (length-2 sequence) The size of the ROI. See ROI(). - centered (bool) If True, scale handles affect the ROI relative to its - center, rather than its origin. - sideScalers (bool) If True, extra scale handles are added at the top and - right edges. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - - """ - def __init__(self, pos, size, centered=False, sideScalers=False, **args): - ROI.__init__(self, pos, size, **args) - if centered: - center = [0.5, 0.5] - else: - center = [0, 0] - - self.addScaleHandle([1, 1], center) - if sideScalers: - self.addScaleHandle([1, 0.5], [center[0], 0.5]) - self.addScaleHandle([0.5, 1], [0.5, center[1]]) - -class LineROI(ROI): - r""" - Rectangular ROI subclass with scale-rotate handles on either side. This - allows the ROI to be positioned as if moving the ends of a line segment. - A third handle controls the width of the ROI orthogonal to its "line" axis. - - ============== ============================================================= - **Arguments** - pos1 (length-2 sequence) The position of the center of the ROI's - left edge. - pos2 (length-2 sequence) The position of the center of the ROI's - right edge. - width (float) The width of the ROI. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - - """ - def __init__(self, pos1, pos2, width, **args): - pos1 = Point(pos1) - pos2 = Point(pos2) - d = pos2-pos1 - l = d.length() - ang = Point(1, 0).angle(d) - ra = ang * np.pi / 180. - c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) - pos1 = pos1 + c - - ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) - self.addScaleRotateHandle([0, 0.5], [1, 0.5]) - self.addScaleRotateHandle([1, 0.5], [0, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - - - - - -class MultiRectROI(QtGui.QGraphicsObject): - r""" - Chain of rectangular ROIs connected by handles. - - This is generally used to mark a curved path through - an image similarly to PolyLineROI. It differs in that each segment - of the chain is rectangular instead of linear and thus has width. - - ============== ============================================================= - **Arguments** - points (list of length-2 sequences) The list of points in the path. - width (float) The width of the ROIs orthogonal to the path. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - """ - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - - def __init__(self, points, width, pen=None, **args): - QtGui.QGraphicsObject.__init__(self) - self.pen = pen - self.roiArgs = args - self.lines = [] - if len(points) < 2: - raise Exception("Must start with at least 2 points") - - ## create first segment - self.addSegment(points[1], connectTo=points[0], scaleHandle=True) - - ## create remaining segments - for p in points[2:]: - self.addSegment(p) - - - def paint(self, *args): - pass - - def boundingRect(self): - return QtCore.QRectF() - - def roiChangedEvent(self): - w = self.lines[0].state['size'][1] - for l in self.lines[1:]: - w0 = l.state['size'][1] - if w == w0: - continue - l.scale([1.0, w/w0], center=[0.5,0.5]) - self.sigRegionChanged.emit(self) - - def roiChangeStartedEvent(self): - self.sigRegionChangeStarted.emit(self) - - def roiChangeFinishedEvent(self): - self.sigRegionChangeFinished.emit(self) - - def getHandlePositions(self): - """Return the positions of all handles in local coordinates.""" - pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())] - for l in self.lines: - pos.append(self.mapFromScene(l.getHandles()[1].scenePos())) - return pos - - def getArrayRegion(self, arr, img=None, axes=(0,1), **kwds): - """ - Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion` for each rect - in the chain concatenated into a single ndarray. - - See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the - arguments. - - Note: ``returnMappedCoords`` is not yet supported for this ROI type. - """ - rgns = [] - for l in self.lines: - rgn = l.getArrayRegion(arr, img, axes=axes, **kwds) - if rgn is None: - continue - rgns.append(rgn) - #print l.state['size'] - - ## make sure orthogonal axis is the same size - ## (sometimes fp errors cause differences) - if img.axisOrder == 'row-major': - axes = axes[::-1] - ms = min([r.shape[axes[1]] for r in rgns]) - sl = [slice(None)] * rgns[0].ndim - sl[axes[1]] = slice(0,ms) - rgns = [r[tuple(sl)] for r in rgns] - #print [r.shape for r in rgns], axes - - return np.concatenate(rgns, axis=axes[0]) - - def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None): - """ - Add a new segment to the ROI connecting from the previous endpoint to *pos*. - (pos is specified in the parent coordinate system of the MultiRectROI) - """ - - ## by default, connect to the previous endpoint - if connectTo is None: - connectTo = self.lines[-1].getHandles()[1] - - ## create new ROI - newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs) - self.lines.append(newRoi) - - ## Add first SR handle - if isinstance(connectTo, Handle): - self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo) - newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene') - else: - h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5]) - newRoi.movePoint(h, connectTo, coords='scene') - - ## add second SR handle - h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5]) - newRoi.movePoint(h, pos) - - ## optionally add scale handle (this MUST come after the two SR handles) - if scaleHandle: - newRoi.addScaleHandle([0.5, 1], [0.5, 0.5]) - - newRoi.translatable = False - newRoi.sigRegionChanged.connect(self.roiChangedEvent) - newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) - newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) - self.sigRegionChanged.emit(self) - - - def removeSegment(self, index=-1): - """Remove a segment from the ROI.""" - roi = self.lines[index] - self.lines.pop(index) - self.scene().removeItem(roi) - roi.sigRegionChanged.disconnect(self.roiChangedEvent) - roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent) - roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent) - - self.sigRegionChanged.emit(self) - - -class MultiLineROI(MultiRectROI): - def __init__(self, *args, **kwds): - MultiRectROI.__init__(self, *args, **kwds) - print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)") - - -class EllipseROI(ROI): - r""" - Elliptical ROI subclass with one scale handle and one rotation handle. - - - ============== ============================================================= - **Arguments** - pos (length-2 sequence) The position of the ROI's origin. - size (length-2 sequence) The size of the ROI's bounding rectangle. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - - """ - def __init__(self, pos, size, **args): - self.path = None - ROI.__init__(self, pos, size, **args) - self.sigRegionChanged.connect(self._clearPath) - self._addHandles() - - def _addHandles(self): - self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - - def _clearPath(self): - self.path = None - - def paint(self, p, opt, widget): - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - - p.scale(r.width(), r.height())## workaround for GL bug - r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) - - p.drawEllipse(r) - - def getArrayRegion(self, arr, img=None, axes=(0, 1), **kwds): - """ - Return the result of :meth:`~pyqtgraph.ROI.getArrayRegion` masked by the - elliptical shape of the ROI. Regions outside the ellipse are set to 0. - - See :meth:`~pyqtgraph.ROI.getArrayRegion` for a description of the - arguments. - - Note: ``returnMappedCoords`` is not yet supported for this ROI type. - """ - # Note: we could use the same method as used by PolyLineROI, but this - # implementation produces a nicer mask. - arr = ROI.getArrayRegion(self, arr, img, axes, **kwds) - if arr is None or arr.shape[axes[0]] == 0 or arr.shape[axes[1]] == 0: - return arr - w = arr.shape[axes[0]] - h = arr.shape[axes[1]] - - ## generate an ellipsoidal mask - mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) - - # reshape to match array axes - if axes[0] > axes[1]: - mask = mask.T - shape = [(n if i in axes else 1) for i,n in enumerate(arr.shape)] - mask = mask.reshape(shape) - - return arr * mask - - def shape(self): - if self.path is None: - path = QtGui.QPainterPath() - - # Note: Qt has a bug where very small ellipses (radius <0.001) do - # not correctly intersect with mouse position (upper-left and - # lower-right quadrants are not clickable). - #path.addEllipse(self.boundingRect()) - - # Workaround: manually draw the path. - br = self.boundingRect() - center = br.center() - r1 = br.width() / 2. - r2 = br.height() / 2. - theta = np.linspace(0, 2*np.pi, 24) - x = center.x() + r1 * np.cos(theta) - y = center.y() + r2 * np.sin(theta) - path.moveTo(x[0], y[0]) - for i in range(1, len(x)): - path.lineTo(x[i], y[i]) - self.path = path - - return self.path - - - -class CircleROI(EllipseROI): - r""" - Circular ROI subclass. Behaves exactly as EllipseROI, but may only be scaled - proportionally to maintain its aspect ratio. - - ============== ============================================================= - **Arguments** - pos (length-2 sequence) The position of the ROI's origin. - size (length-2 sequence) The size of the ROI's bounding rectangle. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - - """ - def __init__(self, pos, size=None, radius=None, **args): - if size is None: - if radius is None: - raise TypeError("Must provide either size or radius.") - size = (radius*2, radius*2) - EllipseROI.__init__(self, pos, size, **args) - self.aspectLocked = True - - def _addHandles(self): - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - - -class PolygonROI(ROI): - - def __init__(self, positions, pos=None, **args): - warnings.warn( - 'PolygonROI has been deprecated, will be removed in 0.13' - 'use PolyLineROI instead', - DeprecationWarning, stacklevel=2 - ) - - if pos is None: - pos = [0,0] - ROI.__init__(self, pos, [1,1], **args) - for p in positions: - self.addFreeHandle(p) - self.setZValue(1000) - - def listPoints(self): - return [p['item'].pos() for p in self.handles] - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - for i in range(len(self.handles)): - h1 = self.handles[i]['item'].pos() - h2 = self.handles[i-1]['item'].pos() - p.drawLine(h1, h2) - - def boundingRect(self): - r = QtCore.QRectF() - for h in self.handles: - r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs - return r - - def shape(self): - p = QtGui.QPainterPath() - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - return p - - def stateCopy(self): - sc = {} - sc['pos'] = Point(self.state['pos']) - sc['size'] = Point(self.state['size']) - sc['angle'] = self.state['angle'] - return sc - - -class PolyLineROI(ROI): - r""" - Container class for multiple connected LineSegmentROIs. - - This class allows the user to draw paths of multiple line segments. - - ============== ============================================================= - **Arguments** - positions (list of length-2 sequences) The list of points in the path. - Note that, unlike the handle positions specified in other - ROIs, these positions must be expressed in the normal - coordinate system of the ROI, rather than (0 to 1) relative - to the size of the ROI. - closed (bool) if True, an extra LineSegmentROI is added connecting - the beginning and end points. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - - """ - def __init__(self, positions, closed=False, pos=None, **args): - - if pos is None: - pos = [0,0] - - self.closed = closed - self.segments = [] - ROI.__init__(self, pos, size=[1,1], **args) - - self.setPoints(positions) - - def setPoints(self, points, closed=None): - """ - Set the complete sequence of points displayed by this ROI. - - ============= ========================================================= - **Arguments** - points List of (x,y) tuples specifying handle locations to set. - closed If bool, then this will set whether the ROI is closed - (the last point is connected to the first point). If - None, then the closed mode is left unchanged. - ============= ========================================================= - - """ - if closed is not None: - self.closed = closed - - self.clearPoints() - - for p in points: - self.addFreeHandle(p) - - start = -1 if self.closed else 0 - for i in range(start, len(self.handles)-1): - self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) - - def clearPoints(self): - """ - Remove all handles and segments. - """ - while len(self.handles) > 0: - self.removeHandle(self.handles[0]['item']) - - def getState(self): - state = ROI.getState(self) - state['closed'] = self.closed - state['points'] = [Point(h.pos()) for h in self.getHandles()] - return state - - def saveState(self): - state = ROI.saveState(self) - state['closed'] = self.closed - state['points'] = [tuple(h.pos()) for h in self.getHandles()] - return state - - def setState(self, state): - ROI.setState(self, state) - self.setPoints(state['points'], closed=state['closed']) - - def addSegment(self, h1, h2, index=None): - seg = _PolyLineSegment(handles=(h1, h2), pen=self.pen, hoverPen=self.hoverPen, - parent=self, movable=False) - if index is None: - self.segments.append(seg) - else: - self.segments.insert(index, seg) - seg.sigClicked.connect(self.segmentClicked) - seg.setAcceptedMouseButtons(QtCore.Qt.LeftButton) - seg.setZValue(self.zValue()+1) - for h in seg.handles: - h['item'].setDeletable(True) - h['item'].setAcceptedMouseButtons(h['item'].acceptedMouseButtons() | QtCore.Qt.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles - - def setMouseHover(self, hover): - ## Inform all the ROI's segments that the mouse is(not) hovering over it - ROI.setMouseHover(self, hover) - for s in self.segments: - s.setParentHover(hover) - - def addHandle(self, info, index=None): - h = ROI.addHandle(self, info, index=index) - h.sigRemoveRequested.connect(self.removeHandle) - self.stateChanged(finish=True) - return h - - def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system - if ev != None: - pos = segment.mapToParent(ev.pos()) - elif pos != None: - pos = pos - else: - raise Exception("Either an event or a position must be given.") - h1 = segment.handles[0]['item'] - h2 = segment.handles[1]['item'] - - i = self.segments.index(segment) - h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2)) - self.addSegment(h3, h2, index=i+1) - segment.replaceHandle(h2, h3) - - def removeHandle(self, handle, updateSegments=True): - ROI.removeHandle(self, handle) - handle.sigRemoveRequested.disconnect(self.removeHandle) - - if not updateSegments: - return - segments = handle.rois[:] - - if len(segments) == 1: - self.removeSegment(segments[0]) - elif len(segments) > 1: - handles = [h['item'] for h in segments[1].handles] - handles.remove(handle) - segments[0].replaceHandle(handle, handles[0]) - self.removeSegment(segments[1]) - self.stateChanged(finish=True) - - def removeSegment(self, seg): - for handle in seg.handles[:]: - seg.removeHandle(handle['item']) - self.segments.remove(seg) - seg.sigClicked.disconnect(self.segmentClicked) - self.scene().removeItem(seg) - - def checkRemoveHandle(self, h): - ## called when a handle is about to display its context menu - if self.closed: - return len(self.handles) > 3 - else: - return len(self.handles) > 2 - - def paint(self, p, *args): - pass - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - p = QtGui.QPainterPath() - if len(self.handles) == 0: - return p - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - p.lineTo(self.handles[0]['item'].pos()) - return p - - def getArrayRegion(self, *args, **kwds): - return self._getArrayRegionForArbitraryShape(*args, **kwds) - - def setPen(self, *args, **kwds): - ROI.setPen(self, *args, **kwds) - for seg in self.segments: - seg.setPen(*args, **kwds) - - - -class LineSegmentROI(ROI): - r""" - ROI subclass with two freely-moving handles defining a line. - - ============== ============================================================= - **Arguments** - positions (list of two length-2 sequences) The endpoints of the line - segment. Note that, unlike the handle positions specified in - other ROIs, these positions must be expressed in the normal - coordinate system of the ROI, rather than (0 to 1) relative - to the size of the ROI. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - """ - - def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args): - if pos is None: - pos = [0,0] - - ROI.__init__(self, pos, [1,1], **args) - if len(positions) > 2: - raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.") - - for i, p in enumerate(positions): - self.addFreeHandle(p, item=handles[i]) - - @property - def endpoints(self): - # must not be cached because self.handles may change. - return [h['item'] for h in self.handles] - - def listPoints(self): - return [p['item'].pos() for p in self.handles] - - def getState(self): - state = ROI.getState(self) - state['points'] = [Point(h.pos()) for h in self.getHandles()] - return state - - def saveState(self): - state = ROI.saveState(self) - state['points'] = [tuple(h.pos()) for h in self.getHandles()] - return state - - def setState(self, state): - ROI.setState(self, state) - p1 = [state['points'][0][0]+state['pos'][0], state['points'][0][1]+state['pos'][1]] - p2 = [state['points'][1][0]+state['pos'][0], state['points'][1][1]+state['pos'][1]] - self.movePoint(self.getHandles()[0], p1, finish=False) - self.movePoint(self.getHandles()[1], p2) - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - h1 = self.endpoints[0].pos() - h2 = self.endpoints[1].pos() - p.drawLine(h1, h2) - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - p = QtGui.QPainterPath() - - h1 = self.endpoints[0].pos() - h2 = self.endpoints[1].pos() - dh = h2-h1 - if dh.length() == 0: - return p - pxv = self.pixelVectors(dh)[1] - if pxv is None: - return p - - pxv *= 4 - - p.moveTo(h1+pxv) - p.lineTo(h2+pxv) - p.lineTo(h2-pxv) - p.lineTo(h1-pxv) - p.lineTo(h1+pxv) - - return p - - def getArrayRegion(self, data, img, axes=(0,1), order=1, returnMappedCoords=False, **kwds): - """ - Use the position of this ROI relative to an imageItem to pull a slice - from an array. - - Since this pulls 1D data from a 2D coordinate system, the return value - will have ndim = data.ndim-1 - - See :meth:`~pytqgraph.ROI.getArrayRegion` for a description of the - arguments. - """ - imgPts = [self.mapToItem(img, h.pos()) for h in self.endpoints] - rgns = [] - coords = [] - - d = Point(imgPts[1] - imgPts[0]) - o = Point(imgPts[0]) - rgn = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=order, returnCoords=returnMappedCoords, **kwds) - - return rgn - - -class _PolyLineSegment(LineSegmentROI): - # Used internally by PolyLineROI - def __init__(self, *args, **kwds): - self._parentHovering = False - LineSegmentROI.__init__(self, *args, **kwds) - - def setParentHover(self, hover): - # set independently of own hover state - if self._parentHovering != hover: - self._parentHovering = hover - self._updateHoverColor() - - def _makePen(self): - if self.mouseHovering or self._parentHovering: - return self.hoverPen - else: - return self.pen - - def hoverEvent(self, ev): - # accept drags even though we discard them to prevent competition with parent ROI - # (unless parent ROI is not movable) - if self.parentItem().translatable: - ev.acceptDrags(QtCore.Qt.LeftButton) - return LineSegmentROI.hoverEvent(self, ev) - - -class CrosshairROI(ROI): - """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" - - def __init__(self, pos=None, size=None, **kargs): - if size == None: - size=[1,1] - if pos == None: - pos = [0,0] - self._shape = None - ROI.__init__(self, pos, size, **kargs) - - self.sigRegionChanged.connect(self.invalidate) - self.addScaleRotateHandle(Point(1, 0), Point(0, 0)) - self.aspectLocked = True - - def invalidate(self): - self._shape = None - self.prepareGeometryChange() - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - if self._shape is None: - radius = self.getState()['size'][1] - p = QtGui.QPainterPath() - p.moveTo(Point(0, -radius)) - p.lineTo(Point(0, radius)) - p.moveTo(Point(-radius, 0)) - p.lineTo(Point(radius, 0)) - p = self.mapToDevice(p) - stroker = QtGui.QPainterPathStroker() - stroker.setWidth(10) - outline = stroker.createStroke(p) - self._shape = self.mapFromDevice(outline) - - return self._shape - - def paint(self, p, *args): - radius = self.getState()['size'][1] - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - - p.drawLine(Point(0, -radius), Point(0, radius)) - p.drawLine(Point(-radius, 0), Point(radius, 0)) - - -class RulerROI(LineSegmentROI): - def paint(self, p, *args): - LineSegmentROI.paint(self, p, *args) - h1 = self.handles[0]['item'].pos() - h2 = self.handles[1]['item'].pos() - p1 = p.transform().map(h1) - p2 = p.transform().map(h2) - - vec = Point(h2) - Point(h1) - length = vec.length() - angle = vec.angle(Point(1, 0)) - - pvec = p2 - p1 - pvecT = Point(pvec.y(), -pvec.x()) - pos = 0.5 * (p1 + p2) + pvecT * 40 / pvecT.length() - - p.resetTransform() - - txt = fn.siFormat(length, suffix='m') + '\n%0.1f deg' % angle - p.drawText(QtCore.QRectF(pos.x()-50, pos.y()-50, 100, 100), QtCore.Qt.AlignCenter, txt) - - def boundingRect(self): - r = LineSegmentROI.boundingRect(self) - pxl = self.pixelLength(Point([1, 0])) - if pxl is None: - return r - pxw = 50 * pxl - return r.adjusted(-50, -50, 50, 50) - - -class TriangleROI(ROI): - """ - Equilateral triangle ROI subclass with one scale handle and one rotation handle. - Arguments - pos (length-2 sequence) The position of the ROI's origin. - size (float) The length of an edge of the triangle. - \**args All extra keyword arguments are passed to ROI() - ============== ============================================================= - """ - - def __init__(self, pos, size, **args): - ROI.__init__(self, pos, [size, size], **args) - self.aspectLocked = True - angles = np.linspace(0, np.pi * 4 / 3, 3) - verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 - self.poly = QtGui.QPolygonF() - for pt in verticies: - self.poly.append(QtCore.QPointF(*pt)) - self.addRotateHandle(verticies[0], [0.5, 0.5]) - self.addScaleHandle(verticies[1], [0.5, 0.5]) - - def paint(self, p, *args): - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.scale(r.width(), r.height()) - p.setPen(self.currentPen) - p.drawPolygon(self.poly) - - def shape(self): - self.path = QtGui.QPainterPath() - r = self.boundingRect() - # scale the path to match whats on the screen - t = QtGui.QTransform() - t.scale(r.width(), r.height()) - self.path.addPolygon(self.poly) - return t.map(self.path) - - def getArrayRegion(self, *args, **kwds): - return self._getArrayRegionForArbitraryShape(*args, **kwds) diff --git a/pyqtgraph/graphicsItems/ScaleBar.py b/pyqtgraph/graphicsItems/ScaleBar.py deleted file mode 100644 index 8ba546f..0000000 --- a/pyqtgraph/graphicsItems/ScaleBar.py +++ /dev/null @@ -1,71 +0,0 @@ -from ..Qt import QtGui, QtCore -from .GraphicsObject import * -from .GraphicsWidgetAnchor import * -from .TextItem import TextItem -import numpy as np -from .. import functions as fn -from .. import getConfigOption -from ..Point import Point - -__all__ = ['ScaleBar'] - -class ScaleBar(GraphicsObject, GraphicsWidgetAnchor): - """ - Displays a rectangular bar to indicate the relative scale of objects on the view. - """ - def __init__(self, size, width=5, brush=None, pen=None, suffix='m', offset=None): - GraphicsObject.__init__(self) - GraphicsWidgetAnchor.__init__(self) - self.setFlag(self.ItemHasNoContents) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - - if brush is None: - brush = getConfigOption('foreground') - self.brush = fn.mkBrush(brush) - self.pen = fn.mkPen(pen) - self._width = width - self.size = size - if offset == None: - offset = (0,0) - self.offset = offset - - self.bar = QtGui.QGraphicsRectItem() - self.bar.setPen(self.pen) - self.bar.setBrush(self.brush) - self.bar.setParentItem(self) - - self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1)) - self.text.setParentItem(self) - - def parentChanged(self): - view = self.parentItem() - if view is None: - return - view.sigRangeChanged.connect(self.updateBar) - self.updateBar() - - - def updateBar(self): - view = self.parentItem() - if view is None: - return - p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0)) - p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0)) - w = (p2-p1).x() - self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width)) - self.text.setPos(-w/2., 0) - - def boundingRect(self): - return QtCore.QRectF() - - def setParentItem(self, p): - ret = GraphicsObject.setParentItem(self, p) - if self.offset is not None: - offset = Point(self.offset) - anchorx = 1 if offset[0] <= 0 else 0 - anchory = 1 if offset[1] <= 0 else 0 - anchor = (anchorx, anchory) - self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) - return ret - - diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py deleted file mode 100644 index 3d0b9f1..0000000 --- a/pyqtgraph/graphicsItems/ScatterPlotItem.py +++ /dev/null @@ -1,1353 +0,0 @@ -# -*- coding: utf-8 -*- -import warnings -from itertools import repeat, chain -try: - from itertools import imap -except ImportError: - imap = map -import itertools -import numpy as np -import weakref -from ..Qt import QtGui, QtCore, QT_LIB -from ..Point import Point -from .. import functions as fn -from .GraphicsItem import GraphicsItem -from .GraphicsObject import GraphicsObject -from .. import getConfigOption -from collections import OrderedDict -from .. import debug -from ..python2_3 import basestring - - -__all__ = ['ScatterPlotItem', 'SpotItem'] - - -# When pxMode=True for ScatterPlotItem, QPainter.drawPixmap is used for drawing, which -# has multiple type signatures. One takes int coordinates of source and target -# rectangles, and another takes QRectF objects. The latter approach has the overhead of -# updating these objects, which can be almost as much as drawing. -# For PyQt5, drawPixmap is significantly faster with QRectF coordinates for some -# reason, offsetting this overhead. For PySide2 this is not the case, and the QRectF -# maintenance overhead is an unnecessary burden. If this performance issue is solved -# by PyQt5, the QRectF coordinate approach can be removed by simply deleting all of the -# "if _USE_QRECT" code blocks in ScatterPlotItem. Ideally, drawPixmap would accept the -# numpy arrays of coordinates directly, which would improve performance significantly, -# as the separate calls to this method are the current bottleneck. -# See: https://bugreports.qt.io/browse/PYSIDE-163 - -_USE_QRECT = QT_LIB not in ['PySide2', 'PySide6'] - -## Build all symbol paths -name_list = ['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star', - 'arrow_up', 'arrow_right', 'arrow_down', 'arrow_left'] -Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in name_list]) -Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) -Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) -coords = { - 't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)], - 't1': [(-0.5, 0.5), (0, -0.5), (0.5, 0.5)], - 't2': [(-0.5, -0.5), (-0.5, 0.5), (0.5, 0)], - 't3': [(0.5, 0.5), (0.5, -0.5), (-0.5, 0)], - 'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)], - '+': [ - (-0.5, -0.1), (-0.5, 0.1), (-0.1, 0.1), (-0.1, 0.5), - (0.1, 0.5), (0.1, 0.1), (0.5, 0.1), (0.5, -0.1), - (0.1, -0.1), (0.1, -0.5), (-0.1, -0.5), (-0.1, -0.1) - ], - 'p': [(0, -0.5), (-0.4755, -0.1545), (-0.2939, 0.4045), - (0.2939, 0.4045), (0.4755, -0.1545)], - 'h': [(0.433, 0.25), (0., 0.5), (-0.433, 0.25), (-0.433, -0.25), - (0, -0.5), (0.433, -0.25)], - 'star': [(0, -0.5), (-0.1123, -0.1545), (-0.4755, -0.1545), - (-0.1816, 0.059), (-0.2939, 0.4045), (0, 0.1910), - (0.2939, 0.4045), (0.1816, 0.059), (0.4755, -0.1545), - (0.1123, -0.1545)], - 'arrow_down': [ - (-0.125, 0.125), (0, 0), (0.125, 0.125), - (0.05, 0.125), (0.05, 0.5), (-0.05, 0.5), (-0.05, 0.125) - ] -} -for k, c in coords.items(): - Symbols[k].moveTo(*c[0]) - for x,y in c[1:]: - Symbols[k].lineTo(x, y) - Symbols[k].closeSubpath() -tr = QtGui.QTransform() -tr.rotate(45) -Symbols['x'] = tr.map(Symbols['+']) -tr.rotate(45) -Symbols['arrow_right'] = tr.map(Symbols['arrow_down']) -Symbols['arrow_up'] = tr.map(Symbols['arrow_right']) -Symbols['arrow_left'] = tr.map(Symbols['arrow_up']) -_DEFAULT_STYLE = {'symbol': None, 'size': -1, 'pen': None, 'brush': None, 'visible': True} - - -def drawSymbol(painter, symbol, size, pen, brush): - if symbol is None: - return - painter.scale(size, size) - painter.setPen(pen) - painter.setBrush(brush) - if isinstance(symbol, basestring): - symbol = Symbols[symbol] - if np.isscalar(symbol): - symbol = list(Symbols.values())[symbol % len(Symbols)] - painter.drawPath(symbol) - - -def renderSymbol(symbol, size, pen, brush, device=None): - """ - Render a symbol specification to QImage. - Symbol may be either a QPainterPath or one of the keys in the Symbols dict. - If *device* is None, a new QPixmap will be returned. Otherwise, - the symbol will be rendered into the device specified (See QPainter documentation - for more information). - """ - ## Render a spot with the given parameters to a pixmap - penPxWidth = max(np.ceil(pen.widthF()), 1) - if device is None: - device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) - device.fill(0) - p = QtGui.QPainter(device) - try: - p.setRenderHint(p.Antialiasing) - p.translate(device.width()*0.5, device.height()*0.5) - drawSymbol(p, symbol, size, pen, brush) - finally: - p.end() - return device - -def makeSymbolPixmap(size, pen, brush, symbol): - warnings.warn( - "This is an internal function that is no longer being used. " - "Will be removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - img = renderSymbol(symbol, size, pen, brush) - return QtGui.QPixmap(img) - - -def _mkPen(*args, **kwargs): - """ - Wrapper for fn.mkPen which avoids creating a new QPen object if passed one as its - sole argument. This is used to avoid unnecessary cache misses in SymbolAtlas which - uses the QPen object id in its key. - """ - if len(args) == 1 and isinstance(args[0], QtGui.QPen): - return args[0] - else: - return fn.mkPen(*args, **kwargs) - - -def _mkBrush(*args, **kwargs): - """ - Wrapper for fn.mkBrush which avoids creating a new QBrush object if passed one as its - sole argument. This is used to avoid unnecessary cache misses in SymbolAtlas which - uses the QBrush object id in its key. - """ - if len(args) == 1 and isinstance(args[0], QtGui.QBrush): - return args[0] - else: - return fn.mkBrush(*args, **kwargs) - - -class SymbolAtlas(object): - """ - Used to efficiently construct a single QPixmap containing all rendered symbols - for a ScatterPlotItem. This is required for fragment rendering. - - Use example: - atlas = SymbolAtlas() - sc1 = atlas[[('o', 5, QPen(..), QBrush(..))]] - sc2 = atlas[[('t', 10, QPen(..), QBrush(..))]] - pm = atlas.pixmap - - """ - _idGenerator = itertools.count() - - def __init__(self): - self._data = np.zeros((0, 0, 4), dtype=np.ubyte) # numpy array of atlas image - self._coords = {} - self._pixmap = None - self._maxWidth = 0 - self._totalWidth = 0 - self._totalArea = 0 - self._pos = (0, 0) - self._rowShape = (0, 0) - - def __getitem__(self, styles): - """ - Given a list of tuples, (symbol, size, pen, brush), return a list of coordinates of - corresponding symbols within the atlas. Note that these coordinates may change if the atlas is rebuilt. - """ - keys = self._keys(styles) - new = {key: style for key, style in zip(keys, styles) if key not in self._coords} - - if new: - self._extend(new) - - return list(imap(self._coords.__getitem__, keys)) - - def __len__(self): - return len(self._coords) - - @property - def pixmap(self): - if self._pixmap is None: - self._pixmap = self._createPixmap() - return self._pixmap - - @property - def maxWidth(self): - return self._maxWidth - - def rebuild(self, styles=None): - profiler = debug.Profiler() - if styles is None: - data = [] - else: - keys = set(self._keys(styles)) - data = list(self._itemData(keys)) - - self.clear() - if data: - self._extendFromData(data) - - def clear(self): - self.__init__() - - def diagnostics(self): - n = len(self) - w, h, _ = self._data.shape - a = self._totalArea - return dict(count=n, - width=w, - height=h, - area=w * h, - area_used=1.0 if n == 0 else a / (w * h), - squareness=1.0 if n == 0 else 2 * w * h / (w**2 + h**2)) - - def _keys(self, styles): - def getId(obj): - try: - return obj._id - except AttributeError: - obj._id = next(SymbolAtlas._idGenerator) - return obj._id - - return [ - (symbol if isinstance(symbol, (str, int)) else getId(symbol), size, getId(pen), getId(brush)) - for symbol, size, pen, brush in styles - ] - - def _itemData(self, keys): - for key in keys: - y, x, h, w = self._coords[key] - yield key, self._data[x:x + w, y:y + h] - - def _extend(self, styles): - profiler = debug.Profiler() - - images = [] - data = [] - for key, style in styles.items(): - img = renderSymbol(*style) - arr = fn.imageToArray(img, copy=False, transpose=False) - images.append(img) # keep these to delay garbage collection - data.append((key, arr)) - - profiler('render') - self._extendFromData(data) - profiler('insert') - - def _extendFromData(self, data): - self._pack(data) - - # expand array if necessary - wNew, hNew = self._minDataShape() - wOld, hOld, _ = self._data.shape - if (wNew > wOld) or (hNew > hOld): - arr = np.zeros((wNew, hNew, 4), dtype=np.ubyte) - arr[:wOld, :hOld] = self._data - self._data = arr - - # insert data into array - for key, arr in data: - y, x, h, w = self._coords[key] - self._data[x:x+w, y:y+h] = arr - - self._pixmap = None - - def _pack(self, data): - # pack each item rectangle as efficiently as possible into a larger, expanding, approximate square - n = len(self) - wMax = self._maxWidth - wSum = self._totalWidth - aSum = self._totalArea - x, y = self._pos - wRow, hRow = self._rowShape - - # update packing statistics - for _, arr in data: - w, h, _ = arr.shape - wMax = max(w, wMax) - wSum += w - aSum += w * h - n += len(data) - - # maybe expand row width for squareness and to accommodate largest width - wRowEst = int(wSum / (n ** 0.5)) - if wRowEst > 2 * wRow: - wRow = wRowEst - wRow = max(wMax, wRow) - - # set coordinates by packing along rows - # sort by rectangle height first to improve packing density - for key, arr in sorted(data, key=lambda data: data[1].shape[1]): - w, h, _ = arr.shape - if x + w > wRow: - # move up a row - x = 0 - y += hRow - hRow = h - hRow = max(h, hRow) - self._coords[key] = (y, x, h, w) - x += w - - self._maxWidth = wMax - self._totalWidth = wSum - self._totalArea = aSum - self._pos = (x, y) - self._rowShape = (wRow, hRow) - - def _minDataShape(self): - x, y = self._pos - w, h = self._rowShape - return int(w), int(y + h) - - def _createPixmap(self): - profiler = debug.Profiler() - if self._data.size == 0: - pm = QtGui.QPixmap(0, 0) - else: - img = fn.makeQImage(self._data, copy=False, transpose=False) - pm = QtGui.QPixmap(img) - return pm - - -class ScatterPlotItem(GraphicsObject): - """ - Displays a set of x/y points. Instances of this class are created - automatically as part of PlotDataItem; these rarely need to be instantiated - directly. - - The size, shape, pen, and fill brush may be set for each point individually - or for all points. - - - ============================ =============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data being plotted has changed - sigClicked(self, points, ev) Emitted when points are clicked. Sends a list - of all the points under the mouse pointer. - sigHovered(self, points, ev) Emitted when the item is hovered. Sends a list - of all the points under the mouse pointer. - ============================ =============================================== - - """ - #sigPointClicked = QtCore.Signal(object, object) - sigClicked = QtCore.Signal(object, object, object) - sigHovered = QtCore.Signal(object, object, object) - sigPlotChanged = QtCore.Signal(object) - - def __init__(self, *args, **kargs): - """ - Accepts the same arguments as setData() - """ - profiler = debug.Profiler() - GraphicsObject.__init__(self) - - self.picture = None # QPicture used for rendering when pxmode==False - self.fragmentAtlas = SymbolAtlas() - - dtype = [ - ('x', float), - ('y', float), - ('size', float), - ('symbol', object), - ('pen', object), - ('brush', object), - ('visible', bool), - ('data', object), - ('hovered', bool), - ('item', object), - ('sourceRect', [ - ('x', int), - ('y', int), - ('w', int), - ('h', int) - ]) - ] - - if _USE_QRECT: - dtype.extend([ - ('sourceQRect', object), - ('targetQRect', object), - ('targetQRectValid', bool) - ]) - self._sourceQRect = {} - - self.data = np.empty(0, dtype=dtype) - self.bounds = [None, None] ## caches data bounds - self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots - self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots - self.opts = { - 'pxMode': True, - 'useCache': True, ## If useCache is False, symbols are re-drawn on every paint. - 'antialias': getConfigOption('antialias'), - 'compositionMode': None, - 'name': None, - 'symbol': 'o', - 'size': 7, - 'pen': fn.mkPen(getConfigOption('foreground')), - 'brush': fn.mkBrush(100, 100, 150), - 'hoverable': False, - 'tip': 'x: {x:.3g}\ny: {y:.3g}\ndata={data}'.format, - } - self.opts.update( - {'hover' + opt.title(): _DEFAULT_STYLE[opt] for opt in ['symbol', 'size', 'pen', 'brush']} - ) - profiler() - self.setData(*args, **kargs) - profiler('setData') - - #self.setCacheMode(self.DeviceCoordinateCache) - - def setData(self, *args, **kargs): - """ - **Ordered Arguments:** - - * If there is only one unnamed argument, it will be interpreted like the 'spots' argument. - * If there are two unnamed arguments, they will be interpreted as sequences of x and y values. - - ====================== =============================================================================================== - **Keyword Arguments:** - *spots* Optional list of dicts. Each dict specifies parameters for a single spot: - {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method - of passing in data for the corresponding arguments. - *x*,*y* 1D arrays of x,y values. - *pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples) - *pxMode* If True, spots are always the same size regardless of scaling, and size is given in px. - Otherwise, size is in scene coordinates and the spots scale with the view. To ensure - effective caching, QPen and QBrush objects should be reused as much as possible. - Default is True - *symbol* can be one (or a list) of: - * 'o' circle (default) - * 's' square - * 't' triangle - * 'd' diamond - * '+' plus - * any QPainterPath to specify custom symbol shapes. To properly obey the position and size, - custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also - possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape. - *pen* The pen (or list of pens) to use for drawing spot outlines. - *brush* The brush (or list of brushes) to use for filling spots. - *size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise, - it is in the item's local coordinate system. - *data* a list of python objects used to uniquely identify each spot. - *hoverable* If True, sigHovered is emitted with a list of hovered points, a tool tip is shown containing - information about them, and an optional separate style for them is used. Default is False. - *tip* A string-valued function of a spot's (x, y, data) values. Set to None to prevent a tool tip - from being shown. - *hoverSymbol* A single symbol to use for hovered spots. Set to None to keep symbol unchanged. Default is None. - *hoverSize* A single size to use for hovered spots. Set to -1 to keep size unchanged. Default is -1. - *hoverPen* A single pen to use for hovered spots. Set to None to keep pen unchanged. Default is None. - *hoverBrush* A single brush to use for hovered spots. Set to None to keep brush unchanged. Default is None. - *antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are - always rendered with antialiasing (since the rendered symbols can be cached, this - incurs very little performance cost) - *compositionMode* If specified, this sets the composition mode used when drawing the - scatter plot (see QPainter::CompositionMode in the Qt documentation). - *name* The name of this item. Names are used for automatically - generating LegendItem entries and by some exporters. - ====================== =============================================================================================== - """ - if 'identical' in kargs: - warnings.warn( - "The *identical* functionality is handled automatically now. " - "Will be removed in 0.13.", - DeprecationWarning, stacklevel=2 - ) - oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered. - self.clear() ## clear out all old data - self.addPoints(*args, **kargs) - - def addPoints(self, *args, **kargs): - """ - Add new points to the scatter plot. - Arguments are the same as setData() - """ - - ## deal with non-keyword arguments - if len(args) == 1: - kargs['spots'] = args[0] - elif len(args) == 2: - kargs['x'] = args[0] - kargs['y'] = args[1] - elif len(args) > 2: - raise Exception('Only accepts up to two non-keyword arguments.') - - ## convert 'pos' argument to 'x' and 'y' - if 'pos' in kargs: - pos = kargs['pos'] - if isinstance(pos, np.ndarray): - kargs['x'] = pos[:,0] - kargs['y'] = pos[:,1] - else: - x = [] - y = [] - for p in pos: - if isinstance(p, QtCore.QPointF): - x.append(p.x()) - y.append(p.y()) - else: - x.append(p[0]) - y.append(p[1]) - kargs['x'] = x - kargs['y'] = y - - ## determine how many spots we have - if 'spots' in kargs: - numPts = len(kargs['spots']) - elif 'y' in kargs and kargs['y'] is not None: - numPts = len(kargs['y']) - else: - kargs['x'] = [] - kargs['y'] = [] - numPts = 0 - - ## Clear current SpotItems since the data references they contain will no longer be current - self.data['item'][...] = None - - ## Extend record array - oldData = self.data - self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype) - ## note that np.empty initializes object fields to None and string fields to '' - - self.data[:len(oldData)] = oldData - #for i in range(len(oldData)): - #oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array - - newData = self.data[len(oldData):] - newData['size'] = -1 ## indicates to use default size - newData['visible'] = True - - if _USE_QRECT: - newData['targetQRect'] = [QtCore.QRectF() for _ in range(numPts)] - newData['targetQRectValid'] = False - - if 'spots' in kargs: - spots = kargs['spots'] - for i in range(len(spots)): - spot = spots[i] - for k in spot: - if k == 'pos': - pos = spot[k] - if isinstance(pos, QtCore.QPointF): - x,y = pos.x(), pos.y() - else: - x,y = pos[0], pos[1] - newData[i]['x'] = x - newData[i]['y'] = y - elif k == 'pen': - newData[i][k] = _mkPen(spot[k]) - elif k == 'brush': - newData[i][k] = _mkBrush(spot[k]) - elif k in ['x', 'y', 'size', 'symbol', 'data']: - newData[i][k] = spot[k] - else: - raise Exception("Unknown spot parameter: %s" % k) - elif 'y' in kargs: - newData['x'] = kargs['x'] - newData['y'] = kargs['y'] - - if 'name' in kargs: - self.opts['name'] = kargs['name'] - if 'pxMode' in kargs: - self.setPxMode(kargs['pxMode']) - if 'antialias' in kargs: - self.opts['antialias'] = kargs['antialias'] - if 'hoverable' in kargs: - self.opts['hoverable'] = bool(kargs['hoverable']) - if 'tip' in kargs: - self.opts['tip'] = kargs['tip'] - - ## Set any extra parameters provided in keyword arguments - for k in ['pen', 'brush', 'symbol', 'size']: - if k in kargs: - setMethod = getattr(self, 'set' + k[0].upper() + k[1:]) - setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None)) - kh = 'hover' + k.title() - if kh in kargs: - vh = kargs[kh] - if k == 'pen': - vh = _mkPen(vh) - elif k == 'brush': - vh = _mkBrush(vh) - self.opts[kh] = vh - if 'data' in kargs: - self.setPointData(kargs['data'], dataSet=newData) - - self.prepareGeometryChange() - self.informViewBoundsChanged() - self.bounds = [None, None] - self.invalidate() - self.updateSpots(newData) - self.sigPlotChanged.emit(self) - - def invalidate(self): - ## clear any cached drawing state - self.picture = None - self.update() - - def getData(self): - return self.data['x'], self.data['y'] - - def setPoints(self, *args, **kargs): - warnings.warn( - "ScatterPlotItem.setPoints is deprecated, use ScatterPlotItem.setData " - "instead. Will be removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - return self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def name(self): - return self.opts.get('name', None) - - def setPen(self, *args, **kargs): - """Set the pen(s) used to draw the outline around each spot. - If a list or array is provided, then the pen for each spot will be set separately. - Otherwise, the arguments are passed to pg.mkPen and used as the default pen for - all spots which do not have a pen explicitly set.""" - update = kargs.pop('update', True) - dataSet = kargs.pop('dataSet', self.data) - - if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): - pens = args[0] - if 'mask' in kargs and kargs['mask'] is not None: - pens = pens[kargs['mask']] - if len(pens) != len(dataSet): - raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet))) - dataSet['pen'] = list(imap(_mkPen, pens)) - else: - self.opts['pen'] = _mkPen(*args, **kargs) - - dataSet['sourceRect'] = 0 - if update: - self.updateSpots(dataSet) - - def setBrush(self, *args, **kargs): - """Set the brush(es) used to fill the interior of each spot. - If a list or array is provided, then the brush for each spot will be set separately. - Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for - all spots which do not have a brush explicitly set.""" - update = kargs.pop('update', True) - dataSet = kargs.pop('dataSet', self.data) - - if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): - brushes = args[0] - if 'mask' in kargs and kargs['mask'] is not None: - brushes = brushes[kargs['mask']] - if len(brushes) != len(dataSet): - raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet))) - dataSet['brush'] = list(imap(_mkBrush, brushes)) - else: - self.opts['brush'] = _mkBrush(*args, **kargs) - - dataSet['sourceRect'] = 0 - if update: - self.updateSpots(dataSet) - - def setSymbol(self, symbol, update=True, dataSet=None, mask=None): - """Set the symbol(s) used to draw each spot. - If a list or array is provided, then the symbol for each spot will be set separately. - Otherwise, the argument will be used as the default symbol for - all spots which do not have a symbol explicitly set.""" - if dataSet is None: - dataSet = self.data - - if isinstance(symbol, np.ndarray) or isinstance(symbol, list): - symbols = symbol - if mask is not None: - symbols = symbols[mask] - if len(symbols) != len(dataSet): - raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet))) - dataSet['symbol'] = symbols - else: - self.opts['symbol'] = symbol - self._spotPixmap = None - - dataSet['sourceRect'] = 0 - if update: - self.updateSpots(dataSet) - - def setSize(self, size, update=True, dataSet=None, mask=None): - """Set the size(s) used to draw each spot. - If a list or array is provided, then the size for each spot will be set separately. - Otherwise, the argument will be used as the default size for - all spots which do not have a size explicitly set.""" - if dataSet is None: - dataSet = self.data - - if isinstance(size, np.ndarray) or isinstance(size, list): - sizes = size - if mask is not None: - sizes = sizes[mask] - if len(sizes) != len(dataSet): - raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) - dataSet['size'] = sizes - else: - self.opts['size'] = size - self._spotPixmap = None - - dataSet['sourceRect'] = 0 - if update: - self.updateSpots(dataSet) - - - def setPointsVisible(self, visible, update=True, dataSet=None, mask=None): - """Set whether or not each spot is visible. - If a list or array is provided, then the visibility for each spot will be set separately. - Otherwise, the argument will be used for all spots.""" - if dataSet is None: - dataSet = self.data - - if isinstance(visible, np.ndarray) or isinstance(visible, list): - visibilities = visible - if mask is not None: - visibilities = visibilities[mask] - if len(visibilities) != len(dataSet): - raise Exception("Number of visibilities does not match number of points (%d != %d)" % (len(visibilities), len(dataSet))) - dataSet['visible'] = visibilities - else: - dataSet['visible'] = visible - - dataSet['sourceRect'] = 0 - if update: - self.updateSpots(dataSet) - - def setPointData(self, data, dataSet=None, mask=None): - if dataSet is None: - dataSet = self.data - - if isinstance(data, np.ndarray) or isinstance(data, list): - if mask is not None: - data = data[mask] - if len(data) != len(dataSet): - raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) - - ## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time. - ## (otherwise they are converted to tuples and thus lose their field names. - if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1: - for i, rec in enumerate(data): - dataSet['data'][i] = rec - else: - dataSet['data'] = data - - def setPxMode(self, mode): - if self.opts['pxMode'] == mode: - return - - self.opts['pxMode'] = mode - self.invalidate() - - def updateSpots(self, dataSet=None): - profiler = debug.Profiler() - - if dataSet is None: - dataSet = self.data - - invalidate = False - if self.opts['pxMode'] and self.opts['useCache']: - mask = dataSet['sourceRect']['w'] == 0 - if np.any(mask): - invalidate = True - coords = self.fragmentAtlas[ - list(zip(*self._style(['symbol', 'size', 'pen', 'brush'], data=dataSet, idx=mask))) - ] - dataSet['sourceRect'][mask] = coords - if _USE_QRECT: - rects = [] - for c in coords: - try: - rect = self._sourceQRect[c] - except KeyError: - rect = QtCore.QRectF(*c) - self._sourceQRect[c] = rect - rects.append(rect) - - dataSet['sourceQRect'][mask] = rects - dataSet['targetQRectValid'][mask] = False - - self._maybeRebuildAtlas() - else: - invalidate = True - - self._updateMaxSpotSizes(data=dataSet) - - if invalidate: - self.invalidate() - - def _maybeRebuildAtlas(self, threshold=4, minlen=1000): - n = len(self.fragmentAtlas) - if (n > minlen) and (n > threshold * len(self.data)): - self.fragmentAtlas.rebuild( - list(zip(*self._style(['symbol', 'size', 'pen', 'brush']))) - ) - self.data['sourceRect'] = 0 - if _USE_QRECT: - self._sourceQRect.clear() - self.updateSpots() - - def _style(self, opts, data=None, idx=None, scale=None): - if data is None: - data = self.data - - if idx is None: - idx = np.s_[:] - - for opt in opts: - col = data[opt][idx] - if col.base is not None: - col = col.copy() - - if self.opts['hoverable']: - val = self.opts['hover' + opt.title()] - if val != _DEFAULT_STYLE[opt]: - col[data['hovered'][idx]] = val - - col[np.equal(col, _DEFAULT_STYLE[opt])] = self.opts[opt] - - if opt == 'size' and scale is not None: - col *= scale - - yield col - - def _updateMaxSpotSizes(self, **kwargs): - if self.opts['pxMode'] and self.opts['useCache']: - w, pw = 0, self.fragmentAtlas.maxWidth - else: - w, pw = max(chain([(self._maxSpotWidth, self._maxSpotPxWidth)], - self._measureSpotSizes(**kwargs))) - self._maxSpotWidth = w - self._maxSpotPxWidth = pw - self.bounds = [None, None] - - def _measureSpotSizes(self, **kwargs): - """Generate pairs (width, pxWidth) for spots in data""" - styles = zip(*self._style(['size', 'pen'], **kwargs)) - - if self.opts['pxMode']: - for size, pen in styles: - yield 0, size + pen.widthF() - else: - for size, pen in styles: - if pen.isCosmetic(): - yield size, pen.widthF() - else: - yield size + pen.widthF(), 0 - - def getSpotOpts(self, recs, scale=1.0): - warnings.warn( - "This is an internal method that is no longer being used. Will be " - "removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - if recs.ndim == 0: - rec = recs - symbol = rec['symbol'] - if symbol is None: - symbol = self.opts['symbol'] - size = rec['size'] - if size < 0: - size = self.opts['size'] - pen = rec['pen'] - if pen is None: - pen = self.opts['pen'] - brush = rec['brush'] - if brush is None: - brush = self.opts['brush'] - return (symbol, size*scale, fn.mkPen(pen), fn.mkBrush(brush)) - else: - recs = recs.copy() - recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol'] - recs['size'][np.equal(recs['size'], -1)] = self.opts['size'] - recs['size'] *= scale - recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen']) - recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) - return recs - - def measureSpotSizes(self, dataSet): - warnings.warn( - "This is an internal method that is no longer being used. " - "Will be removed in 0.13.", - DeprecationWarning, stacklevel=2 - ) - for size, pen in zip(*self._style(['size', 'pen'], data=dataSet)): - ## keep track of the maximum spot size and pixel size - width = 0 - pxWidth = 0 - if self.opts['pxMode']: - pxWidth = size + pen.widthF() - else: - width = size - if pen.isCosmetic(): - pxWidth += pen.widthF() - else: - width += pen.widthF() - self._maxSpotWidth = max(self._maxSpotWidth, width) - self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) - self.bounds = [None, None] - - def clear(self): - """Remove all spots from the scatter plot""" - #self.clearItems() - self._maxSpotWidth = 0 - self._maxSpotPxWidth = 0 - self.data = np.empty(0, dtype=self.data.dtype) - self.bounds = [None, None] - self.invalidate() - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None: - return self.bounds[ax] - - #self.prepareGeometryChange() - if self.data is None or len(self.data) == 0: - return (None, None) - - if ax == 0: - d = self.data['x'] - d2 = self.data['y'] - elif ax == 1: - d = self.data['y'] - d2 = self.data['x'] - - if orthoRange is not None: - mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) - d = d[mask] - d2 = d2[mask] - - if d.size == 0: - return (None, None) - - if frac >= 1.0: - self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072) - return self.bounds[ax] - elif frac <= 0.0: - raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) - else: - mask = np.isfinite(d) - d = d[mask] - return np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) - - def pixelPadding(self): - return self._maxSpotPxWidth*0.7072 - - def boundingRect(self): - (xmn, xmx) = self.dataBounds(ax=0) - (ymn, ymx) = self.dataBounds(ax=1) - if xmn is None or xmx is None: - xmn = 0 - xmx = 0 - if ymn is None or ymx is None: - ymn = 0 - ymx = 0 - - px = py = 0.0 - pxPad = self.pixelPadding() - if pxPad > 0: - # determine length of pixel in local x, y directions - px, py = self.pixelVectors() - try: - px = 0 if px is None else px.length() - except OverflowError: - px = 0 - try: - py = 0 if py is None else py.length() - except OverflowError: - py = 0 - - # return bounds expanded by pixel size - px *= pxPad - py *= pxPad - return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) - - def viewTransformChanged(self): - self.prepareGeometryChange() - GraphicsObject.viewTransformChanged(self) - self.bounds = [None, None] - if _USE_QRECT: - self.data['targetQRectValid'] = False - - def setExportMode(self, *args, **kwds): - GraphicsObject.setExportMode(self, *args, **kwds) - self.invalidate() - - def mapPointsToDevice(self, pts): - warnings.warn( - "This is an internal method that is no longer being used. " - "Will be removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - # Map point locations to device - tr = self.deviceTransform() - if tr is None: - return None - - pts = fn.transformCoordinates(tr, pts) - pts -= self.data['sourceRect']['w'] / 2 - pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault. - - return pts - - def getViewMask(self, pts): - warnings.warn( - "This is an internal method that is no longer being used. " - "Will be removed in 0.13", - DeprecationWarning, stacklevel=2 - ) - # Return bool mask indicating all points that are within viewbox - # pts is expressed in *device coordiantes* - vb = self.getViewBox() - if vb is None: - return None - viewBounds = vb.mapRectToDevice(vb.boundingRect()) - w = self.data['sourceRect']['w'] / 2 - mask = ((pts[0] + w > viewBounds.left()) & - (pts[0] - w < viewBounds.right()) & - (pts[1] + w > viewBounds.top()) & - (pts[1] - w < viewBounds.bottom())) ## remove out of view points - - mask &= self.data['visible'] - return mask - - @debug.warnOnException ## raising an exception here causes crash - def paint(self, p, *args): - profiler = debug.Profiler() - cmode = self.opts.get('compositionMode', None) - if cmode is not None: - p.setCompositionMode(cmode) - #p.setPen(fn.mkPen('r')) - #p.drawRect(self.boundingRect()) - - if self._exportOpts is not False: - aa = self._exportOpts.get('antialias', True) - scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed - else: - aa = self.opts['antialias'] - scale = 1.0 - - if self.opts['pxMode'] is True: - # Cull points that are outside view - viewMask = self._maskAt(self.getViewBox().viewRect()) - - # Map points using painter's world transform so they are drawn with pixel-valued sizes - pts = np.vstack([self.data['x'], self.data['y']]) - pts = fn.transformCoordinates(p.transform(), pts) - pts = np.clip(pts, -2 ** 30, 2 ** 30) # prevent Qt segmentation fault. - p.resetTransform() - - if self.opts['useCache'] and self._exportOpts is False: - # Map pts to (x, y) coordinates of targetRect - pts -= self.data['sourceRect']['w'] / 2 - - # Draw symbols from pre-rendered atlas - pm = self.fragmentAtlas.pixmap - - if _USE_QRECT: - # Update targetRects if necessary - updateMask = viewMask & (~self.data['targetQRectValid']) - if np.any(updateMask): - x, y = pts[:, updateMask].tolist() - tr = self.data['targetQRect'][updateMask].tolist() - w = self.data['sourceRect']['w'][updateMask].tolist() - list(imap(QtCore.QRectF.setRect, tr, x, y, w, w)) - self.data['targetQRectValid'][updateMask] = True - - profiler('prep') - if QT_LIB == 'PyQt4': - p.drawPixmapFragments( - self.data['targetQRect'][viewMask].tolist(), - self.data['sourceQRect'][viewMask].tolist(), - pm - ) - else: - list(imap(p.drawPixmap, - self.data['targetQRect'][viewMask].tolist(), - repeat(pm), - self.data['sourceQRect'][viewMask].tolist())) - profiler('draw') - else: - x, y = pts[:, viewMask].astype(int) - sr = self.data['sourceRect'][viewMask] - - profiler('prep') - list(imap(p.drawPixmap, - x.tolist(), y.tolist(), repeat(pm), - sr['x'].tolist(), sr['y'].tolist(), sr['w'].tolist(), sr['h'].tolist())) - profiler('draw') - - else: - # render each symbol individually - p.setRenderHint(p.Antialiasing, aa) - - for pt, style in zip( - pts[:, viewMask].T, - zip(*(self._style(['symbol', 'size', 'pen', 'brush'], idx=viewMask, scale=scale))) - ): - p.resetTransform() - p.translate(*pt) - drawSymbol(p, *style) - else: - if self.picture is None: - self.picture = QtGui.QPicture() - p2 = QtGui.QPainter(self.picture) - - for x, y, style in zip( - self.data['x'], - self.data['y'], - zip(*self._style(['symbol', 'size', 'pen', 'brush'], scale=scale)) - ): - p2.resetTransform() - p2.translate(x, y) - drawSymbol(p2, *style) - p2.end() - - p.setRenderHint(p.Antialiasing, aa) - self.picture.play(p) - - def points(self): - m = np.equal(self.data['item'], None) - for i in np.argwhere(m)[:, 0]: - rec = self.data[i] - if rec['item'] is None: - rec['item'] = SpotItem(rec, self, i) - return self.data['item'] - - def pointsAt(self, pos): - return self.points()[self._maskAt(pos)][::-1] - - def _maskAt(self, obj): - """ - Return a boolean mask indicating all points that overlap obj, a QPointF or QRectF. - """ - if isinstance(obj, QtCore.QPointF): - l = r = obj.x() - t = b = obj.y() - elif isinstance(obj, QtCore.QRectF): - l = obj.left() - r = obj.right() - t = obj.top() - b = obj.bottom() - else: - raise TypeError - - if self.opts['pxMode'] and self.opts['useCache']: - w = self.data['sourceRect']['w'] - h = self.data['sourceRect']['h'] - else: - s, = self._style(['size']) - w = h = s - - w = w / 2 - h = h / 2 - - if self.opts['pxMode']: - # determine length of pixel in local x, y directions - px, py = self.pixelVectors() - try: - px = 0 if px is None else px.length() - except OverflowError: - px = 0 - try: - py = 0 if py is None else py.length() - except OverflowError: - py = 0 - w *= px - h *= py - - return (self.data['visible'] - & (self.data['x'] + w > l) - & (self.data['x'] - w < r) - & (self.data['y'] + h > t) - & (self.data['y'] - h < b)) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - pts = self.pointsAt(ev.pos()) - if len(pts) > 0: - self.ptsClicked = pts - ev.accept() - self.sigClicked.emit(self, self.ptsClicked, ev) - else: - #print "no spots" - ev.ignore() - else: - ev.ignore() - - def hoverEvent(self, ev): - if self.opts['hoverable']: - old = self.data['hovered'] - - if ev.exit: - new = np.zeros_like(self.data['hovered']) - else: - new = self._maskAt(ev.pos()) - - if self._hasHoverStyle(): - self.data['sourceRect'][old ^ new] = 0 - self.data['hovered'] = new - self.updateSpots() - - points = self.points()[new][::-1] - - # Show information about hovered points in a tool tip - vb = self.getViewBox() - if vb is not None and self.opts['tip'] is not None: - cutoff = 3 - tip = [self.opts['tip'](x=pt.pos().x(), y=pt.pos().y(), data=pt.data()) - for pt in points[:cutoff]] - if len(points) > cutoff: - tip.append('({} others...)'.format(len(points) - cutoff)) - vb.setToolTip('\n\n'.join(tip)) - - self.sigHovered.emit(self, points, ev) - - def _hasHoverStyle(self): - return any(self.opts['hover' + opt.title()] != _DEFAULT_STYLE[opt] - for opt in ['symbol', 'size', 'pen', 'brush']) - - -class SpotItem(object): - """ - Class referring to individual spots in a scatter plot. - These can be retrieved by calling ScatterPlotItem.points() or - by connecting to the ScatterPlotItem's click signals. - """ - - def __init__(self, data, plot, index): - self._data = data - self._index = index - # SpotItems are kept in plot.data["items"] numpy object array which - # does not support cyclic garbage collection (numpy issue 6581). - # Keeping a strong ref to plot here would leak the cycle - self.__plot_ref = weakref.ref(plot) - - @property - def _plot(self): - return self.__plot_ref() - - def data(self): - """Return the user data associated with this spot.""" - return self._data['data'] - - def index(self): - """Return the index of this point as given in the scatter plot data.""" - return self._index - - def size(self): - """Return the size of this spot. - If the spot has no explicit size set, then return the ScatterPlotItem's default size instead.""" - if self._data['size'] == -1: - return self._plot.opts['size'] - else: - return self._data['size'] - - def pos(self): - return Point(self._data['x'], self._data['y']) - - def viewPos(self): - return self._plot.mapToView(self.pos()) - - def setSize(self, size): - """Set the size of this spot. - If the size is set to -1, then the ScatterPlotItem's default size - will be used instead.""" - self._data['size'] = size - self.updateItem() - - def symbol(self): - """Return the symbol of this spot. - If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead. - """ - symbol = self._data['symbol'] - if symbol is None: - symbol = self._plot.opts['symbol'] - try: - n = int(symbol) - symbol = list(Symbols.keys())[n % len(Symbols)] - except: - pass - return symbol - - def setSymbol(self, symbol): - """Set the symbol for this spot. - If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead.""" - self._data['symbol'] = symbol - self.updateItem() - - def pen(self): - pen = self._data['pen'] - if pen is None: - pen = self._plot.opts['pen'] - return fn.mkPen(pen) - - def setPen(self, *args, **kargs): - """Set the outline pen for this spot""" - self._data['pen'] = _mkPen(*args, **kargs) - self.updateItem() - - def resetPen(self): - """Remove the pen set for this spot; the scatter plot's default pen will be used instead.""" - self._data['pen'] = None ## Note this is NOT the same as calling setPen(None) - self.updateItem() - - def brush(self): - brush = self._data['brush'] - if brush is None: - brush = self._plot.opts['brush'] - return fn.mkBrush(brush) - - def setBrush(self, *args, **kargs): - """Set the fill brush for this spot""" - self._data['brush'] = _mkBrush(*args, **kargs) - self.updateItem() - - def resetBrush(self): - """Remove the brush set for this spot; the scatter plot's default brush will be used instead.""" - self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None) - self.updateItem() - - - def isVisible(self): - return self._data['visible'] - - def setVisible(self, visible): - """Set whether or not this spot is visible.""" - self._data['visible'] = visible - self.updateItem() - - def setData(self, data): - """Set the user-data associated with this spot""" - self._data['data'] = data - - def updateItem(self): - self._data['sourceRect'] = (0, 0, 0, 0) # numpy <=1.13.1 won't let us set this with a single zero - self._plot.updateSpots(self._data.reshape(1)) diff --git a/pyqtgraph/graphicsItems/TargetItem.py b/pyqtgraph/graphicsItems/TargetItem.py deleted file mode 100644 index 782e8a3..0000000 --- a/pyqtgraph/graphicsItems/TargetItem.py +++ /dev/null @@ -1,132 +0,0 @@ -from ..Qt import QtGui, QtCore -import numpy as np -from ..Point import Point -from .. import functions as fn -from .GraphicsObject import GraphicsObject -from .TextItem import TextItem - - -class TargetItem(GraphicsObject): - """Draws a draggable target symbol (circle plus crosshair). - - The size of TargetItem will remain fixed on screen even as the view is zoomed. - Includes an optional text label. - """ - sigDragged = QtCore.Signal(object) - - def __init__(self, movable=True, radii=(5, 10, 10), pen=(255, 255, 0), brush=(0, 0, 255, 100)): - GraphicsObject.__init__(self) - self._bounds = None - self._radii = radii - self._picture = None - self.movable = movable - self.moving = False - self.label = None - self.labelAngle = 0 - self.pen = fn.mkPen(pen) - self.brush = fn.mkBrush(brush) - - def setLabel(self, label): - if label is None: - if self.label is not None: - self.label.scene().removeItem(self.label) - self.label = None - else: - if self.label is None: - self.label = TextItem() - self.label.setParentItem(self) - self.label.setText(label) - self._updateLabel() - - def setLabelAngle(self, angle): - if self.labelAngle != angle: - self.labelAngle = angle - self._updateLabel() - - def boundingRect(self): - if self._picture is None: - self._drawPicture() - return self._bounds - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - return [0, 0] - - def viewTransformChanged(self): - self._picture = None - self.prepareGeometryChange() - self._updateLabel() - - def _updateLabel(self): - if self.label is None: - return - - # find an optimal location for text at the given angle - angle = self.labelAngle * np.pi / 180. - lbr = self.label.boundingRect() - center = lbr.center() - a = abs(np.sin(angle) * lbr.height()*0.5) - b = abs(np.cos(angle) * lbr.width()*0.5) - r = max(self._radii) + 2 + max(a, b) - pos = self.mapFromScene(self.mapToScene(QtCore.QPointF(0, 0)) + r * QtCore.QPointF(np.cos(angle), -np.sin(angle)) - center) - self.label.setPos(pos) - - def paint(self, p, *args): - if self._picture is None: - self._drawPicture() - self._picture.play(p) - - def _drawPicture(self): - self._picture = QtGui.QPicture() - p = QtGui.QPainter(self._picture) - p.setRenderHint(p.Antialiasing) - - # Note: could do this with self.pixelLength, but this is faster. - o = self.mapToScene(QtCore.QPointF(0, 0)) - dx = (self.mapToScene(QtCore.QPointF(1, 0)) - o).x() - dy = (self.mapToScene(QtCore.QPointF(0, 1)) - o).y() - if dx == 0 or dy == 0: - p.end() - self._bounds = QtCore.QRectF() - return - px = abs(1.0 / dx) - py = abs(1.0 / dy) - - r, w, h = self._radii - w = w * px - h = h * py - rx = r * px - ry = r * py - rect = QtCore.QRectF(-rx, -ry, rx*2, ry*2) - p.setPen(self.pen) - p.setBrush(self.brush) - p.drawEllipse(rect) - p.drawLine(Point(-w, 0), Point(w, 0)) - p.drawLine(Point(0, -h), Point(0, h)) - p.end() - - bx = max(w, rx) - by = max(h, ry) - self._bounds = QtCore.QRectF(-bx, -by, bx*2, by*2) - - def mouseDragEvent(self, ev): - if not self.movable: - return - if ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self.moving = True - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.startPosition = self.pos() - ev.accept() - - if not self.moving: - return - - self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) - if ev.isFinish(): - self.moving = False - self.sigDragged.emit(self) - - def hoverEvent(self, ev): - if self.movable: - ev.acceptDrags(QtCore.Qt.LeftButton) - diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py deleted file mode 100644 index 8dcb04f..0000000 --- a/pyqtgraph/graphicsItems/TextItem.py +++ /dev/null @@ -1,216 +0,0 @@ -import numpy as np -from ..Qt import QtCore, QtGui -from ..Point import Point -from .. import functions as fn -from .GraphicsObject import GraphicsObject - - -class TextItem(GraphicsObject): - """ - GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). - """ - def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), - border=None, fill=None, angle=0, rotateAxis=None): - """ - ============== ================================================================================= - **Arguments:** - *text* The text to display - *color* The color of the text (any format accepted by pg.mkColor) - *html* If specified, this overrides both *text* and *color* - *anchor* A QPointF or (x,y) sequence indicating what region of the text box will - be anchored to the item's position. A value of (0,0) sets the upper-left corner - of the text box to be at the position specified by setPos(), while a value of (1,1) - sets the lower-right corner. - *border* A pen to use when drawing the border - *fill* A brush to use when filling within the border - *angle* Angle in degrees to rotate text. Default is 0; text will be displayed upright. - *rotateAxis* If None, then a text angle of 0 always points along the +x axis of the scene. - If a QPointF or (x,y) sequence is given, then it represents a vector direction - in the parent's coordinate system that the 0-degree line will be aligned to. This - Allows text to follow both the position and orientation of its parent while still - discarding any scale and shear factors. - ============== ================================================================================= - - - The effects of the `rotateAxis` and `angle` arguments are added independently. So for example: - - * rotateAxis=None, angle=0 -> normal horizontal text - * rotateAxis=None, angle=90 -> normal vertical text - * rotateAxis=(1, 0), angle=0 -> text aligned with x axis of its parent - * rotateAxis=(0, 1), angle=0 -> text aligned with y axis of its parent - * rotateAxis=(1, 0), angle=90 -> text orthogonal to x axis of its parent - """ - - self.anchor = Point(anchor) - self.rotateAxis = None if rotateAxis is None else Point(rotateAxis) - #self.angle = 0 - GraphicsObject.__init__(self) - self.textItem = QtGui.QGraphicsTextItem() - self.textItem.setParentItem(self) - self._lastTransform = None - self._lastScene = None - self._bounds = QtCore.QRectF() - if html is None: - self.setColor(color) - self.setText(text) - else: - self.setHtml(html) - self.fill = fn.mkBrush(fill) - self.border = fn.mkPen(border) - self.setAngle(angle) - - def setText(self, text, color=None): - """ - Set the text of this item. - - This method sets the plain text of the item; see also setHtml(). - """ - if color is not None: - self.setColor(color) - self.setPlainText(text) - - def setPlainText(self, text): - """ - Set the plain text to be rendered by this item. - - See QtGui.QGraphicsTextItem.setPlainText(). - """ - if text != self.toPlainText(): - self.textItem.setPlainText(text) - self.updateTextPos() - - def toPlainText(self): - return self.textItem.toPlainText() - - def setHtml(self, html): - """ - Set the HTML code to be rendered by this item. - - See QtGui.QGraphicsTextItem.setHtml(). - """ - if self.toHtml() != html: - self.textItem.setHtml(html) - self.updateTextPos() - - def toHtml(self): - return self.textItem.toHtml() - - def setTextWidth(self, *args): - """ - Set the width of the text. - - If the text requires more space than the width limit, then it will be - wrapped into multiple lines. - - See QtGui.QGraphicsTextItem.setTextWidth(). - """ - self.textItem.setTextWidth(*args) - self.updateTextPos() - - def setFont(self, *args): - """ - Set the font for this text. - - See QtGui.QGraphicsTextItem.setFont(). - """ - self.textItem.setFont(*args) - self.updateTextPos() - - def setAngle(self, angle): - """ - Set the angle of the text in degrees. - - This sets the rotation angle of the text as a whole, measured - counter-clockwise from the x axis of the parent. Note that this rotation - angle does not depend on horizontal/vertical scaling of the parent. - """ - self.angle = angle - self.updateTransform(force=True) - - def setAnchor(self, anchor): - self.anchor = Point(anchor) - self.updateTextPos() - - def setColor(self, color): - """ - Set the color for this text. - - See QtGui.QGraphicsItem.setDefaultTextColor(). - """ - self.color = fn.mkColor(color) - self.textItem.setDefaultTextColor(self.color) - - def updateTextPos(self): - # update text position to obey anchor - r = self.textItem.boundingRect() - tl = self.textItem.mapToParent(r.topLeft()) - br = self.textItem.mapToParent(r.bottomRight()) - offset = (br - tl) * self.anchor - self.textItem.setPos(-offset) - - - def boundingRect(self): - return self.textItem.mapRectToParent(self.textItem.boundingRect()) - - def viewTransformChanged(self): - # called whenever view transform has changed. - # Do this here to avoid double-updates when view changes. - self.updateTransform() - - def paint(self, p, *args): - # this is not ideal because it requires the transform to be updated at every draw. - # ideally, we would have a sceneTransformChanged event to react to.. - s = self.scene() - ls = self._lastScene - if s is not ls: - if ls is not None: - ls.sigPrepareForPaint.disconnect(self.updateTransform) - self._lastScene = s - if s is not None: - s.sigPrepareForPaint.connect(self.updateTransform) - self.updateTransform() - p.setTransform(self.sceneTransform()) - - if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush: - p.setPen(self.border) - p.setBrush(self.fill) - p.setRenderHint(p.Antialiasing, True) - p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect())) - - def setVisible(self, v): - GraphicsObject.setVisible(self, v) - if v: - self.updateTransform() - - def updateTransform(self, force=False): - if not self.isVisible(): - return - - # update transform such that this item has the correct orientation - # and scaling relative to the scene, but inherits its position from its - # parent. - # This is similar to setting ItemIgnoresTransformations = True, but - # does not break mouse interaction and collision detection. - p = self.parentItem() - if p is None: - pt = QtGui.QTransform() - else: - pt = p.sceneTransform() - - if not force and pt == self._lastTransform: - return - - t = pt.inverted()[0] - # reset translation - t.setMatrix(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), 0, 0, t.m33()) - - # apply rotation - angle = -self.angle - if self.rotateAxis is not None: - d = pt.map(self.rotateAxis) - pt.map(Point(0, 0)) - a = np.arctan2(d.y(), d.x()) * 180 / np.pi - angle += a - t.rotate(angle) - self.setTransform(t) - self._lastTransform = pt - self.updateTextPos() diff --git a/pyqtgraph/graphicsItems/UIGraphicsItem.py b/pyqtgraph/graphicsItems/UIGraphicsItem.py deleted file mode 100644 index 2f9a3bf..0000000 --- a/pyqtgraph/graphicsItems/UIGraphicsItem.py +++ /dev/null @@ -1,124 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB -import weakref -from .GraphicsObject import GraphicsObject -if QT_LIB.startswith('PyQt'): - from ..Qt import sip - -__all__ = ['UIGraphicsItem'] -class UIGraphicsItem(GraphicsObject): - """ - Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. - The purpose of this class is to allow the creation of GraphicsItems which live inside - a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. - For example: GridItem, InfiniteLine - - The view can be specified on initialization or it can be automatically detected when the item is painted. - - NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged - to respond to changes in the view. - """ - - #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed - - def __init__(self, bounds=None, parent=None): - """ - ============== ============================================================================= - **Arguments:** - bounds QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), - which means the item will have the same bounds as the view. - ============== ============================================================================= - """ - GraphicsObject.__init__(self, parent) - self.setFlag(self.ItemSendsScenePositionChanges) - - if bounds is None: - self._bounds = QtCore.QRectF(0, 0, 1, 1) - else: - self._bounds = bounds - - self._boundingRect = None - self._updateView() - - def paint(self, *args): - ## check for a new view object every time we paint. - #self.updateView() - pass - - def itemChange(self, change, value): - ret = GraphicsObject.itemChange(self, change, value) - - ## workaround for pyqt bug: - ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html - if QT_LIB in ['PyQt4', 'PyQt5'] and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): - ret = sip.cast(ret, QtGui.QGraphicsItem) - - if change == self.ItemScenePositionHasChanged: - self.setNewBounds() - return ret - - #def updateView(self): - ### called to see whether this item has a new view to connect to - - ### check for this item's current viewbox or view widget - #view = self.getViewBox() - #if view is None: - ##print " no view" - #return - - #if self._connectedView is not None and view is self._connectedView(): - ##print " already have view", view - #return - - ### disconnect from previous view - #if self._connectedView is not None: - #cv = self._connectedView() - #if cv is not None: - ##print "disconnect:", self - #cv.sigRangeChanged.disconnect(self.viewRangeChanged) - - ### connect to new view - ##print "connect:", self - #view.sigRangeChanged.connect(self.viewRangeChanged) - #self._connectedView = weakref.ref(view) - #self.setNewBounds() - - def boundingRect(self): - if self._boundingRect is None: - br = self.viewRect() - if br is None: - return QtCore.QRectF() - else: - self._boundingRect = br - return QtCore.QRectF(self._boundingRect) - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - """Called by ViewBox for determining the auto-range bounds. - By default, UIGraphicsItems are excluded from autoRange.""" - return None - - def viewRangeChanged(self): - """Called when the view widget/viewbox is resized/rescaled""" - self.setNewBounds() - self.update() - - def setNewBounds(self): - """Update the item's bounding rect to match the viewport""" - self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. - self.prepareGeometryChange() - - - def setPos(self, *args): - GraphicsObject.setPos(self, *args) - self.setNewBounds() - - def mouseShape(self): - """Return the shape of this item after expanding by 2 pixels""" - shape = self.shape() - ds = self.mapToDevice(shape) - stroker = QtGui.QPainterPathStroker() - stroker.setWidh(2) - ds2 = stroker.createStroke(ds).united(ds) - return self.mapFromDevice(ds2) - - - diff --git a/pyqtgraph/graphicsItems/VTickGroup.py b/pyqtgraph/graphicsItems/VTickGroup.py deleted file mode 100644 index 2b4f256..0000000 --- a/pyqtgraph/graphicsItems/VTickGroup.py +++ /dev/null @@ -1,99 +0,0 @@ -if __name__ == '__main__': - import os, sys - path = os.path.abspath(os.path.dirname(__file__)) - sys.path.insert(0, os.path.join(path, '..', '..')) - -from ..Qt import QtGui, QtCore -from .. import functions as fn -import weakref -from .UIGraphicsItem import UIGraphicsItem - -__all__ = ['VTickGroup'] -class VTickGroup(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` - - Draws a set of tick marks which always occupy the same vertical range of the view, - but have x coordinates relative to the data within the view. - - """ - def __init__(self, xvals=None, yrange=None, pen=None): - """ - ============== =================================================================== - **Arguments:** - xvals A list of x values (in data coordinates) at which to draw ticks. - yrange A list of [low, high] limits for the tick. 0 is the bottom of - the view, 1 is the top. [0.8, 1] would draw ticks in the top - fifth of the view. - pen The pen to use for drawing ticks. Default is grey. Can be specified - as any argument valid for :func:`mkPen` - ============== =================================================================== - """ - if yrange is None: - yrange = [0, 1] - if xvals is None: - xvals = [] - - UIGraphicsItem.__init__(self) - - if pen is None: - pen = (200, 200, 200) - - self.path = QtGui.QGraphicsPathItem() - - self.ticks = [] - self.xvals = [] - self.yrange = [0,1] - self.setPen(pen) - self.setYRange(yrange) - self.setXVals(xvals) - - def setPen(self, *args, **kwargs): - """Set the pen to use for drawing ticks. Can be specified as any arguments valid - for :func:`mkPen`""" - self.pen = fn.mkPen(*args, **kwargs) - - def setXVals(self, vals): - """Set the x values for the ticks. - - ============== ===================================================================== - **Arguments:** - vals A list of x values (in data/plot coordinates) at which to draw ticks. - ============== ===================================================================== - """ - self.xvals = vals - self.rebuildTicks() - #self.valid = False - - def setYRange(self, vals): - """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of - the view, 1 is the top.""" - self.yrange = vals - self.rebuildTicks() - - def dataBounds(self, *args, **kargs): - return None ## item should never affect view autoscaling - - def yRange(self): - return self.yrange - - def rebuildTicks(self): - self.path = QtGui.QPainterPath() - yrange = self.yRange() - for x in self.xvals: - self.path.moveTo(x, 0.) - self.path.lineTo(x, 1.) - - def paint(self, p, *args): - UIGraphicsItem.paint(self, p, *args) - - br = self.boundingRect() - h = br.height() - br.setY(br.y() + self.yrange[0] * h) - br.setHeight((self.yrange[1] - self.yrange[0]) * h) - p.translate(0, br.y()) - p.scale(1.0, br.height()) - p.setPen(self.pen) - p.drawPath(self.path) - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py deleted file mode 100644 index 83d095b..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ /dev/null @@ -1,1714 +0,0 @@ -# -*- coding: utf-8 -*- -import weakref -import sys -from copy import deepcopy -import numpy as np -from ...Qt import QtGui, QtCore -from ...python2_3 import basestring -from ...Point import Point -from ... import functions as fn -from .. ItemGroup import ItemGroup -from .. GraphicsWidget import GraphicsWidget -from ... import debug as debug -from ... import getConfigOption -from ...Qt import isQObjectAlive - -__all__ = ['ViewBox'] - - -class WeakList(object): - - def __init__(self): - self._items = [] - - def append(self, obj): - #Add backwards to iterate backwards (to make iterating more efficient on removal). - self._items.insert(0, weakref.ref(obj)) - - def __iter__(self): - i = len(self._items)-1 - while i >= 0: - ref = self._items[i] - d = ref() - if d is None: - del self._items[i] - else: - yield d - i -= 1 - - -class ChildGroup(ItemGroup): - - def __init__(self, parent): - ItemGroup.__init__(self, parent) - - # Used as callback to inform ViewBox when items are added/removed from - # the group. - # Note 1: We would prefer to override itemChange directly on the - # ViewBox, but this causes crashes on PySide. - # Note 2: We might also like to use a signal rather than this callback - # mechanism, but this causes a different PySide crash. - self.itemsChangedListeners = WeakList() - - # excempt from telling view when transform changes - self._GraphicsObject__inform_view_on_change = False - - def itemChange(self, change, value): - ret = ItemGroup.itemChange(self, change, value) - if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange: - try: - itemsChangedListeners = self.itemsChangedListeners - except AttributeError: - # It's possible that the attribute was already collected when the itemChange happened - # (if it was triggered during the gc of the object). - pass - else: - for listener in itemsChangedListeners: - listener.itemsChanged() - return ret - - -class ViewBox(GraphicsWidget): - """ - **Bases:** :class:`GraphicsWidget ` - - Box that allows internal scaling/panning of children by mouse drag. - This class is usually created automatically as part of a :class:`PlotItem ` or :class:`Canvas ` or with :func:`GraphicsLayout.addViewBox() `. - - Features: - - * Scaling contents by mouse or auto-scale when contents change - * View linking--multiple views display the same data ranges - * Configurable by context menu - * Item coordinate mapping methods - - """ - - sigYRangeChanged = QtCore.Signal(object, object) - sigXRangeChanged = QtCore.Signal(object, object) - sigRangeChangedManually = QtCore.Signal(object) - sigRangeChanged = QtCore.Signal(object, object) - sigStateChanged = QtCore.Signal(object) - sigTransformChanged = QtCore.Signal(object) - sigResized = QtCore.Signal(object) - - ## mouse modes - PanMode = 3 - RectMode = 1 - - ## axes - XAxis = 0 - YAxis = 1 - XYAxes = 2 - - ## for linking views together - NamedViews = weakref.WeakValueDictionary() # name: ViewBox - AllViews = weakref.WeakKeyDictionary() # ViewBox: None - - def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None, invertX=False): - """ - ============== ============================================================= - **Arguments:** - *parent* (QGraphicsWidget) Optional parent widget - *border* (QPen) Do draw a border around the view, give any - single argument accepted by :func:`mkPen ` - *lockAspect* (False or float) The aspect ratio to lock the view - coorinates to. (or False to allow the ratio to change) - *enableMouse* (bool) Whether mouse can be used to scale/pan the view - *invertY* (bool) See :func:`invertY ` - *invertX* (bool) See :func:`invertX ` - *enableMenu* (bool) Whether to display a context menu when - right-clicking on the ViewBox background. - *name* (str) Used to register this ViewBox so that it appears - in the "Link axis" dropdown inside other ViewBox - context menus. This allows the user to manually link - the axes of any other view to this one. - ============== ============================================================= - """ - - GraphicsWidget.__init__(self, parent) - self.name = None - self.linksBlocked = False - self.addedItems = [] - self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred - self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. - - self._lastScene = None ## stores reference to the last known scene this view was a part of. - - self.state = { - - ## separating targetRange and viewRange allows the view to be resized - ## while keeping all previously viewed contents visible - 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] - 'viewRange': [[0,1], [0,1]], ## actual range viewed - - 'yInverted': invertY, - 'xInverted': invertX, - 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. - 'autoRange': [True, True], ## False if auto range is disabled, - ## otherwise float gives the fraction of data that is visible - 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled - 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot - 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view) - ## a name string indicates that the view *should* link to another, but no view with that name exists yet. - - 'mouseEnabled': [enableMouse, enableMouse], - 'mouseMode': ViewBox.PanMode if getConfigOption('leftButtonPan') else ViewBox.RectMode, - 'enableMenu': enableMenu, - 'wheelScaleFactor': -1.0 / 8.0, - - 'background': None, - - # Limits - 'limits': { - 'xLimits': [None, None], # Maximum and minimum visible X values - 'yLimits': [None, None], # Maximum and minimum visible Y values - 'xRange': [None, None], # Maximum and minimum X range - 'yRange': [None, None], # Maximum and minimum Y range - } - - } - self._updatingRange = False ## Used to break recursive loops. See updateAutoRange. - self._itemBoundsCache = weakref.WeakKeyDictionary() - - self.locateGroup = None ## items displayed when using ViewBox.locate(item) - - self.setFlag(self.ItemClipsChildrenToShape) - self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses - - ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. - ## this is a workaround for a Qt + OpenGL bug that causes improper clipping - ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 - self.childGroup = ChildGroup(self) - self.childGroup.itemsChangedListeners.append(self) - - self.background = QtGui.QGraphicsRectItem(self.rect()) - self.background.setParentItem(self) - self.background.setZValue(-1e6) - self.background.setPen(fn.mkPen(None)) - self.updateBackground() - - self.border = fn.mkPen(border) - - self.borderRect = QtGui.QGraphicsRectItem(self.rect()) - self.borderRect.setParentItem(self) - self.borderRect.setZValue(1e3) - self.borderRect.setPen(self.border) - - ## Make scale box that is shown when dragging on the view - self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) - self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) - self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) - self.rbScaleBox.setZValue(1e9) - self.rbScaleBox.hide() - self.addItem(self.rbScaleBox, ignoreBounds=True) - - ## show target rect for debugging - self.target = QtGui.QGraphicsRectItem(0, 0, 1, 1) - self.target.setPen(fn.mkPen('r')) - self.target.setParentItem(self) - self.target.hide() - - self.axHistory = [] # maintain a history of zoom locations - self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" - - self.setZValue(-100) - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - - self.setAspectLocked(lockAspect) - - if enableMenu: - self.menu = ViewBoxMenu(self) - else: - self.menu = None - - self.register(name) - if name is None: - self.updateViewLists() - - def getAspectRatio(self): - '''return the current aspect ratio''' - rect = self.rect() - vr = self.viewRect() - if rect.height() == 0 or vr.width() == 0 or vr.height() == 0: - currentRatio = 1.0 - else: - currentRatio = (rect.width()/float(rect.height())) / ( - vr.width()/vr.height()) - return currentRatio - - def register(self, name): - """ - Add this ViewBox to the registered list of views. - - This allows users to manually link the axes of any other ViewBox to - this one. The specified *name* will appear in the drop-down lists for - axis linking in the context menus of all other views. - - The same can be accomplished by initializing the ViewBox with the *name* attribute. - """ - ViewBox.AllViews[self] = None - if self.name is not None: - del ViewBox.NamedViews[self.name] - self.name = name - if name is not None: - ViewBox.NamedViews[name] = self - ViewBox.updateAllViewLists() - sid = id(self) - self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) - - def unregister(self): - """ - Remove this ViewBox from the list of linkable views. (see :func:`register() `) - """ - del ViewBox.AllViews[self] - if self.name is not None: - del ViewBox.NamedViews[self.name] - - def close(self): - self.clear() - self.unregister() - - def implements(self, interface): - return interface == 'ViewBox' - - # removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 - #def itemChange(self, change, value): - ## Note: Calling QWidget.itemChange causes segv in python 3 + PyQt - ##ret = QtGui.QGraphicsItem.itemChange(self, change, value) - #ret = GraphicsWidget.itemChange(self, change, value) - #if change == self.ItemSceneChange: - #scene = self.scene() - #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): - #scene.sigPrepareForPaint.disconnect(self.prepareForPaint) - #elif change == self.ItemSceneHasChanged: - #scene = self.scene() - #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): - #scene.sigPrepareForPaint.connect(self.prepareForPaint) - #return ret - - def prepareForPaint(self): - #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) - # don't check whether auto range is enabled here--only check when setting dirty flag. - if self._autoRangeNeedsUpdate: # and autoRangeEnabled: - self.updateAutoRange() - self.updateMatrix() - - def getState(self, copy=True): - """Return the current state of the ViewBox. - Linked views are always converted to view names in the returned state.""" - state = self.state.copy() - views = [] - for v in state['linkedViews']: - if isinstance(v, weakref.ref): - v = v() - if v is None or isinstance(v, basestring): - views.append(v) - else: - views.append(v.name) - state['linkedViews'] = views - if copy: - return deepcopy(state) - else: - return state - - def setState(self, state): - """Restore the state of this ViewBox. - (see also getState)""" - state = state.copy() - self.setXLink(state['linkedViews'][0]) - self.setYLink(state['linkedViews'][1]) - del state['linkedViews'] - - self.state.update(state) - - self._applyMenuEnabled() - self.updateViewRange() - self.sigStateChanged.emit(self) - - def setBackgroundColor(self, color): - """ - Set the background color of the ViewBox. - - If color is None, then no background will be drawn. - - Added in version 0.9.9 - """ - self.background.setVisible(color is not None) - self.state['background'] = color - self.updateBackground() - - def setMouseMode(self, mode): - """ - Set the mouse interaction mode. *mode* must be either ViewBox.PanMode or ViewBox.RectMode. - In PanMode, the left mouse button pans the view and the right button scales. - In RectMode, the left button draws a rectangle which updates the visible region (this mode is more suitable for single-button mice) - """ - if mode not in [ViewBox.PanMode, ViewBox.RectMode]: - raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") - self.state['mouseMode'] = mode - self.sigStateChanged.emit(self) - - def setLeftButtonAction(self, mode='rect'): ## for backward compatibility - if mode.lower() == 'rect': - self.setMouseMode(ViewBox.RectMode) - elif mode.lower() == 'pan': - self.setMouseMode(ViewBox.PanMode) - else: - raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) - - def innerSceneItem(self): - return self.childGroup - - def setMouseEnabled(self, x=None, y=None): - """ - Set whether each axis is enabled for mouse interaction. *x*, *y* arguments must be True or False. - This allows the user to pan/scale one axis of the view while leaving the other axis unchanged. - """ - if x is not None: - self.state['mouseEnabled'][0] = x - if y is not None: - self.state['mouseEnabled'][1] = y - self.sigStateChanged.emit(self) - - def mouseEnabled(self): - return self.state['mouseEnabled'][:] - - def setMenuEnabled(self, enableMenu=True): - self.state['enableMenu'] = enableMenu - self._applyMenuEnabled() - self.sigStateChanged.emit(self) - - def menuEnabled(self): - return self.state.get('enableMenu', True) - - def _applyMenuEnabled(self): - enableMenu = self.state.get("enableMenu", True) - if enableMenu and self.menu is None: - self.menu = ViewBoxMenu(self) - self.updateViewLists() - elif not enableMenu and self.menu is not None: - self.menu.setParent(None) - self.menu = None - - def addItem(self, item, ignoreBounds=False): - """ - Add a QGraphicsItem to this view. The view will include this item when determining how to set its range - automatically unless *ignoreBounds* is True. - """ - if item.zValue() < self.zValue(): - item.setZValue(self.zValue()+1) - - scene = self.scene() - if scene is not None and scene is not item.scene(): - scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 - item.setParentItem(self.childGroup) - - if not ignoreBounds: - self.addedItems.append(item) - self.updateAutoRange() - - def removeItem(self, item): - """Remove an item from this view.""" - try: - self.addedItems.remove(item) - except: - pass - - scene = self.scene() - if scene is not None: - scene.removeItem(item) - item.setParentItem(None) - - self.updateAutoRange() - - def clear(self): - for i in self.addedItems[:]: - self.removeItem(i) - for ch in self.childGroup.childItems(): - ch.setParentItem(None) - - def resizeEvent(self, ev): - if ev.oldSize() != ev.newSize(): - self._matrixNeedsUpdate = True - - self.linkedXChanged() - self.linkedYChanged() - - self.updateAutoRange() - self.updateViewRange() - - # self._matrixNeedsUpdate = True - - self.background.setRect(self.rect()) - self.borderRect.setRect(self.rect()) - - self.sigStateChanged.emit(self) - self.sigResized.emit(self) - - - def viewRange(self): - """Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]""" - return [x[:] for x in self.state['viewRange']] ## return copy - - def viewRect(self): - """Return a QRectF bounding the region visible within the ViewBox""" - try: - vr0 = self.state['viewRange'][0] - vr1 = self.state['viewRange'][1] - return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) - except: - print("make qrectf failed:", self.state['viewRange']) - raise - - def targetRange(self): - return [x[:] for x in self.state['targetRange']] ## return copy - - def targetRect(self): - """ - Return the region which has been requested to be visible. - (this is not necessarily the same as the region that is *actually* visible-- - resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) - """ - try: - tr0 = self.state['targetRange'][0] - tr1 = self.state['targetRange'][1] - return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) - except: - print("make qrectf failed:", self.state['targetRange']) - raise - - def _resetTarget(self): - # Reset target range to exactly match current view range. - # This is used during mouse interaction to prevent unpredictable - # behavior (because the user is unaware of targetRange). - if self.state['aspectLocked'] is False: # (interferes with aspect locking) - self.state['targetRange'] = [self.state['viewRange'][0][:], self.state['viewRange'][1][:]] - - def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): - """ - Set the visible range of the ViewBox. - Must specify at least one of *rect*, *xRange*, or *yRange*. - - ================== ===================================================================== - **Arguments:** - *rect* (QRectF) The full range that should be visible in the view box. - *xRange* (min,max) The range that should be visible along the x-axis. - *yRange* (min,max) The range that should be visible along the y-axis. - *padding* (float) Expand the view by a fraction of the requested range. - By default, this value is set between 0.02 and 0.1 depending on - the size of the ViewBox. - *update* (bool) If True, update the range of the ViewBox immediately. - Otherwise, the update is deferred until before the next render. - *disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left - unchanged. - ================== ===================================================================== - - """ - - changes = {} # axes - setRequested = [False, False] - - if rect is not None: - changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} - setRequested = [True, True] - if xRange is not None: - changes[0] = xRange - setRequested[0] = True - if yRange is not None: - changes[1] = yRange - setRequested[1] = True - - if len(changes) == 0: - print(rect) - raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect))) - - # Update axes one at a time - changed = [False, False] - - # Disable auto-range for each axis that was requested to be set - if disableAutoRange: - xOff = False if setRequested[0] else None - yOff = False if setRequested[1] else None - self.enableAutoRange(x=xOff, y=yOff) - changed.append(True) - - limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits']) - minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] - maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]] - - for ax, range in changes.items(): - mn = min(range) - mx = max(range) - - # If we requested 0 range, try to preserve previous scale. - # Otherwise just pick an arbitrary scale. - if mn == mx: - dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] - if dy == 0: - dy = 1 - mn -= dy*0.5 - mx += dy*0.5 - xpad = 0.0 - - # Make sure no nan/inf get through - if not all(np.isfinite([mn, mx])): - raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) - - # Apply padding - if padding is None: - xpad = self.suggestPadding(ax) - else: - xpad = padding - p = (mx-mn) * xpad - mn -= p - mx += p - - # max range cannot be larger than bounds, if they are given - if limits[ax][0] is not None and limits[ax][1] is not None: - if maxRng[ax] is not None: - maxRng[ax] = min(maxRng[ax], limits[ax][1] - limits[ax][0]) - else: - maxRng[ax] = limits[ax][1] - limits[ax][0] - - # If we have limits, we will have at least a max range as well - if maxRng[ax] is not None or minRng[ax] is not None: - diff = mx - mn - if maxRng[ax] is not None and diff > maxRng[ax]: - delta = maxRng[ax] - diff - elif minRng[ax] is not None and diff < minRng[ax]: - delta = minRng[ax] - diff - else: - delta = 0 - - mn -= delta / 2. - mx += delta / 2. - - # Make sure our requested area is within limits, if any - if limits[ax][0] is not None or limits[ax][1] is not None: - lmn, lmx = limits[ax] - if lmn is not None and mn < lmn: - delta = lmn - mn # Shift the requested view to match our lower limit - mn = lmn - mx += delta - elif lmx is not None and mx > lmx: - delta = lmx - mx - mx = lmx - mn += delta - - # Set target range - if self.state['targetRange'][ax] != [mn, mx]: - self.state['targetRange'][ax] = [mn, mx] - changed[ax] = True - - # Update viewRange to match targetRange as closely as possible while - # accounting for aspect ratio constraint - lockX, lockY = setRequested - if lockX and lockY: - lockX = False - lockY = False - self.updateViewRange(lockX, lockY) - - # If nothing has changed, we are done. - if any(changed): - # Update target rect for debugging - if self.target.isVisible(): - self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) - - # If ortho axes have auto-visible-only, update them now - # Note that aspect ratio constraints and auto-visible probably do not work together.. - if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): - self._autoRangeNeedsUpdate = True - elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): - self._autoRangeNeedsUpdate = True - - self.sigStateChanged.emit(self) - - def setYRange(self, min, max, padding=None, update=True): - """ - Set the visible Y range of the view to [*min*, *max*]. - The *padding* argument causes the range to be set larger by the fraction specified. - (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) - """ - self.setRange(yRange=[min, max], update=update, padding=padding) - - def setXRange(self, min, max, padding=None, update=True): - """ - Set the visible X range of the view to [*min*, *max*]. - The *padding* argument causes the range to be set larger by the fraction specified. - (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) - """ - self.setRange(xRange=[min, max], update=update, padding=padding) - - def autoRange(self, padding=None, items=None, item=None): - """ - Set the range of the view box to make all children visible. - Note that this is not the same as enableAutoRange, which causes the view to - automatically auto-range whenever its contents are changed. - - ============== ============================================================ - **Arguments:** - padding The fraction of the total data range to add on to the final - visible range. By default, this value is set between 0.02 - and 0.1 depending on the size of the ViewBox. - items If specified, this is a list of items to consider when - determining the visible range. - ============== ============================================================ - """ - if item is None: - bounds = self.childrenBoundingRect(items=items) - else: - bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() - - if bounds is not None: - self.setRange(bounds, padding=padding) - - def suggestPadding(self, axis): - l = self.width() if axis==0 else self.height() - if l > 0: - padding = np.clip(1./(l**0.5), 0.02, 0.1) - else: - padding = 0.02 - return padding - - def setLimits(self, **kwds): - """ - Set limits that constrain the possible view ranges. - - **Panning limits**. The following arguments define the region within the - viewbox coordinate system that may be accessed by panning the view. - - =========== ============================================================ - xMin Minimum allowed x-axis value - xMax Maximum allowed x-axis value - yMin Minimum allowed y-axis value - yMax Maximum allowed y-axis value - =========== ============================================================ - - **Scaling limits**. These arguments prevent the view being zoomed in or - out too far. - - =========== ============================================================ - minXRange Minimum allowed left-to-right span across the view. - maxXRange Maximum allowed left-to-right span across the view. - minYRange Minimum allowed top-to-bottom span across the view. - maxYRange Maximum allowed top-to-bottom span across the view. - =========== ============================================================ - - Added in version 0.9.9 - """ - update = False - allowed = ['xMin', 'xMax', 'yMin', 'yMax', 'minXRange', 'maxXRange', 'minYRange', 'maxYRange'] - for kwd in kwds: - if kwd not in allowed: - raise ValueError("Invalid keyword argument '%s'." % kwd) - for axis in [0,1]: - for mnmx in [0,1]: - kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx] - lname = ['xLimits', 'yLimits'][axis] - if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: - self.state['limits'][lname][mnmx] = kwds[kwd] - update = True - kwd = [['minXRange', 'maxXRange'], ['minYRange', 'maxYRange']][axis][mnmx] - lname = ['xRange', 'yRange'][axis] - if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: - self.state['limits'][lname][mnmx] = kwds[kwd] - update = True - - if update: - self.updateViewRange() - - def scaleBy(self, s=None, center=None, x=None, y=None): - """ - Scale by *s* around given center point (or center of view). - *s* may be a Point or tuple (x, y). - - Optionally, x or y may be specified individually. This allows the other - axis to be left unaffected (note that using a scale factor of 1.0 may - cause slight changes due to floating-point error). - """ - if s is not None: - x, y = s[0], s[1] - - affect = [x is not None, y is not None] - if not any(affect): - return - - scale = Point([1.0 if x is None else x, 1.0 if y is None else y]) - - if self.state['aspectLocked'] is not False: - scale[0] = scale[1] - - vr = self.targetRect() - if center is None: - center = Point(vr.center()) - else: - center = Point(center) - - tl = center + (vr.topLeft()-center) * scale - br = center + (vr.bottomRight()-center) * scale - - if not affect[0]: - self.setYRange(tl.y(), br.y(), padding=0) - elif not affect[1]: - self.setXRange(tl.x(), br.x(), padding=0) - else: - self.setRange(QtCore.QRectF(tl, br), padding=0) - - def translateBy(self, t=None, x=None, y=None): - """ - Translate the view by *t*, which may be a Point or tuple (x, y). - - Alternately, x or y may be specified independently, leaving the other - axis unchanged (note that using a translation of 0 may still cause - small changes due to floating-point error). - """ - vr = self.targetRect() - if t is not None: - t = Point(t) - self.setRange(vr.translated(t), padding=0) - else: - if x is not None: - x = vr.left()+x, vr.right()+x - if y is not None: - y = vr.top()+y, vr.bottom()+y - if x is not None or y is not None: - self.setRange(xRange=x, yRange=y, padding=0) - - def enableAutoRange(self, axis=None, enable=True, x=None, y=None): - """ - Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both - (if *axis* is omitted, both axes will be changed). - When enabled, the axis will automatically rescale when items are added/removed or change their shape. - The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should - be visible (this only works with items implementing a dataRange method, such as PlotDataItem). - """ - # support simpler interface: - if x is not None or y is not None: - if x is not None: - self.enableAutoRange(ViewBox.XAxis, x) - if y is not None: - self.enableAutoRange(ViewBox.YAxis, y) - return - - if enable is True: - enable = 1.0 - - if axis is None: - axis = ViewBox.XYAxes - - needAutoRangeUpdate = False - - if axis == ViewBox.XYAxes or axis == 'xy': - axes = [0, 1] - elif axis == ViewBox.XAxis or axis == 'x': - axes = [0] - elif axis == ViewBox.YAxis or axis == 'y': - axes = [1] - else: - raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') - - for ax in axes: - if self.state['autoRange'][ax] != enable: - # If we are disabling, do one last auto-range to make sure that - # previously scheduled auto-range changes are enacted - if enable is False and self._autoRangeNeedsUpdate: - self.updateAutoRange() - - self.state['autoRange'][ax] = enable - self._autoRangeNeedsUpdate |= (enable is not False) - self.update() - - self.sigStateChanged.emit(self) - - def disableAutoRange(self, axis=None): - """Disables auto-range. (See enableAutoRange)""" - self.enableAutoRange(axis, enable=False) - - def autoRangeEnabled(self): - return self.state['autoRange'][:] - - def setAutoPan(self, x=None, y=None): - """Set whether automatic range will only pan (not scale) the view. - """ - if x is not None: - self.state['autoPan'][0] = x - if y is not None: - self.state['autoPan'][1] = y - if None not in [x,y]: - self.updateAutoRange() - - def setAutoVisible(self, x=None, y=None): - """Set whether automatic range uses only visible data when determining - the range to show. - """ - if x is not None: - self.state['autoVisibleOnly'][0] = x - if x is True: - self.state['autoVisibleOnly'][1] = False - if y is not None: - self.state['autoVisibleOnly'][1] = y - if y is True: - self.state['autoVisibleOnly'][0] = False - - if x is not None or y is not None: - self.updateAutoRange() - - def updateAutoRange(self): - ## Break recursive loops when auto-ranging. - ## This is needed because some items change their size in response - ## to a view change. - if self._updatingRange: - return - self._updatingRange = True - try: - targetRect = self.viewRange() - if not any(self.state['autoRange']): - return - - fractionVisible = self.state['autoRange'][:] - for i in [0,1]: - if type(fractionVisible[i]) is bool: - fractionVisible[i] = 1.0 - - childRange = None - - order = [0,1] - if self.state['autoVisibleOnly'][0] is True: - order = [1,0] - - args = {} - for ax in order: - if self.state['autoRange'][ax] is False: - continue - if self.state['autoVisibleOnly'][ax]: - oRange = [None, None] - oRange[ax] = targetRect[1-ax] - childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange) - else: - if childRange is None: - childRange = self.childrenBounds(frac=fractionVisible) - ## Make corrections to range - xr = childRange[ax] - if xr is not None: - if self.state['autoPan'][ax]: - x = sum(xr) * 0.5 - w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. - childRange[ax] = [x-w2, x+w2] - else: - padding = self.suggestPadding(ax) - wp = (xr[1] - xr[0]) * padding - childRange[ax][0] -= wp - childRange[ax][1] += wp - targetRect[ax] = childRange[ax] - args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] - - # check for and ignore bad ranges - for k in ['xRange', 'yRange']: - if k in args: - if not np.all(np.isfinite(args[k])): - r = args.pop(k) - #print("Warning: %s is invalid: %s" % (k, str(r)) - - if len(args) == 0: - return - args['padding'] = 0.0 - args['disableAutoRange'] = False - self.setRange(**args) - finally: - self._autoRangeNeedsUpdate = False - self._updatingRange = False - - def setXLink(self, view): - """Link this view's X axis to another view. (see LinkView)""" - self.linkView(self.XAxis, view) - - def setYLink(self, view): - """Link this view's Y axis to another view. (see LinkView)""" - self.linkView(self.YAxis, view) - - def linkView(self, axis, view): - """ - Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. - If view is None, the axis is left unlinked. - """ - if isinstance(view, basestring): - if view == '': - view = None - else: - view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible - - if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): - view = view.getViewBox() - - ## used to connect/disconnect signals between a pair of views - if axis == ViewBox.XAxis: - signal = 'sigXRangeChanged' - slot = self.linkedXChanged - else: - signal = 'sigYRangeChanged' - slot = self.linkedYChanged - - - oldLink = self.linkedView(axis) - if oldLink is not None: - try: - getattr(oldLink, signal).disconnect(slot) - oldLink.sigResized.disconnect(slot) - except (TypeError, RuntimeError): - ## This can occur if the view has been deleted already - pass - - - if view is None or isinstance(view, basestring): - self.state['linkedViews'][axis] = view - else: - self.state['linkedViews'][axis] = weakref.ref(view) - getattr(view, signal).connect(slot) - view.sigResized.connect(slot) - if view.autoRangeEnabled()[axis] is not False: - self.enableAutoRange(axis, False) - slot() - else: - if self.autoRangeEnabled()[axis] is False: - slot() - - - self.sigStateChanged.emit(self) - - def blockLink(self, b): - self.linksBlocked = b ## prevents recursive plot-change propagation - - def linkedXChanged(self): - ## called when x range of linked view has changed - view = self.linkedView(0) - self.linkedViewChanged(view, ViewBox.XAxis) - - def linkedYChanged(self): - ## called when y range of linked view has changed - view = self.linkedView(1) - self.linkedViewChanged(view, ViewBox.YAxis) - - def linkedView(self, ax): - ## Return the linked view for axis *ax*. - ## this method _always_ returns either a ViewBox or None. - v = self.state['linkedViews'][ax] - if v is None or isinstance(v, basestring): - return None - else: - return v() ## dereference weakref pointer. If the reference is dead, this returns None - - def linkedViewChanged(self, view, axis): - if self.linksBlocked or view is None: - return - - #print self.name, "ViewBox.linkedViewChanged", axis, view.viewRange()[axis] - vr = view.viewRect() - vg = view.screenGeometry() - sg = self.screenGeometry() - if vg is None or sg is None: - return - - view.blockLink(True) - try: - if axis == ViewBox.XAxis: - overlap = min(sg.right(), vg.right()) - max(sg.left(), vg.left()) - if overlap < min(vg.width()/3, sg.width()/3): ## if less than 1/3 of views overlap, - ## then just replicate the view - x1 = vr.left() - x2 = vr.right() - else: ## views overlap; line them up - upp = float(vr.width()) / vg.width() - if self.xInverted(): - x1 = vr.left() + (sg.right()-vg.right()) * upp - else: - x1 = vr.left() + (sg.x()-vg.x()) * upp - x2 = x1 + sg.width() * upp - self.enableAutoRange(ViewBox.XAxis, False) - self.setXRange(x1, x2, padding=0) - else: - overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) - if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, - ## then just replicate the view - y1 = vr.top() - y2 = vr.bottom() - else: ## views overlap; line them up - upp = float(vr.height()) / vg.height() - if self.yInverted(): - y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp - else: - y2 = vr.bottom() + (sg.top()-vg.top()) * upp - y1 = y2 - sg.height() * upp - self.enableAutoRange(ViewBox.YAxis, False) - self.setYRange(y1, y2, padding=0) - finally: - view.blockLink(False) - - def screenGeometry(self): - """return the screen geometry of the viewbox""" - v = self.getViewWidget() - if v is None: - return None - b = self.sceneBoundingRect() - wr = v.mapFromScene(b).boundingRect() - pos = v.mapToGlobal(v.pos()) - wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) - return wr - - def itemsChanged(self): - ## called when items are added/removed from self.childGroup - self.updateAutoRange() - - def itemBoundsChanged(self, item): - self._itemBoundsCache.pop(item, None) - if (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False): - self._autoRangeNeedsUpdate = True - self.update() - - def _invertAxis(self, ax, inv): - key = 'xy'[ax] + 'Inverted' - if self.state[key] == inv: - return - - self.state[key] = inv - self._matrixNeedsUpdate = True # updateViewRange won't detect this for us - self.updateViewRange() - self.update() - self.sigStateChanged.emit(self) - if ax: - self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][ax])) - else: - self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][ax])) - - def invertY(self, b=True): - """ - By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. - """ - self._invertAxis(1, b) - - def yInverted(self): - return self.state['yInverted'] - - def invertX(self, b=True): - """ - By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis. - """ - self._invertAxis(0, b) - - def xInverted(self): - return self.state['xInverted'] - - def setBorder(self, *args, **kwds): - """ - Set the pen used to draw border around the view - - If border is None, then no border will be drawn. - - Added in version 0.9.10 - - See :func:`mkPen ` for arguments. - """ - self.border = fn.mkPen(*args, **kwds) - self.borderRect.setPen(self.border) - - def setAspectLocked(self, lock=True, ratio=1): - """ - If the aspect ratio is locked, view scaling must always preserve the aspect ratio. - By default, the ratio is set to 1; x and y both have the same scaling. - This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio. - """ - - if not lock: - if self.state['aspectLocked'] == False: - return - self.state['aspectLocked'] = False - else: - currentRatio = self.getAspectRatio() - if ratio is None: - ratio = currentRatio - if self.state['aspectLocked'] == ratio: # nothing to change - return - self.state['aspectLocked'] = ratio - if ratio != currentRatio: ## If this would change the current range, do that now - self.updateViewRange() - - self.updateAutoRange() - self.updateViewRange() - self.sigStateChanged.emit(self) - - def childTransform(self): - """ - Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. - (This maps from inside the viewbox to outside) - """ - self.updateMatrix() - m = self.childGroup.transform() - return m - - def mapToView(self, obj): - """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" - self.updateMatrix() - m = fn.invertQTransform(self.childTransform()) - return m.map(obj) - - def mapFromView(self, obj): - """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" - self.updateMatrix() - m = self.childTransform() - return m.map(obj) - - def mapSceneToView(self, obj): - """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" - self.updateMatrix() - return self.mapToView(self.mapFromScene(obj)) - - def mapViewToScene(self, obj): - """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" - self.updateMatrix() - return self.mapToScene(self.mapFromView(obj)) - - def mapFromItemToView(self, item, obj): - """Maps *obj* from the local coordinate system of *item* to the view coordinates""" - self.updateMatrix() - return self.childGroup.mapFromItem(item, obj) - - def mapFromViewToItem(self, item, obj): - """Maps *obj* from view coordinates to the local coordinate system of *item*.""" - self.updateMatrix() - return self.childGroup.mapToItem(item, obj) - - def mapViewToDevice(self, obj): - self.updateMatrix() - return self.mapToDevice(self.mapFromView(obj)) - - def mapDeviceToView(self, obj): - self.updateMatrix() - return self.mapToView(self.mapFromDevice(obj)) - - def viewPixelSize(self): - """Return the (width, height) of a screen pixel in view coordinates.""" - o = self.mapToView(Point(0,0)) - px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] - return (px.length(), py.length()) - - def itemBoundingRect(self, item): - """Return the bounding rect of the item in view coordinates""" - return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() - - def wheelEvent(self, ev, axis=None): - if axis in (0, 1): - mask = [False, False] - mask[axis] = self.state['mouseEnabled'][axis] - else: - mask = self.state['mouseEnabled'][:] - s = 1.02 ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor - s = [(None if m is False else s) for m in mask] - center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) - - self._resetTarget() - self.scaleBy(s, center) - ev.accept() - self.sigRangeChangedManually.emit(mask) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): - ev.accept() - self.raiseContextMenu(ev) - - def raiseContextMenu(self, ev): - menu = self.getMenu(ev) - if menu is not None: - self.scene().addParentContextMenus(self, menu, ev) - menu.popup(ev.screenPos().toPoint()) - - def getMenu(self, ev): - return self.menu - - def getContextMenus(self, event): - return self.menu.actions() if self.menuEnabled() else [] - - def mouseDragEvent(self, ev, axis=None): - ## if axis is specified, event will only affect that axis. - ev.accept() ## we accept all buttons - - pos = ev.pos() - lastPos = ev.lastPos() - dif = pos - lastPos - dif = dif * -1 - - ## Ignore axes if mouse is disabled - mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float64) - mask = mouseEnabled.copy() - if axis is not None: - mask[1-axis] = 0.0 - - ## Scale or translate based on mouse button - if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MiddleButton): - if self.state['mouseMode'] == ViewBox.RectMode and axis is None: - if ev.isFinish(): ## This is the final move in the drag; change the view scale now - #print "finish" - self.rbScaleBox.hide() - ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) - ax = self.childGroup.mapRectFromParent(ax) - self.showAxRect(ax) - self.axHistoryPointer += 1 - self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] - else: - ## update shape of scale box - self.updateScaleBox(ev.buttonDownPos(), ev.pos()) - else: - tr = self.childGroup.transform() - tr = fn.invertQTransform(tr) - tr = tr.map(dif*mask) - tr.map(Point(0,0)) - - x = tr.x() if mask[0] == 1 else None - y = tr.y() if mask[1] == 1 else None - - self._resetTarget() - if x is not None or y is not None: - self.translateBy(x=x, y=y) - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - elif ev.button() & QtCore.Qt.RightButton: - #print "vb.rightDrag" - if self.state['aspectLocked'] is not False: - mask[0] = 0 - - dif = ev.screenPos() - ev.lastScreenPos() - dif = np.array([dif.x(), dif.y()]) - dif[0] *= -1 - s = ((mask * 0.02) + 1) ** dif - - tr = self.childGroup.transform() - tr = fn.invertQTransform(tr) - - x = s[0] if mouseEnabled[0] == 1 else None - y = s[1] if mouseEnabled[1] == 1 else None - - center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) - self._resetTarget() - self.scaleBy(x=x, y=y, center=center) - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - - def keyPressEvent(self, ev): - """ - This routine should capture key presses in the current view box. - Key presses are used only when mouse mode is RectMode - The following events are implemented: - ctrl-A : zooms out to the default "full" view of the plot - ctrl-+ : moves forward in the zooming stack (if it exists) - ctrl-- : moves backward in the zooming stack (if it exists) - - """ - ev.accept() - if ev.text() == '-': - self.scaleHistory(-1) - elif ev.text() in ['+', '=']: - self.scaleHistory(1) - elif ev.key() == QtCore.Qt.Key_Backspace: - self.scaleHistory(len(self.axHistory)) - else: - ev.ignore() - - def scaleHistory(self, d): - if len(self.axHistory) == 0: - return - ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) - if ptr != self.axHistoryPointer: - self.axHistoryPointer = ptr - self.showAxRect(self.axHistory[ptr]) - - def updateScaleBox(self, p1, p2): - r = QtCore.QRectF(p1, p2) - r = self.childGroup.mapRectFromParent(r) - self.rbScaleBox.setPos(r.topLeft()) - tr = QtGui.QTransform.fromScale(r.width(), r.height()) - self.rbScaleBox.setTransform(tr) - self.rbScaleBox.show() - - def showAxRect(self, ax, **kwargs): - """Set the visible range to the given rectangle - Passes keyword arguments to setRange - """ - self.setRange(ax.normalized(), **kwargs) # be sure w, h are correct coordinates - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - - def allChildren(self, item=None): - """Return a list of all children and grandchildren of this ViewBox""" - if item is None: - item = self.childGroup - - children = [item] - for ch in item.childItems(): - children.extend(self.allChildren(ch)) - return children - - def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): - """Return the bounding range of all children. - [[xmin, xmax], [ymin, ymax]] - Values may be None if there are no specific bounds for an axis. - """ - profiler = debug.Profiler() - if items is None: - items = self.addedItems - - ## measure pixel dimensions in view box - px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] - - ## First collect all boundary information - itemBounds = [] - for item in items: - if not item.isVisible() or not item.scene() is self.scene(): - continue - - useX = True - useY = True - - if hasattr(item, 'dataBounds'): - if frac is None: - frac = (1.0, 1.0) - xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) - yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) - pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() - if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any(): - useX = False - xr = (0,0) - if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any(): - useY = False - yr = (0,0) - - bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) - bounds = self.mapFromItemToView(item, bounds).boundingRect() - - if not any([useX, useY]): - continue - - ## If we are ignoring only one axis, we need to check for rotations - if useX != useY: ## != means xor - ang = round(item.transformAngle()) - if ang == 0 or ang == 180: - pass - elif ang == 90 or ang == 270: - useX, useY = useY, useX - else: - ## Item is rotated at non-orthogonal angle, ignore bounds entirely. - ## Not really sure what is the expected behavior in this case. - continue ## need to check for item rotations and decide how best to apply this boundary. - - - itemBounds.append((bounds, useX, useY, pxPad)) - else: - if item.flags() & item.ItemHasNoContents: - continue - bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() - itemBounds.append((bounds, True, True, 0)) - - ## determine tentative new range - range = [None, None] - for bounds, useX, useY, px in itemBounds: - if useY: - if range[1] is not None: - range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] - else: - range[1] = [bounds.top(), bounds.bottom()] - if useX: - if range[0] is not None: - range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] - else: - range[0] = [bounds.left(), bounds.right()] - profiler() - - ## Now expand any bounds that have a pixel margin - ## This must be done _after_ we have a good estimate of the new range - ## to ensure that the pixel size is roughly accurate. - w = self.width() - h = self.height() - if w > 0 and range[0] is not None: - pxSize = (range[0][1] - range[0][0]) / w - for bounds, useX, useY, px in itemBounds: - if px == 0 or not useX: - continue - range[0][0] = min(range[0][0], bounds.left() - px*pxSize) - range[0][1] = max(range[0][1], bounds.right() + px*pxSize) - if h > 0 and range[1] is not None: - pxSize = (range[1][1] - range[1][0]) / h - for bounds, useX, useY, px in itemBounds: - if px == 0 or not useY: - continue - range[1][0] = min(range[1][0], bounds.top() - px*pxSize) - range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) - return range - - def childrenBoundingRect(self, *args, **kwds): - range = self.childrenBounds(*args, **kwds) - tr = self.targetRange() - if range[0] is None: - range[0] = tr[0] - if range[1] is None: - range[1] = tr[1] - - bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) - return bounds - - def updateViewRange(self, forceX=False, forceY=False): - ## Update viewRange to match targetRange as closely as possible, given - ## aspect ratio constraints. The *force* arguments are used to indicate - ## which axis (if any) should be unchanged when applying constraints. - viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] - changed = [False, False] - - #-------- Make correction for aspect ratio constraint ---------- - - # aspect is (widget w/h) / (view range w/h) - aspect = self.state['aspectLocked'] # size ratio / view ratio - tr = self.targetRect() - bounds = self.rect() - - limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits']) - minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] - maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]] - - for axis in [0, 1]: - if limits[axis][0] is None and limits[axis][1] is None and minRng[axis] is None and maxRng[axis] is None: - continue - - # max range cannot be larger than bounds, if they are given - if limits[axis][0] is not None and limits[axis][1] is not None: - if maxRng[axis] is not None: - maxRng[axis] = min(maxRng[axis], limits[axis][1] - limits[axis][0]) - else: - maxRng[axis] = limits[axis][1] - limits[axis][0] - - if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]: - - ## This is the view range aspect ratio we have requested - targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1 - ## This is the view range aspect ratio we need to obey aspect constraint - viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect - viewRatio = 1 if viewRatio == 0 else viewRatio - - # Calculate both the x and y ranges that would be needed to obtain the desired aspect ratio - dy = 0.5 * (tr.width() / viewRatio - tr.height()) - dx = 0.5 * (tr.height() * viewRatio - tr.width()) - - rangeY = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy] - rangeX = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] - - canidateRange = [rangeX, rangeY] - - # Decide which range to try to keep unchanged - #print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange'] - if forceX: - ax = 0 - elif forceY: - ax = 1 - else: - # if we are not required to keep a particular axis unchanged, - # then try to make the entire target range visible - ax = 0 if targetRatio > viewRatio else 1 - target = 0 if ax == 1 else 1 - # See if this choice would cause out-of-range issues - if maxRng is not None or minRng is not None: - diff = canidateRange[target][1] - canidateRange[target][0] - if maxRng[target] is not None and diff > maxRng[target] or \ - minRng[target] is not None and diff < minRng[target]: - # tweak the target range down so we can still pan properly - self.state['targetRange'][ax] = canidateRange[ax] - ax = target # Switch the "fixed" axes - - if ax == 0: - ## view range needs to be taller than target - if dy != 0: - changed[1] = True - viewRange[1] = rangeY - else: - ## view range needs to be wider than target - if dx != 0: - changed[0] = True - viewRange[0] = rangeX - - - changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] - self.state['viewRange'] = viewRange - - if any(changed): - self._matrixNeedsUpdate = True - self.update() - - # Inform linked views that the range has changed - for ax in [0, 1]: - if not changed[ax]: - continue - link = self.linkedView(ax) - if link is not None: - link.linkedViewChanged(self, ax) - - # emit range change signals - if changed[0]: - self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) - if changed[1]: - self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) - self.sigRangeChanged.emit(self, self.state['viewRange']) - - def updateMatrix(self, changed=None): - if not self._matrixNeedsUpdate: - return - ## Make the childGroup's transform match the requested viewRange. - bounds = self.rect() - - vr = self.viewRect() - if vr.height() == 0 or vr.width() == 0: - return - scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) - if not self.state['yInverted']: - scale = scale * Point(1, -1) - if self.state['xInverted']: - scale = scale * Point(-1, 1) - m = QtGui.QTransform() - - ## First center the viewport at 0 - center = bounds.center() - m.translate(center.x(), center.y()) - - ## Now scale and translate properly - m.scale(scale[0], scale[1]) - st = Point(vr.center()) - m.translate(-st[0], -st[1]) - - self.childGroup.setTransform(m) - self._matrixNeedsUpdate = False - - self.sigTransformChanged.emit(self) ## segfaults here: 1 - - def paint(self, p, opt, widget): - self.prepareForPaint() - if self.border is not None: - bounds = self.shape() - p.setPen(self.border) - #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) - p.drawPath(bounds) - - #p.setPen(fn.mkPen('r')) - #path = QtGui.QPainterPath() - #path.addRect(self.targetRect()) - #tr = self.mapFromView(path) - #p.drawPath(tr) - - def updateBackground(self): - bg = self.state['background'] - if bg is None: - self.background.hide() - else: - self.background.show() - self.background.setBrush(fn.mkBrush(bg)) - - def updateViewLists(self): - try: - self.window() - except RuntimeError: ## this view has already been deleted; it will probably be collected shortly. - return - - def view_key(view): - return (view.window() is self.window(), view.name) - - ## make a sorted list of all named views - nv = sorted(ViewBox.NamedViews.values(), key=view_key) - - if self in nv: - nv.remove(self) - - if self.menu is not None: - self.menu.setViewList(nv) - - for ax in [0,1]: - link = self.state['linkedViews'][ax] - if isinstance(link, basestring): ## axis has not been linked yet; see if it's possible now - for v in nv: - if link == v.name: - self.linkView(ax, v) - - @staticmethod - def updateAllViewLists(): - for v in ViewBox.AllViews: - v.updateViewLists() - - @staticmethod - def forgetView(vid, name): - if ViewBox is None: ## can happen as python is shutting down - return - if QtGui.QApplication.instance() is None: - return - ## Called with ID and name of view (the view itself is no longer available) - for v in list(ViewBox.AllViews.keys()): - if id(v) == vid: - ViewBox.AllViews.pop(v) - break - ViewBox.NamedViews.pop(name, None) - ViewBox.updateAllViewLists() - - @staticmethod - def quit(): - ## called when the application is about to exit. - ## this disables all callbacks, which might otherwise generate errors if invoked during exit. - for k in ViewBox.AllViews: - if isQObjectAlive(k) and getConfigOption('crashWarning'): - sys.stderr.write('Warning: ViewBox should be closed before application exit.\n') - - try: - k.destroyed.disconnect() - except RuntimeError: ## signal is already disconnected. - pass - except TypeError: ## view has already been deleted (?) - pass - except AttributeError: # PySide has deleted signal - pass - - def locate(self, item, timeout=3.0, children=False): - """ - Temporarily display the bounding rect of an item and lines connecting to the center of the view. - This is useful for determining the location of items that may be out of the range of the ViewBox. - if allChildren is True, then the bounding rect of all item's children will be shown instead. - """ - self.clearLocate() - - if item.scene() is not self.scene(): - raise Exception("Item does not share a scene with this ViewBox.") - - c = self.viewRect().center() - if children: - br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect() - else: - br = self.mapFromItemToView(item, item.boundingRect()).boundingRect() - - g = ItemGroup() - g.setParentItem(self.childGroup) - self.locateGroup = g - g.box = QtGui.QGraphicsRectItem(br) - g.box.setParentItem(g) - g.lines = [] - for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()): - line = QtGui.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y()) - line.setParentItem(g) - g.lines.append(line) - - for item in g.childItems(): - item.setPen(fn.mkPen(color='y', width=3)) - g.setZValue(1000000) - - if children: - g.path = QtGui.QGraphicsPathItem(g.childrenShape()) - else: - g.path = QtGui.QGraphicsPathItem(g.shape()) - g.path.setParentItem(g) - g.path.setPen(fn.mkPen('g')) - g.path.setZValue(100) - - QtCore.QTimer.singleShot(timeout*1000, self.clearLocate) - - def clearLocate(self): - if self.locateGroup is None: - return - self.scene().removeItem(self.locateGroup) - self.locateGroup = None - - -from .ViewBoxMenu import ViewBoxMenu diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py b/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py deleted file mode 100644 index 5113812..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding: utf-8 -*- -from ...Qt import QtCore, QtGui, QT_LIB -from ...python2_3 import asUnicode -from ...WidgetGroup import WidgetGroup - -import importlib -ui_template = importlib.import_module( - f'.axisCtrlTemplate_{QT_LIB.lower()}', package=__package__) - -import weakref - -translate = QtCore.QCoreApplication.translate -class ViewBoxMenu(QtGui.QMenu): - def __init__(self, view): - QtGui.QMenu.__init__(self) - - self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected) - self.valid = False ## tells us whether the ui needs to be updated - self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos - - self.setTitle(translate("ViewBox", "ViewBox options")) - self.viewAll = QtGui.QAction(translate("ViewBox", "View All"), self) - self.viewAll.triggered.connect(self.autoRange) - self.addAction(self.viewAll) - - self.axes = [] - self.ctrl = [] - self.widgetGroups = [] - self.dv = QtGui.QDoubleValidator(self) - for axis in 'XY': - m = QtGui.QMenu() - m.setTitle(f"{axis} {translate('ViewBox', 'axis')}") - w = QtGui.QWidget() - ui = ui_template.Ui_Form() - ui.setupUi(w) - a = QtGui.QWidgetAction(self) - a.setDefaultWidget(w) - m.addAction(a) - self.addMenu(m) - self.axes.append(m) - self.ctrl.append(ui) - wg = WidgetGroup(w) - self.widgetGroups.append(w) - - connects = [ - (ui.mouseCheck.toggled, 'MouseToggled'), - (ui.manualRadio.clicked, 'ManualClicked'), - (ui.minText.editingFinished, 'RangeTextChanged'), - (ui.maxText.editingFinished, 'RangeTextChanged'), - (ui.autoRadio.clicked, 'AutoClicked'), - (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), - (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), - (ui.autoPanCheck.toggled, 'AutoPanToggled'), - (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled') - ] - - for sig, fn in connects: - sig.connect(getattr(self, axis.lower()+fn)) - - self.ctrl[0].invertCheck.toggled.connect(self.xInvertToggled) - self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled) - ## exporting is handled by GraphicsScene now - #self.export = QtGui.QMenu("Export") - #self.setExportMethods(view.exportMethods) - #self.addMenu(self.export) - - self.leftMenu = QtGui.QMenu(translate("ViewBox", "Mouse Mode")) - group = QtGui.QActionGroup(self) - - # This does not work! QAction _must_ be initialized with a permanent - # object as the parent or else it may be collected prematurely. - #pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) - #zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) - pan = QtGui.QAction(translate("ViewBox", "3 button"), self.leftMenu) - zoom = QtGui.QAction(translate("ViewBox", "1 button"), self.leftMenu) - self.leftMenu.addAction(pan) - self.leftMenu.addAction(zoom) - pan.triggered.connect(self.set3ButtonMode) - zoom.triggered.connect(self.set1ButtonMode) - - pan.setCheckable(True) - zoom.setCheckable(True) - pan.setActionGroup(group) - zoom.setActionGroup(group) - self.mouseModes = [pan, zoom] - self.addMenu(self.leftMenu) - - self.view().sigStateChanged.connect(self.viewStateChanged) - - self.updateState() - - def setExportMethods(self, methods): - self.exportMethods = methods - self.export.clear() - for opt, fn in methods.items(): - self.export.addAction(opt, self.exportMethod) - - - def viewStateChanged(self): - self.valid = False - if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): - self.updateState() - - def updateState(self): - ## Something about the viewbox has changed; update the menu GUI - - state = self.view().getState(copy=False) - if state['mouseMode'] == ViewBox.PanMode: - self.mouseModes[0].setChecked(True) - else: - self.mouseModes[1].setChecked(True) - - for i in [0,1]: # x, y - tr = state['targetRange'][i] - self.ctrl[i].minText.setText("%0.5g" % tr[0]) - self.ctrl[i].maxText.setText("%0.5g" % tr[1]) - if state['autoRange'][i] is not False: - self.ctrl[i].autoRadio.setChecked(True) - if state['autoRange'][i] is not True: - self.ctrl[i].autoPercentSpin.setValue(state['autoRange'][i]*100) - else: - self.ctrl[i].manualRadio.setChecked(True) - self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) - - ## Update combo to show currently linked view - c = self.ctrl[i].linkCombo - c.blockSignals(True) - try: - view = state['linkedViews'][i] ## will always be string or None - if view is None: - view = '' - - ind = c.findText(view) - - if ind == -1: - ind = 0 - c.setCurrentIndex(ind) - finally: - c.blockSignals(False) - - self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) - self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) - xy = ['x', 'y'][i] - self.ctrl[i].invertCheck.setChecked(state.get(xy+'Inverted', False)) - - self.valid = True - - def popup(self, *args): - if not self.valid: - self.updateState() - QtGui.QMenu.popup(self, *args) - - def autoRange(self): - self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument - - def xMouseToggled(self, b): - self.view().setMouseEnabled(x=b) - - def xManualClicked(self): - self.view().enableAutoRange(ViewBox.XAxis, False) - - def xRangeTextChanged(self): - self.ctrl[0].manualRadio.setChecked(True) - self.view().setXRange(*self._validateRangeText(0), padding=0) - - def xAutoClicked(self): - val = self.ctrl[0].autoPercentSpin.value() * 0.01 - self.view().enableAutoRange(ViewBox.XAxis, val) - - def xAutoSpinChanged(self, val): - self.ctrl[0].autoRadio.setChecked(True) - self.view().enableAutoRange(ViewBox.XAxis, val*0.01) - - def xLinkComboChanged(self, ind): - self.view().setXLink(str(self.ctrl[0].linkCombo.currentText())) - - def xAutoPanToggled(self, b): - self.view().setAutoPan(x=b) - - def xVisibleOnlyToggled(self, b): - self.view().setAutoVisible(x=b) - - - def yMouseToggled(self, b): - self.view().setMouseEnabled(y=b) - - def yManualClicked(self): - self.view().enableAutoRange(ViewBox.YAxis, False) - - def yRangeTextChanged(self): - self.ctrl[1].manualRadio.setChecked(True) - self.view().setYRange(*self._validateRangeText(1), padding=0) - - def yAutoClicked(self): - val = self.ctrl[1].autoPercentSpin.value() * 0.01 - self.view().enableAutoRange(ViewBox.YAxis, val) - - def yAutoSpinChanged(self, val): - self.ctrl[1].autoRadio.setChecked(True) - self.view().enableAutoRange(ViewBox.YAxis, val*0.01) - - def yLinkComboChanged(self, ind): - self.view().setYLink(str(self.ctrl[1].linkCombo.currentText())) - - def yAutoPanToggled(self, b): - self.view().setAutoPan(y=b) - - def yVisibleOnlyToggled(self, b): - self.view().setAutoVisible(y=b) - - def yInvertToggled(self, b): - self.view().invertY(b) - - def xInvertToggled(self, b): - self.view().invertX(b) - - def exportMethod(self): - act = self.sender() - self.exportMethods[str(act.text())]() - - def set3ButtonMode(self): - self.view().setLeftButtonAction('pan') - - def set1ButtonMode(self): - self.view().setLeftButtonAction('rect') - - def setViewList(self, views): - names = [''] - self.viewMap.clear() - - ## generate list of views to show in the link combo - for v in views: - name = v.name - if name is None: ## unnamed views do not show up in the view list (although they are linkable) - continue - names.append(name) - self.viewMap[name] = v - - for i in [0,1]: - c = self.ctrl[i].linkCombo - current = asUnicode(c.currentText()) - c.blockSignals(True) - changed = True - try: - c.clear() - for name in names: - c.addItem(name) - if name == current: - changed = False - c.setCurrentIndex(c.count()-1) - finally: - c.blockSignals(False) - - if changed: - c.setCurrentIndex(0) - c.currentIndexChanged.emit(c.currentIndex()) - - def _validateRangeText(self, axis): - """Validate range text inputs. Return current value(s) if invalid.""" - inputs = (self.ctrl[axis].minText.text(), - self.ctrl[axis].maxText.text()) - vals = self.view().viewRange()[axis] - for i, text in enumerate(inputs): - try: - vals[i] = float(text) - except ValueError: - # could not convert string to float - pass - return vals - - -from .ViewBox import ViewBox - - diff --git a/pyqtgraph/graphicsItems/ViewBox/__init__.py b/pyqtgraph/graphicsItems/ViewBox/__init__.py deleted file mode 100644 index 685a314..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ViewBox import ViewBox diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-36.pyc deleted file mode 100644 index 757ae26..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-37.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-37.pyc deleted file mode 100644 index 369c45e..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBox.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-36.pyc deleted file mode 100644 index f602d56..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-37.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-37.pyc deleted file mode 100644 index 5daaa18..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/ViewBoxMenu.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 90cc5ce..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 9c0ff00..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index 1725f37..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index 44c5ca9..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside2.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index f5170c2..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-36.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index adebb6c..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-37.pyc b/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-37.pyc deleted file mode 100644 index 284afea..0000000 Binary files a/pyqtgraph/graphicsItems/ViewBox/__pycache__/axisCtrlTemplate_pyside6.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt5.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt5.py deleted file mode 100644 index 0a28e7f..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt5.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' -# -# Created: Wed Mar 26 15:09:28 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(186, 154) - Form.setMaximumSize(QtCore.QSize(200, 16777215)) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - self.linkCombo = QtWidgets.QComboBox(Form) - self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - self.linkCombo.setObjectName("linkCombo") - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - self.autoPercentSpin = QtWidgets.QSpinBox(Form) - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty("value", 100) - self.autoPercentSpin.setObjectName("autoPercentSpin") - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - self.autoRadio = QtWidgets.QRadioButton(Form) - self.autoRadio.setChecked(True) - self.autoRadio.setObjectName("autoRadio") - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - self.manualRadio = QtWidgets.QRadioButton(Form) - self.manualRadio.setObjectName("manualRadio") - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - self.minText = QtWidgets.QLineEdit(Form) - self.minText.setObjectName("minText") - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - self.maxText = QtWidgets.QLineEdit(Form) - self.maxText.setObjectName("maxText") - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - self.invertCheck = QtWidgets.QCheckBox(Form) - self.invertCheck.setObjectName("invertCheck") - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - self.mouseCheck = QtWidgets.QCheckBox(Form) - self.mouseCheck.setChecked(True) - self.mouseCheck.setObjectName("mouseCheck") - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) - self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - self.autoPanCheck = QtWidgets.QCheckBox(Form) - self.autoPanCheck.setObjectName("autoPanCheck") - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.label.setText(_translate("Form", "Link Axis:")) - self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

")) - self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

")) - self.autoPercentSpin.setSuffix(_translate("Form", "%")) - self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

")) - self.autoRadio.setText(_translate("Form", "Auto")) - self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

")) - self.manualRadio.setText(_translate("Form", "Manual")) - self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

")) - self.minText.setText(_translate("Form", "0")) - self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

")) - self.maxText.setText(_translate("Form", "0")) - self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

")) - self.invertCheck.setText(_translate("Form", "Invert Axis")) - self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

")) - self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) - self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

")) - self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) - self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

")) - self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) - diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py deleted file mode 100644 index 6c88361..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py +++ /dev/null @@ -1,88 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\graphicsItems\ViewBox\axisCtrlTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(186, 154) - Form.setMaximumSize(QtCore.QSize(200, 16777215)) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - self.linkCombo = QtWidgets.QComboBox(Form) - self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents) - self.linkCombo.setObjectName("linkCombo") - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - self.autoPercentSpin = QtWidgets.QSpinBox(Form) - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty("value", 100) - self.autoPercentSpin.setObjectName("autoPercentSpin") - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - self.autoRadio = QtWidgets.QRadioButton(Form) - self.autoRadio.setChecked(True) - self.autoRadio.setObjectName("autoRadio") - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - self.manualRadio = QtWidgets.QRadioButton(Form) - self.manualRadio.setObjectName("manualRadio") - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - self.minText = QtWidgets.QLineEdit(Form) - self.minText.setObjectName("minText") - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - self.maxText = QtWidgets.QLineEdit(Form) - self.maxText.setObjectName("maxText") - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - self.invertCheck = QtWidgets.QCheckBox(Form) - self.invertCheck.setObjectName("invertCheck") - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - self.mouseCheck = QtWidgets.QCheckBox(Form) - self.mouseCheck.setChecked(True) - self.mouseCheck.setObjectName("mouseCheck") - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) - self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - self.autoPanCheck = QtWidgets.QCheckBox(Form) - self.autoPanCheck.setObjectName("autoPanCheck") - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.label.setText(_translate("Form", "Link Axis:")) - self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

")) - self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

")) - self.autoPercentSpin.setSuffix(_translate("Form", "%")) - self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

")) - self.autoRadio.setText(_translate("Form", "Auto")) - self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

")) - self.manualRadio.setText(_translate("Form", "Manual")) - self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

")) - self.minText.setText(_translate("Form", "0")) - self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

")) - self.maxText.setText(_translate("Form", "0")) - self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

")) - self.invertCheck.setText(_translate("Form", "Invert Axis")) - self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

")) - self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) - self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

")) - self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) - self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

")) - self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside2.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside2.py deleted file mode 100644 index 401c52f..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside2.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' -# -# Created: Wed Mar 26 15:09:28 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(186, 154) - Form.setMaximumSize(QtCore.QSize(200, 16777215)) - self.gridLayout = QtWidgets.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - self.linkCombo = QtWidgets.QComboBox(Form) - self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - self.linkCombo.setObjectName("linkCombo") - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - self.autoPercentSpin = QtWidgets.QSpinBox(Form) - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty("value", 100) - self.autoPercentSpin.setObjectName("autoPercentSpin") - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - self.autoRadio = QtWidgets.QRadioButton(Form) - self.autoRadio.setChecked(True) - self.autoRadio.setObjectName("autoRadio") - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - self.manualRadio = QtWidgets.QRadioButton(Form) - self.manualRadio.setObjectName("manualRadio") - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - self.minText = QtWidgets.QLineEdit(Form) - self.minText.setObjectName("minText") - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - self.maxText = QtWidgets.QLineEdit(Form) - self.maxText.setObjectName("maxText") - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - self.invertCheck = QtWidgets.QCheckBox(Form) - self.invertCheck.setObjectName("invertCheck") - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - self.mouseCheck = QtWidgets.QCheckBox(Form) - self.mouseCheck.setChecked(True) - self.mouseCheck.setObjectName("mouseCheck") - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) - self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - self.autoPanCheck = QtWidgets.QCheckBox(Form) - self.autoPanCheck.setObjectName("autoPanCheck") - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "Form")) - self.label.setText(_translate("Form", "Link Axis:")) - self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

")) - self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

")) - self.autoPercentSpin.setSuffix(_translate("Form", "%")) - self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

")) - self.autoRadio.setText(_translate("Form", "Auto")) - self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

")) - self.manualRadio.setText(_translate("Form", "Manual")) - self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

")) - self.minText.setText(_translate("Form", "0")) - self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

")) - self.maxText.setText(_translate("Form", "0")) - self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

")) - self.invertCheck.setText(_translate("Form", "Invert Axis")) - self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

")) - self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) - self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

")) - self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) - self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

")) - self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) - diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside6.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside6.py deleted file mode 100644 index f52ddf1..0000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside6.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'axisCtrlTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(186, 154) - Form.setMaximumSize(QSize(200, 16777215)) - self.gridLayout = QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName(u"gridLayout") - self.label = QLabel(Form) - self.label.setObjectName(u"label") - - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - - self.linkCombo = QComboBox(Form) - self.linkCombo.setObjectName(u"linkCombo") - self.linkCombo.setSizeAdjustPolicy(QComboBox.AdjustToContents) - - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - - self.autoPercentSpin = QSpinBox(Form) - self.autoPercentSpin.setObjectName(u"autoPercentSpin") - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setValue(100) - - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - - self.autoRadio = QRadioButton(Form) - self.autoRadio.setObjectName(u"autoRadio") - self.autoRadio.setChecked(True) - - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - - self.manualRadio = QRadioButton(Form) - self.manualRadio.setObjectName(u"manualRadio") - - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - - self.minText = QLineEdit(Form) - self.minText.setObjectName(u"minText") - - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - - self.maxText = QLineEdit(Form) - self.maxText.setObjectName(u"maxText") - - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - - self.invertCheck = QCheckBox(Form) - self.invertCheck.setObjectName(u"invertCheck") - - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - - self.mouseCheck = QCheckBox(Form) - self.mouseCheck.setObjectName(u"mouseCheck") - self.mouseCheck.setChecked(True) - - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - - self.visibleOnlyCheck = QCheckBox(Form) - self.visibleOnlyCheck.setObjectName(u"visibleOnlyCheck") - - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - - self.autoPanCheck = QCheckBox(Form) - self.autoPanCheck.setObjectName(u"autoPanCheck") - - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.label.setText(QCoreApplication.translate("Form", u"Link Axis:", None)) -#if QT_CONFIG(tooltip) - self.linkCombo.setToolTip(QCoreApplication.translate("Form", u"

Links this axis with another view. When linked, both views will display the same data range.

", None)) -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(tooltip) - self.autoPercentSpin.setToolTip(QCoreApplication.translate("Form", u"

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoPercentSpin.setSuffix(QCoreApplication.translate("Form", u"%", None)) -#if QT_CONFIG(tooltip) - self.autoRadio.setToolTip(QCoreApplication.translate("Form", u"

Automatically resize this axis whenever the displayed data is changed.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoRadio.setText(QCoreApplication.translate("Form", u"Auto", None)) -#if QT_CONFIG(tooltip) - self.manualRadio.setToolTip(QCoreApplication.translate("Form", u"

Set the range for this axis manually. This disables automatic scaling.

", None)) -#endif // QT_CONFIG(tooltip) - self.manualRadio.setText(QCoreApplication.translate("Form", u"Manual", None)) -#if QT_CONFIG(tooltip) - self.minText.setToolTip(QCoreApplication.translate("Form", u"

Minimum value to display for this axis.

", None)) -#endif // QT_CONFIG(tooltip) - self.minText.setText(QCoreApplication.translate("Form", u"0", None)) -#if QT_CONFIG(tooltip) - self.maxText.setToolTip(QCoreApplication.translate("Form", u"

Maximum value to display for this axis.

", None)) -#endif // QT_CONFIG(tooltip) - self.maxText.setText(QCoreApplication.translate("Form", u"0", None)) -#if QT_CONFIG(tooltip) - self.invertCheck.setToolTip(QCoreApplication.translate("Form", u"

Inverts the display of this axis. (+y points downward instead of upward)

", None)) -#endif // QT_CONFIG(tooltip) - self.invertCheck.setText(QCoreApplication.translate("Form", u"Invert Axis", None)) -#if QT_CONFIG(tooltip) - self.mouseCheck.setToolTip(QCoreApplication.translate("Form", u"

Enables mouse interaction (panning, scaling) for this axis.

", None)) -#endif // QT_CONFIG(tooltip) - self.mouseCheck.setText(QCoreApplication.translate("Form", u"Mouse Enabled", None)) -#if QT_CONFIG(tooltip) - self.visibleOnlyCheck.setToolTip(QCoreApplication.translate("Form", u"

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None)) -#endif // QT_CONFIG(tooltip) - self.visibleOnlyCheck.setText(QCoreApplication.translate("Form", u"Visible Data Only", None)) -#if QT_CONFIG(tooltip) - self.autoPanCheck.setToolTip(QCoreApplication.translate("Form", u"

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None)) -#endif // QT_CONFIG(tooltip) - self.autoPanCheck.setText(QCoreApplication.translate("Form", u"Auto Pan Only", None)) - # retranslateUi - diff --git a/pyqtgraph/graphicsItems/__init__.py b/pyqtgraph/graphicsItems/__init__.py deleted file mode 100644 index 8e41181..0000000 --- a/pyqtgraph/graphicsItems/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -### just import everything from sub-modules - -#import os - -#d = os.path.split(__file__)[0] -#files = [] -#for f in os.listdir(d): - #if os.path.isdir(os.path.join(d, f)): - #files.append(f) - #elif f[-3:] == '.py' and f != '__init__.py': - #files.append(f[:-3]) - -#for modName in files: - #mod = __import__(modName, globals(), locals(), fromlist=['*']) - #if hasattr(mod, '__all__'): - #names = mod.__all__ - #else: - #names = [n for n in dir(mod) if n[0] != '_'] - #for k in names: - ##print modName, k - #globals()[k] = getattr(mod, k) diff --git a/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-36.pyc deleted file mode 100644 index 27c2884..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-37.pyc deleted file mode 100644 index 7b2d85d..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ArrowItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-36.pyc deleted file mode 100644 index 89e007d..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-37.pyc deleted file mode 100644 index f467bbc..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/AxisItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-36.pyc deleted file mode 100644 index bb8b78e..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-37.pyc deleted file mode 100644 index b832295..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/BarGraphItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-36.pyc deleted file mode 100644 index d1d8eb0..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-37.pyc deleted file mode 100644 index 9c0303c..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ButtonItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-36.pyc deleted file mode 100644 index 80d6d35..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-37.pyc deleted file mode 100644 index 9b4faca..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/CurvePoint.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-36.pyc deleted file mode 100644 index d7ba978..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-37.pyc deleted file mode 100644 index 34aa5e7..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/DateAxisItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-36.pyc deleted file mode 100644 index fa9fac9..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-37.pyc deleted file mode 100644 index 0ddc77c..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ErrorBarItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-36.pyc deleted file mode 100644 index 1097643..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-37.pyc deleted file mode 100644 index c94b71e..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/FillBetweenItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-36.pyc deleted file mode 100644 index 2fc7263..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-37.pyc deleted file mode 100644 index 616fc4e..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GradientEditorItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-36.pyc deleted file mode 100644 index 2bba72c..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-37.pyc deleted file mode 100644 index ad5c718..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GradientLegend.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-36.pyc deleted file mode 100644 index 6493a7a..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-37.pyc deleted file mode 100644 index ee06f64..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-36.pyc deleted file mode 100644 index 4245080..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-37.pyc deleted file mode 100644 index fef96a3..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-36.pyc deleted file mode 100644 index 5abfd16..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-37.pyc deleted file mode 100644 index 9b5a977..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsLayout.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-36.pyc deleted file mode 100644 index 45fb98f..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-37.pyc deleted file mode 100644 index 014d064..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsObject.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-36.pyc deleted file mode 100644 index c74665d..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-37.pyc deleted file mode 100644 index 382ca16..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-36.pyc deleted file mode 100644 index 63109f4..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-37.pyc deleted file mode 100644 index 2893bb7..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GraphicsWidgetAnchor.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-36.pyc deleted file mode 100644 index 6b19bdc..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-37.pyc deleted file mode 100644 index 7b1608f..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/GridItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-36.pyc deleted file mode 100644 index aad2d4d..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-37.pyc deleted file mode 100644 index 88e5151..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/HistogramLUTItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-36.pyc deleted file mode 100644 index 21ca54f..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-37.pyc deleted file mode 100644 index 85bac84..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ImageItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-36.pyc deleted file mode 100644 index 4a4f1b8..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-37.pyc deleted file mode 100644 index 851e4a1..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/InfiniteLine.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-36.pyc deleted file mode 100644 index 1cec0fe..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-37.pyc deleted file mode 100644 index 03c5d83..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/IsocurveItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-36.pyc deleted file mode 100644 index 8deb551..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-37.pyc deleted file mode 100644 index db47565..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ItemGroup.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-36.pyc deleted file mode 100644 index 4b87df7..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-37.pyc deleted file mode 100644 index 49b47d9..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LabelItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-36.pyc deleted file mode 100644 index 2538ed7..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-37.pyc deleted file mode 100644 index b98c903..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LegendItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-36.pyc deleted file mode 100644 index 0ba8a2c..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-37.pyc deleted file mode 100644 index ff932b8..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/LinearRegionItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-36.pyc deleted file mode 100644 index 9f6d46f..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-37.pyc deleted file mode 100644 index 4cf2cb2..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/MultiPlotItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/NonUniformImage.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/NonUniformImage.cpython-36.pyc deleted file mode 100644 index c37d42d..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/NonUniformImage.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-36.pyc deleted file mode 100644 index 152eec6..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-37.pyc deleted file mode 100644 index 3372aae..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PColorMeshItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-36.pyc deleted file mode 100644 index 50436c5..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-37.pyc deleted file mode 100644 index 2a65fc9..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PlotCurveItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-36.pyc deleted file mode 100644 index f1b548f..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-37.pyc deleted file mode 100644 index c22d743..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/PlotDataItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-36.pyc deleted file mode 100644 index 96af512..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-37.pyc deleted file mode 100644 index 96f5e48..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ROI.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-36.pyc deleted file mode 100644 index 7c93b7e..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-37.pyc deleted file mode 100644 index c1e8c45..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ScaleBar.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-36.pyc deleted file mode 100644 index 2380ea4..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-37.pyc deleted file mode 100644 index 6032dc6..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/ScatterPlotItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/TargetItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/TargetItem.cpython-36.pyc deleted file mode 100644 index 8e8a5eb..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/TargetItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-36.pyc deleted file mode 100644 index d443f87..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-37.pyc deleted file mode 100644 index 9f4efe1..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/TextItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-36.pyc deleted file mode 100644 index 69a52db..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-37.pyc deleted file mode 100644 index 4608633..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/UIGraphicsItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-36.pyc deleted file mode 100644 index b777f98..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-37.pyc deleted file mode 100644 index 6290356..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/VTickGroup.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 24e69ad..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 7d34e86..0000000 Binary files a/pyqtgraph/graphicsItems/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/graphicsWindows.py b/pyqtgraph/graphicsWindows.py deleted file mode 100644 index 02206d3..0000000 --- a/pyqtgraph/graphicsWindows.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -""" -DEPRECATED: The classes below are convenience classes that create a new window -containting a single, specific widget. These classes are now unnecessary because -it is possible to place any widget into its own window by simply calling its -show() method. -""" - -from .Qt import QtCore, QtGui, mkQApp -from .widgets.PlotWidget import * -from .imageview import * -from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget -from .widgets.GraphicsView import GraphicsView -import warnings - - -class GraphicsWindow(GraphicsLayoutWidget): - """ - (deprecated; use :class:`~pyqtgraph.GraphicsLayoutWidget` instead) - - Convenience subclass of :class:`~pyqtgraph.GraphicsLayoutWidget`. This class - is intended for use from the interactive python prompt. - """ - def __init__(self, title=None, size=(800,600), **kargs): - warnings.warn( - 'GraphicsWindow is deprecated, use GraphicsLayoutWidget instead,' - 'will be removed in 0.13', - DeprecationWarning, stacklevel=2 - ) - mkQApp() - GraphicsLayoutWidget.__init__(self, **kargs) - self.resize(*size) - if title is not None: - self.setWindowTitle(title) - self.show() - - -class TabWindow(QtGui.QMainWindow): - """ - (deprecated) - """ - def __init__(self, title=None, size=(800,600)): - warnings.warn( - 'TabWindow is deprecated, will be removed in 0.13', - DeprecationWarning, stacklevel=2 - ) - mkQApp() - QtGui.QMainWindow.__init__(self) - self.resize(*size) - self.cw = QtGui.QTabWidget() - self.setCentralWidget(self.cw) - if title is not None: - self.setWindowTitle(title) - self.show() - - def __getattr__(self, attr): - return getattr(self.cw, attr) - - -class PlotWindow(PlotWidget): - sigClosed = QtCore.Signal(object) - - """ - (deprecated; use :class:`~pyqtgraph.PlotWidget` instead) - """ - def __init__(self, title=None, **kargs): - warnings.warn( - 'PlotWindow is deprecated, use PlotWidget instead,' - 'will be removed in 0.13', - DeprecationWarning, stacklevel=2 - ) - mkQApp() - self.win = QtGui.QMainWindow() - PlotWidget.__init__(self, **kargs) - self.win.setCentralWidget(self) - for m in ['resize']: - setattr(self, m, getattr(self.win, m)) - if title is not None: - self.win.setWindowTitle(title) - self.win.show() - - def closeEvent(self, event): - PlotWidget.closeEvent(self, event) - self.sigClosed.emit(self) - - -class ImageWindow(ImageView): - sigClosed = QtCore.Signal(object) - - """ - (deprecated; use :class:`~pyqtgraph.ImageView` instead) - """ - def __init__(self, *args, **kargs): - warnings.warn( - 'ImageWindow is deprecated, use ImageView instead' - 'will be removed in 0.13', - DeprecationWarning, stacklevel=2 - ) - mkQApp() - self.win = QtGui.QMainWindow() - self.win.resize(800,600) - if 'title' in kargs: - self.win.setWindowTitle(kargs['title']) - del kargs['title'] - ImageView.__init__(self, self.win) - if len(args) > 0 or len(kargs) > 0: - self.setImage(*args, **kargs) - - self.win.setCentralWidget(self) - for m in ['resize']: - setattr(self, m, getattr(self.win, m)) - self.win.show() - - def closeEvent(self, event): - ImageView.closeEvent(self, event) - self.sigClosed.emit(self) diff --git a/pyqtgraph/icons/__init__.py b/pyqtgraph/icons/__init__.py deleted file mode 100644 index beae182..0000000 --- a/pyqtgraph/icons/__init__.py +++ /dev/null @@ -1,79 +0,0 @@ -import os.path as op -import warnings - -from ..Qt import QtGui - -__all__ = ['getGraphIcon', 'getGraphPixmap'] - -_ICON_REGISTRY = {} - - -class GraphIcon: - """An icon place holder for lazy loading of QIcons - - The icon must reside in the icons folder and the path refers to the full - name including suffix of the icon file, e.g.: - - tiny = GraphIcon("tiny.png") - - Icons can be later retrieved via the function `getGraphIcon` and providing - the name: - - tiny = getGraphIcon("tiny") - """ - - def __init__(self, path): - self._path = path - name = path.split('.')[0] - _ICON_REGISTRY[name] = self - self._icon = None - - def _build_qicon(self): - icon = QtGui.QIcon(op.join(op.dirname(__file__), self._path)) - name = self._path.split('.')[0] - _ICON_REGISTRY[name] = icon - self._icon = icon - - @property - def qicon(self): - if self._icon is None: - self._build_qicon() - - return self._icon - - -def getGraphIcon(name): - """Return a `PyQtGraph` icon from the registry by `name`""" - icon = _ICON_REGISTRY[name] - if isinstance(icon, GraphIcon): - icon = icon.qicon - _ICON_REGISTRY[name] = icon - - return icon - - -def getGraphPixmap(name, size=(20, 20)): - """Return a `QPixmap` from the registry by `name`""" - icon = getGraphIcon(name) - - return icon.pixmap(*size) - - -def getPixmap(name, size=(20, 20)): - """Historic `getPixmap` function - - (eg. getPixmap('auto') loads pyqtgraph/icons/auto.png) - """ - warnings.warn( - "'getPixmap' is deprecated and will be removed soon, " - "please use `getGraphPixmap` in the future", - DeprecationWarning, stacklevel=2) - return getGraphPixmap(name, size=size) - - -# Note: List all graph icons here ... -auto = GraphIcon("auto.png") -ctrl = GraphIcon("ctrl.png") -default = GraphIcon("default.png") -invisibleEye = GraphIcon("invisibleEye.svg") -lock = GraphIcon("lock.png") diff --git a/pyqtgraph/icons/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/icons/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 3e5d723..0000000 Binary files a/pyqtgraph/icons/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/icons/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/icons/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 19023c2..0000000 Binary files a/pyqtgraph/icons/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/icons/auto.png b/pyqtgraph/icons/auto.png deleted file mode 100644 index a27ff4f..0000000 Binary files a/pyqtgraph/icons/auto.png and /dev/null differ diff --git a/pyqtgraph/icons/ctrl.png b/pyqtgraph/icons/ctrl.png deleted file mode 100644 index c8dc96e..0000000 Binary files a/pyqtgraph/icons/ctrl.png and /dev/null differ diff --git a/pyqtgraph/icons/default.png b/pyqtgraph/icons/default.png deleted file mode 100644 index f123942..0000000 Binary files a/pyqtgraph/icons/default.png and /dev/null differ diff --git a/pyqtgraph/icons/icons.svg b/pyqtgraph/icons/icons.svg deleted file mode 100644 index cfdfeba..0000000 --- a/pyqtgraph/icons/icons.svg +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - A - - - - - - - - - - - diff --git a/pyqtgraph/icons/invisibleEye.svg b/pyqtgraph/icons/invisibleEye.svg deleted file mode 100644 index 5a67832..0000000 --- a/pyqtgraph/icons/invisibleEye.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - diff --git a/pyqtgraph/icons/lock.png b/pyqtgraph/icons/lock.png deleted file mode 100644 index 3f85dde..0000000 Binary files a/pyqtgraph/icons/lock.png and /dev/null differ diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py deleted file mode 100644 index 2cbafce..0000000 --- a/pyqtgraph/imageview/ImageView.py +++ /dev/null @@ -1,857 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ImageView.py - Widget for basic image dispay and analysis -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -Widget used for displaying 2D or 3D data. Features: - - float or int (including 16-bit int) image display via ImageItem - - zoom/pan via GraphicsView - - black/white level controls - - time slider for 3D data sets - - ROI plotting - - Image normalization through a variety of methods -""" -import os, sys -import numpy as np - -from ..Qt import QtCore, QtGui, QT_LIB -import importlib -ui_template = importlib.import_module( - f'.ImageViewTemplate_{QT_LIB.lower()}', package=__package__) - -from ..graphicsItems.ImageItem import * -from ..graphicsItems.ROI import * -from ..graphicsItems.LinearRegionItem import * -from ..graphicsItems.InfiniteLine import * -from ..graphicsItems.ViewBox import * -from ..graphicsItems.VTickGroup import VTickGroup -from ..graphicsItems.GradientEditorItem import addGradientListToDocstring -from .. import ptime as ptime -from .. import debug as debug -from ..SignalProxy import SignalProxy -from .. import getConfigOption - -try: - from bottleneck import nanmin, nanmax -except ImportError: - from numpy import nanmin, nanmax - -translate = QtCore.QCoreApplication.translate - -class PlotROI(ROI): - def __init__(self, size): - ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True) - self.addScaleHandle([1, 1], [0, 0]) - self.addRotateHandle([0, 0], [0.5, 0.5]) - - -class ImageView(QtGui.QWidget): - """ - Widget used for display and analysis of image data. - Implements many features: - - * Displays 2D and 3D image data. For 3D data, a z-axis - slider is displayed allowing the user to select which frame is displayed. - * Displays histogram of image data with movable region defining the dark/light levels - * Editable gradient provides a color lookup table - * Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end. - * Basic analysis features including: - - * ROI and embedded plot for measuring image values across frames - * Image normalization / background subtraction - - Basic Usage:: - - imv = pg.ImageView() - imv.show() - imv.setImage(data) - - **Keyboard interaction** - - * left/right arrows step forward/backward 1 frame when pressed, - seek at 20fps when held. - * up/down arrows seek at 100fps - * pgup/pgdn seek at 1000fps - * home/end seek immediately to the first/last frame - * space begins playing frames. If time values (in seconds) are given - for each frame, then playback is in realtime. - """ - sigTimeChanged = QtCore.Signal(object, object) - sigProcessingChanged = QtCore.Signal(object) - - def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, - levelMode='mono', *args): - """ - By default, this class creates an :class:`ImageItem ` to display image data - and a :class:`ViewBox ` to contain the ImageItem. - - ============= ========================================================= - **Arguments** - parent (QWidget) Specifies the parent widget to which - this ImageView will belong. If None, then the ImageView - is created with no parent. - name (str) The name used to register both the internal ViewBox - and the PlotItem used to display ROI data. See the *name* - argument to :func:`ViewBox.__init__() - `. - view (ViewBox or PlotItem) If specified, this will be used - as the display area that contains the displayed image. - Any :class:`ViewBox `, - :class:`PlotItem `, or other - compatible object is acceptable. - imageItem (ImageItem) If specified, this object will be used to - display the image. Must be an instance of ImageItem - or other compatible object. - levelMode See the *levelMode* argument to - :func:`HistogramLUTItem.__init__() - ` - ============= ========================================================= - - Note: to display axis ticks inside the ImageView, instantiate it - with a PlotItem instance as its view:: - - pg.ImageView(view=pg.PlotItem()) - """ - QtGui.QWidget.__init__(self, parent, *args) - self._imageLevels = None # [(min, max), ...] per channel image metrics - self.levelMin = None # min / max levels across all channels - self.levelMax = None - - self.name = name - self.image = None - self.axes = {} - self.imageDisp = None - self.ui = ui_template.Ui_Form() - self.ui.setupUi(self) - self.scene = self.ui.graphicsView.scene() - self.ui.histogram.setLevelMode(levelMode) - - self.ignorePlaying = False - - if view is None: - self.view = ViewBox() - else: - self.view = view - self.ui.graphicsView.setCentralItem(self.view) - self.view.setAspectLocked(True) - self.view.invertY() - - if imageItem is None: - self.imageItem = ImageItem() - else: - self.imageItem = imageItem - self.view.addItem(self.imageItem) - self.currentIndex = 0 - - self.ui.histogram.setImageItem(self.imageItem) - - self.menu = None - - self.ui.normGroup.hide() - - self.roi = PlotROI(10) - self.roi.setZValue(20) - self.view.addItem(self.roi) - self.roi.hide() - self.normRoi = PlotROI(10) - self.normRoi.setPen('y') - self.normRoi.setZValue(20) - self.view.addItem(self.normRoi) - self.normRoi.hide() - self.roiCurves = [] - self.roiCurve = self.ui.roiPlot.plot() - self.timeLine = InfiniteLine(0, movable=True) - if getConfigOption('background')=='w': - self.timeLine.setPen((20, 80,80, 200)) - else: - self.timeLine.setPen((255, 255, 0, 200)) - self.timeLine.setZValue(1) - self.ui.roiPlot.addItem(self.timeLine) - self.ui.splitter.setSizes([self.height()-35, 35]) - - # make splitter an unchangeable small grey line: - s = self.ui.splitter - s.handle(1).setEnabled(False) - s.setStyleSheet("QSplitter::handle{background-color: grey}") - s.setHandleWidth(2) - - self.ui.roiPlot.hideAxis('left') - self.frameTicks = VTickGroup(yrange=[0.8, 1], pen=0.4) - self.ui.roiPlot.addItem(self.frameTicks, ignoreBounds=True) - - self.keysPressed = {} - self.playTimer = QtCore.QTimer() - self.playRate = 0 - self.fps = 1 # 1 Hz by default - self.lastPlayTime = 0 - - self.normRgn = LinearRegionItem() - self.normRgn.setZValue(0) - self.ui.roiPlot.addItem(self.normRgn) - self.normRgn.hide() - - ## wrap functions from view box - for fn in ['addItem', 'removeItem']: - setattr(self, fn, getattr(self.view, fn)) - - ## wrap functions from histogram - for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: - setattr(self, fn, getattr(self.ui.histogram, fn)) - - self.timeLine.sigPositionChanged.connect(self.timeLineChanged) - self.ui.roiBtn.clicked.connect(self.roiClicked) - self.roi.sigRegionChanged.connect(self.roiChanged) - #self.ui.normBtn.toggled.connect(self.normToggled) - self.ui.menuBtn.clicked.connect(self.menuClicked) - self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) - self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) - self.ui.normOffRadio.clicked.connect(self.normRadioChanged) - self.ui.normROICheck.clicked.connect(self.updateNorm) - self.ui.normFrameCheck.clicked.connect(self.updateNorm) - self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) - self.playTimer.timeout.connect(self.timeout) - - self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) - self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) - - self.ui.roiPlot.registerPlot(self.name + '_ROI') - self.view.register(self.name) - - self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] - - self.roiClicked() ## initialize roi plot to correct shape / visibility - - def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True, levelMode=None): - """ - Set the image to be displayed in the widget. - - ================== =========================================================================== - **Arguments:** - img (numpy array) the image to be displayed. See :func:`ImageItem.setImage` and - *notes* below. - xvals (numpy array) 1D array of z-axis values corresponding to the first axis - in a 3D image. For video, this array should contain the time of each - frame. - autoRange (bool) whether to scale/pan the view to fit the image. - autoLevels (bool) whether to update the white/black levels to fit the image. - levels (min, max); the white and black level values to use. - axes Dictionary indicating the interpretation for each axis. - This is only needed to override the default guess. Format is:: - - {'t':0, 'x':1, 'y':2, 'c':3}; - - pos Change the position of the displayed image - scale Change the scale of the displayed image - transform Set the transform of the displayed image. This option overrides *pos* - and *scale*. - autoHistogramRange If True, the histogram y-range is automatically scaled to fit the - image data. - levelMode If specified, this sets the user interaction mode for setting image - levels. Options are 'mono', which provides a single level control for - all image channels, and 'rgb' or 'rgba', which provide individual - controls for each channel. - ================== =========================================================================== - - **Notes:** - - For backward compatibility, image data is assumed to be in column-major order (column, row). - However, most image data is stored in row-major order (row, column) and will need to be - transposed before calling setImage():: - - imageview.setImage(imagedata.T) - - This requirement can be changed by the ``imageAxisOrder`` - :ref:`global configuration option `. - - """ - profiler = debug.Profiler() - - if hasattr(img, 'implements') and img.implements('MetaArray'): - img = img.asarray() - - if not isinstance(img, np.ndarray): - required = ['dtype', 'max', 'min', 'ndim', 'shape', 'size'] - if not all([hasattr(img, attr) for attr in required]): - raise TypeError("Image must be NumPy array or any object " - "that provides compatible attributes/methods:\n" - " %s" % str(required)) - - self.image = img - self.imageDisp = None - if levelMode is not None: - self.ui.histogram.setLevelMode(levelMode) - - profiler() - - if axes is None: - x,y = (0, 1) if self.imageItem.axisOrder == 'col-major' else (1, 0) - - if img.ndim == 2: - self.axes = {'t': None, 'x': x, 'y': y, 'c': None} - elif img.ndim == 3: - # Ambiguous case; make a guess - if img.shape[2] <= 4: - self.axes = {'t': None, 'x': x, 'y': y, 'c': 2} - else: - self.axes = {'t': 0, 'x': x+1, 'y': y+1, 'c': None} - elif img.ndim == 4: - # Even more ambiguous; just assume the default - self.axes = {'t': 0, 'x': x+1, 'y': y+1, 'c': 3} - else: - raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) - elif isinstance(axes, dict): - self.axes = axes.copy() - elif isinstance(axes, list) or isinstance(axes, tuple): - self.axes = {} - for i in range(len(axes)): - self.axes[axes[i]] = i - else: - raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes))) - - for x in ['t', 'x', 'y', 'c']: - self.axes[x] = self.axes.get(x, None) - axes = self.axes - - if xvals is not None: - self.tVals = xvals - elif axes['t'] is not None: - if hasattr(img, 'xvals'): - try: - self.tVals = img.xvals(axes['t']) - except: - self.tVals = np.arange(img.shape[axes['t']]) - else: - self.tVals = np.arange(img.shape[axes['t']]) - - profiler() - - self.currentIndex = 0 - self.updateImage(autoHistogramRange=autoHistogramRange) - if levels is None and autoLevels: - self.autoLevels() - if levels is not None: ## this does nothing since getProcessedImage sets these values again. - self.setLevels(*levels) - - if self.ui.roiBtn.isChecked(): - self.roiChanged() - - profiler() - - if self.axes['t'] is not None: - self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) - self.frameTicks.setXVals(self.tVals) - self.timeLine.setValue(0) - if len(self.tVals) > 1: - start = self.tVals.min() - stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02 - elif len(self.tVals) == 1: - start = self.tVals[0] - 0.5 - stop = self.tVals[0] + 0.5 - else: - start = 0 - stop = 1 - for s in [self.timeLine, self.normRgn]: - s.setBounds([start, stop]) - - profiler() - - self.imageItem.resetTransform() - if scale is not None: - self.imageItem.scale(*scale) - if pos is not None: - self.imageItem.setPos(*pos) - if transform is not None: - self.imageItem.setTransform(transform) - - profiler() - - if autoRange: - self.autoRange() - self.roiClicked() - - profiler() - - def clear(self): - self.image = None - self.imageItem.clear() - - def play(self, rate=None): - """Begin automatically stepping frames forward at the given rate (in fps). - This can also be accessed by pressing the spacebar.""" - #print "play:", rate - if rate is None: - rate = self.fps - self.playRate = rate - - if rate == 0: - self.playTimer.stop() - return - - self.lastPlayTime = ptime.time() - if not self.playTimer.isActive(): - self.playTimer.start(16) - - def autoLevels(self): - """Set the min/max intensity levels automatically to match the image data.""" - self.setLevels(rgba=self._imageLevels) - - def setLevels(self, *args, **kwds): - """Set the min/max (bright and dark) levels. - - See :func:`HistogramLUTItem.setLevels `. - """ - self.ui.histogram.setLevels(*args, **kwds) - - def autoRange(self): - """Auto scale and pan the view around the image such that the image fills the view.""" - image = self.getProcessedImage() - self.view.autoRange() - - def getProcessedImage(self): - """Returns the image data after it has been processed by any normalization options in use. - """ - if self.imageDisp is None: - image = self.normalize(self.image) - self.imageDisp = image - self._imageLevels = self.quickMinMax(self.imageDisp) - self.levelMin = min([level[0] for level in self._imageLevels]) - self.levelMax = max([level[1] for level in self._imageLevels]) - - return self.imageDisp - - def close(self): - """Closes the widget nicely, making sure to clear the graphics scene and release memory.""" - self.clear() - self.imageDisp = None - self.imageItem.setParent(None) - super(ImageView, self).close() - self.setParent(None) - - def keyPressEvent(self, ev): - if not self.hasTimeAxis(): - super().keyPressEvent(ev) - return - - if ev.key() == QtCore.Qt.Key_Space: - if self.playRate == 0: - self.play() - else: - self.play(0) - ev.accept() - elif ev.key() == QtCore.Qt.Key_Home: - self.setCurrentIndex(0) - self.play(0) - ev.accept() - elif ev.key() == QtCore.Qt.Key_End: - self.setCurrentIndex(self.getProcessedImage().shape[0]-1) - self.play(0) - ev.accept() - elif ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - self.keysPressed[ev.key()] = 1 - self.evalKeyState() - else: - super().keyPressEvent(ev) - - def keyReleaseEvent(self, ev): - if not self.hasTimeAxis(): - super().keyReleaseEvent(ev) - return - - if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]: - ev.accept() - elif ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - try: - del self.keysPressed[ev.key()] - except: - self.keysPressed = {} - self.evalKeyState() - else: - super().keyReleaseEvent(ev) - - def evalKeyState(self): - if len(self.keysPressed) == 1: - key = list(self.keysPressed.keys())[0] - if key == QtCore.Qt.Key_Right: - self.play(20) - self.jumpFrames(1) - self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start - ## This happens *after* jumpFrames, since it might take longer than 2ms - elif key == QtCore.Qt.Key_Left: - self.play(-20) - self.jumpFrames(-1) - self.lastPlayTime = ptime.time() + 0.2 - elif key == QtCore.Qt.Key_Up: - self.play(-100) - elif key == QtCore.Qt.Key_Down: - self.play(100) - elif key == QtCore.Qt.Key_PageUp: - self.play(-1000) - elif key == QtCore.Qt.Key_PageDown: - self.play(1000) - else: - self.play(0) - - def timeout(self): - now = ptime.time() - dt = now - self.lastPlayTime - if dt < 0: - return - n = int(self.playRate * dt) - if n != 0: - self.lastPlayTime += (float(n)/self.playRate) - if self.currentIndex+n > self.image.shape[self.axes['t']]: - self.play(0) - self.jumpFrames(n) - - def setCurrentIndex(self, ind): - """Set the currently displayed frame index.""" - index = np.clip(ind, 0, self.getProcessedImage().shape[self.axes['t']]-1) - self.ignorePlaying = True - # Implicitly call timeLineChanged - self.timeLine.setValue(self.tVals[index]) - self.ignorePlaying = False - - def jumpFrames(self, n): - """Move video frame ahead n frames (may be negative)""" - if self.axes['t'] is not None: - self.setCurrentIndex(self.currentIndex + n) - - def normRadioChanged(self): - self.imageDisp = None - self.updateImage() - self.autoLevels() - self.roiChanged() - self.sigProcessingChanged.emit(self) - - def updateNorm(self): - if self.ui.normTimeRangeCheck.isChecked(): - self.normRgn.show() - else: - self.normRgn.hide() - - if self.ui.normROICheck.isChecked(): - self.normRoi.show() - else: - self.normRoi.hide() - - if not self.ui.normOffRadio.isChecked(): - self.imageDisp = None - self.updateImage() - self.autoLevels() - self.roiChanged() - self.sigProcessingChanged.emit(self) - - def normToggled(self, b): - self.ui.normGroup.setVisible(b) - self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) - self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) - - def hasTimeAxis(self): - return 't' in self.axes and self.axes['t'] is not None - - def roiClicked(self): - showRoiPlot = False - if self.ui.roiBtn.isChecked(): - showRoiPlot = True - self.roi.show() - #self.ui.roiPlot.show() - self.ui.roiPlot.setMouseEnabled(True, True) - self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4]) - self.ui.splitter.handle(1).setEnabled(True) - self.roiCurve.show() - self.roiChanged() - self.ui.roiPlot.showAxis('left') - else: - self.roi.hide() - self.ui.roiPlot.setMouseEnabled(False, False) - for c in self.roiCurves: - c.hide() - self.ui.roiPlot.hideAxis('left') - - if self.hasTimeAxis(): - showRoiPlot = True - mn = self.tVals.min() - mx = self.tVals.max() - self.ui.roiPlot.setXRange(mn, mx, padding=0.01) - self.timeLine.show() - self.timeLine.setBounds([mn, mx]) - self.ui.roiPlot.show() - if not self.ui.roiBtn.isChecked(): - self.ui.splitter.setSizes([self.height()-35, 35]) - self.ui.splitter.handle(1).setEnabled(False) - else: - self.timeLine.hide() - #self.ui.roiPlot.hide() - - self.ui.roiPlot.setVisible(showRoiPlot) - - def roiChanged(self): - # Extract image data from ROI - if self.image is None: - return - - image = self.getProcessedImage() - - # getArrayRegion axes should be (x, y) of data array for col-major, - # (y, x) for row-major - # can't just transpose input because ROI is axisOrder aware - colmaj = self.imageItem.axisOrder == 'col-major' - if colmaj: - axes = (self.axes['x'], self.axes['y']) - else: - axes = (self.axes['y'], self.axes['x']) - - data, coords = self.roi.getArrayRegion( - image.view(np.ndarray), img=self.imageItem, axes=axes, - returnMappedCoords=True) - - if data is None: - return - - # Convert extracted data into 1D plot data - if self.axes['t'] is None: - # Average across y-axis of ROI - data = data.mean(axis=self.axes['y']) - - # get coordinates along x axis of ROI mapped to range (0, roiwidth) - if colmaj: - coords = coords[:, :, 0] - coords[:, 0:1, 0] - else: - coords = coords[:, 0, :] - coords[:, 0, 0:1] - xvals = (coords**2).sum(axis=0) ** 0.5 - else: - # Average data within entire ROI for each frame - data = data.mean(axis=axes) - xvals = self.tVals - - # Handle multi-channel data - if data.ndim == 1: - plots = [(xvals, data, 'w')] - if data.ndim == 2: - if data.shape[1] == 1: - colors = 'w' - else: - colors = 'rgbw' - plots = [] - for i in range(data.shape[1]): - d = data[:,i] - plots.append((xvals, d, colors[i])) - - # Update plot line(s) - while len(plots) < len(self.roiCurves): - c = self.roiCurves.pop() - c.scene().removeItem(c) - while len(plots) > len(self.roiCurves): - self.roiCurves.append(self.ui.roiPlot.plot()) - for i in range(len(plots)): - x, y, p = plots[i] - self.roiCurves[i].setData(x, y, pen=p) - - def quickMinMax(self, data): - """ - Estimate the min/max values of *data* by subsampling. - Returns [(min, max), ...] with one item per channel - """ - while data.size > 1e6: - ax = np.argmax(data.shape) - sl = [slice(None)] * data.ndim - sl[ax] = slice(None, None, 2) - data = data[tuple(sl)] - - cax = self.axes['c'] - if cax is None: - if data.size == 0: - return [(0, 0)] - return [(float(nanmin(data)), float(nanmax(data)))] - else: - if data.size == 0: - return [(0, 0)] * data.shape[-1] - return [(float(nanmin(data.take(i, axis=cax))), - float(nanmax(data.take(i, axis=cax)))) for i in range(data.shape[-1])] - - def normalize(self, image): - """ - Process *image* using the normalization options configured in the - control panel. - - This can be repurposed to process any data through the same filter. - """ - if self.ui.normOffRadio.isChecked(): - return image - - div = self.ui.normDivideRadio.isChecked() - norm = image.view(np.ndarray).copy() - #if div: - #norm = ones(image.shape) - #else: - #norm = zeros(image.shape) - if div: - norm = norm.astype(np.float32) - - if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3: - (sind, start) = self.timeIndex(self.normRgn.lines[0]) - (eind, end) = self.timeIndex(self.normRgn.lines[1]) - #print start, end, sind, eind - n = image[sind:eind+1].mean(axis=0) - n.shape = (1,) + n.shape - if div: - norm /= n - else: - norm -= n - - if self.ui.normFrameCheck.isChecked() and image.ndim == 3: - n = image.mean(axis=1).mean(axis=1) - n.shape = n.shape + (1, 1) - if div: - norm /= n - else: - norm -= n - - if self.ui.normROICheck.isChecked() and image.ndim == 3: - n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1) - n = n[:,np.newaxis,np.newaxis] - #print start, end, sind, eind - if div: - norm /= n - else: - norm -= n - - return norm - - def timeLineChanged(self): - if not self.ignorePlaying: - self.play(0) - - (ind, time) = self.timeIndex(self.timeLine) - if ind != self.currentIndex: - self.currentIndex = ind - self.updateImage() - self.sigTimeChanged.emit(ind, time) - - def updateImage(self, autoHistogramRange=True): - ## Redraw image on screen - if self.image is None: - return - - image = self.getProcessedImage() - - if autoHistogramRange: - self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) - - # Transpose image into order expected by ImageItem - if self.imageItem.axisOrder == 'col-major': - axorder = ['t', 'x', 'y', 'c'] - else: - axorder = ['t', 'y', 'x', 'c'] - axorder = [self.axes[ax] for ax in axorder if self.axes[ax] is not None] - image = image.transpose(axorder) - - # Select time index - if self.axes['t'] is not None: - self.ui.roiPlot.show() - image = image[self.currentIndex] - - self.imageItem.updateImage(image) - - - def timeIndex(self, slider): - ## Return the time and frame index indicated by a slider - if not self.hasTimeAxis(): - return (0,0) - - t = slider.value() - - xv = self.tVals - if xv is None: - ind = int(t) - else: - if len(xv) < 2: - return (0,0) - totTime = xv[-1] + (xv[-1]-xv[-2]) - inds = np.argwhere(xv <= t) - if len(inds) < 1: - return (0,t) - ind = inds[-1,0] - return ind, t - - def getView(self): - """Return the ViewBox (or other compatible object) which displays the ImageItem""" - return self.view - - def getImageItem(self): - """Return the ImageItem for this ImageView.""" - return self.imageItem - - def getRoiPlot(self): - """Return the ROI PlotWidget for this ImageView""" - return self.ui.roiPlot - - def getHistogramWidget(self): - """Return the HistogramLUTWidget for this ImageView""" - return self.ui.histogram - - def export(self, fileName): - """ - Export data from the ImageView to a file, or to a stack of files if - the data is 3D. Saving an image stack will result in index numbers - being added to the file name. Images are saved as they would appear - onscreen, with levels and lookup table applied. - """ - img = self.getProcessedImage() - if self.hasTimeAxis(): - base, ext = os.path.splitext(fileName) - fmt = "%%s%%0%dd%%s" % int(np.log10(img.shape[0])+1) - for i in range(img.shape[0]): - self.imageItem.setImage(img[i], autoLevels=False) - self.imageItem.save(fmt % (base, i, ext)) - self.updateImage() - else: - self.imageItem.save(fileName) - - def exportClicked(self): - fileName = QtGui.QFileDialog.getSaveFileName() - if isinstance(fileName, tuple): - fileName = fileName[0] # Qt4/5 API difference - if fileName == '': - return - self.export(str(fileName)) - - def buildMenu(self): - self.menu = QtGui.QMenu() - self.normAction = QtGui.QAction(translate("ImageView", "Normalization"), self.menu) - self.normAction.setCheckable(True) - self.normAction.toggled.connect(self.normToggled) - self.menu.addAction(self.normAction) - self.exportAction = QtGui.QAction(translate("ImageView", "Export"), self.menu) - self.exportAction.triggered.connect(self.exportClicked) - self.menu.addAction(self.exportAction) - - def menuClicked(self): - if self.menu is None: - self.buildMenu() - self.menu.popup(QtGui.QCursor.pos()) - - def setColorMap(self, colormap): - """Set the color map. - - ============= ========================================================= - **Arguments** - colormap (A ColorMap() instance) The ColorMap to use for coloring - images. - ============= ========================================================= - """ - self.ui.histogram.gradient.setColorMap(colormap) - - @addGradientListToDocstring() - def setPredefinedGradient(self, name): - """Set one of the gradients defined in :class:`GradientEditorItem `. - Currently available gradients are: - """ - self.ui.histogram.gradient.loadPreset(name) diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt5.py b/pyqtgraph/imageview/ImageViewTemplate_pyqt5.py deleted file mode 100644 index 87f3f25..0000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyqt5.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/imageview/ImageViewTemplate.ui' -# -# Created: Wed Mar 26 15:09:28 2014 -# by: PyQt5 UI code generator 5.0.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(726, 588) - self.gridLayout_3 = QtWidgets.QGridLayout(Form) - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName("gridLayout_3") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName("histogram") - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName("roiBtn") - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) - self.menuBtn.setSizePolicy(sizePolicy) - self.menuBtn.setCheckable(True) - self.menuBtn.setObjectName("menuBtn") - self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName("roiPlot") - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - self.normGroup = QtWidgets.QGroupBox(Form) - self.normGroup.setObjectName("normGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName("normSubtractRadio") - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName("normDivideRadio") - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_5.setFont(font) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_4.setFont(font) - self.label_4.setObjectName("label_4") - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtWidgets.QCheckBox(self.normGroup) - self.normROICheck.setObjectName("normROICheck") - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName("normXBlurSpin") - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtWidgets.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtWidgets.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName("normYBlurSpin") - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtWidgets.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName("normOffRadio") - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName("normFrameCheck") - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName("normTBlurSpin") - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.roiBtn.setText(_translate("Form", "ROI")) - self.menuBtn.setText(_translate("Form", "Norm")) - self.normGroup.setTitle(_translate("Form", "Normalization")) - self.normSubtractRadio.setText(_translate("Form", "Subtract")) - self.normDivideRadio.setText(_translate("Form", "Divide")) - self.label_5.setText(_translate("Form", "Operation:")) - self.label_3.setText(_translate("Form", "Mean:")) - self.label_4.setText(_translate("Form", "Blur:")) - self.normROICheck.setText(_translate("Form", "ROI")) - self.label_8.setText(_translate("Form", "X")) - self.label_9.setText(_translate("Form", "Y")) - self.label_10.setText(_translate("Form", "T")) - self.normOffRadio.setText(_translate("Form", "Off")) - self.normTimeRangeCheck.setText(_translate("Form", "Time range")) - self.normFrameCheck.setText(_translate("Form", "Frame")) - -from ..widgets.HistogramLUTWidget import HistogramLUTWidget -from ..widgets.PlotWidget import PlotWidget -from ..widgets.GraphicsView import GraphicsView diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py b/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py deleted file mode 100644 index c27aa23..0000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py +++ /dev/null @@ -1,151 +0,0 @@ -# Form implementation generated from reading ui file 'pyqtgraph\imageview\ImageViewTemplate.ui' -# -# Created by: PyQt6 UI code generator 6.0.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(726, 588) - self.gridLayout_3 = QtWidgets.QGridLayout(Form) - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName("gridLayout_3") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Orientations.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName("histogram") - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName("roiBtn") - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) - self.menuBtn.setSizePolicy(sizePolicy) - self.menuBtn.setObjectName("menuBtn") - self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName("roiPlot") - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - self.normGroup = QtWidgets.QGroupBox(Form) - self.normGroup.setObjectName("normGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName("normSubtractRadio") - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName("normDivideRadio") - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - self.label_5.setFont(font) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - self.label_4.setFont(font) - self.label_4.setObjectName("label_4") - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtWidgets.QCheckBox(self.normGroup) - self.normROICheck.setObjectName("normROICheck") - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName("normXBlurSpin") - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtWidgets.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtWidgets.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName("normYBlurSpin") - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtWidgets.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName("normOffRadio") - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName("normFrameCheck") - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName("normTBlurSpin") - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - _translate = QtCore.QCoreApplication.translate - Form.setWindowTitle(_translate("Form", "PyQtGraph")) - self.roiBtn.setText(_translate("Form", "ROI")) - self.menuBtn.setText(_translate("Form", "Menu")) - self.normGroup.setTitle(_translate("Form", "Normalization")) - self.normSubtractRadio.setText(_translate("Form", "Subtract")) - self.normDivideRadio.setText(_translate("Form", "Divide")) - self.label_5.setText(_translate("Form", "Operation:")) - self.label_3.setText(_translate("Form", "Mean:")) - self.label_4.setText(_translate("Form", "Blur:")) - self.normROICheck.setText(_translate("Form", "ROI")) - self.label_8.setText(_translate("Form", "X")) - self.label_9.setText(_translate("Form", "Y")) - self.label_10.setText(_translate("Form", "T")) - self.normOffRadio.setText(_translate("Form", "Off")) - self.normTimeRangeCheck.setText(_translate("Form", "Time range")) - self.normFrameCheck.setText(_translate("Form", "Frame")) -from ..widgets.GraphicsView import GraphicsView -from ..widgets.HistogramLUTWidget import HistogramLUTWidget -from ..widgets.PlotWidget import PlotWidget diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyside2.py b/pyqtgraph/imageview/ImageViewTemplate_pyside2.py deleted file mode 100644 index cfe400c..0000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyside2.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ImageViewTemplate.ui' -# -# Created: Sun Sep 18 19:17:41 2016 -# by: pyside2-uic running on PySide2 2.0.0~alpha0 -# -# WARNING! All changes made in this file will be lost! - -from PySide2 import QtCore, QtGui, QtWidgets - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(726, 588) - self.gridLayout_3 = QtWidgets.QGridLayout(Form) - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName("gridLayout_3") - self.splitter = QtWidgets.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtWidgets.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName("histogram") - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName("roiBtn") - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) - self.menuBtn.setSizePolicy(sizePolicy) - self.menuBtn.setObjectName("menuBtn") - self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName("roiPlot") - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - self.normGroup = QtWidgets.QGroupBox(Form) - self.normGroup.setObjectName("normGroup") - self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName("normSubtractRadio") - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName("normDivideRadio") - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_5.setFont(font) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtWidgets.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_4.setFont(font) - self.label_4.setObjectName("label_4") - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtWidgets.QCheckBox(self.normGroup) - self.normROICheck.setObjectName("normROICheck") - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName("normXBlurSpin") - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtWidgets.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtWidgets.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName("normYBlurSpin") - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtWidgets.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName("normOffRadio") - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName("normFrameCheck") - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName("normTBlurSpin") - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1)) - self.roiBtn.setText(QtWidgets.QApplication.translate("Form", "ROI", None, -1)) - self.menuBtn.setText(QtWidgets.QApplication.translate("Form", "Menu", None, -1)) - self.normGroup.setTitle(QtWidgets.QApplication.translate("Form", "Normalization", None, -1)) - self.normSubtractRadio.setText(QtWidgets.QApplication.translate("Form", "Subtract", None, -1)) - self.normDivideRadio.setText(QtWidgets.QApplication.translate("Form", "Divide", None, -1)) - self.label_5.setText(QtWidgets.QApplication.translate("Form", "Operation:", None, -1)) - self.label_3.setText(QtWidgets.QApplication.translate("Form", "Mean:", None, -1)) - self.label_4.setText(QtWidgets.QApplication.translate("Form", "Blur:", None, -1)) - self.normROICheck.setText(QtWidgets.QApplication.translate("Form", "ROI", None, -1)) - self.label_8.setText(QtWidgets.QApplication.translate("Form", "X", None, -1)) - self.label_9.setText(QtWidgets.QApplication.translate("Form", "Y", None, -1)) - self.label_10.setText(QtWidgets.QApplication.translate("Form", "T", None, -1)) - self.normOffRadio.setText(QtWidgets.QApplication.translate("Form", "Off", None, -1)) - self.normTimeRangeCheck.setText(QtWidgets.QApplication.translate("Form", "Time range", None, -1)) - self.normFrameCheck.setText(QtWidgets.QApplication.translate("Form", "Frame", None, -1)) - -from ..widgets.HistogramLUTWidget import HistogramLUTWidget -from ..widgets.PlotWidget import PlotWidget -from ..widgets.GraphicsView import GraphicsView diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyside6.py b/pyqtgraph/imageview/ImageViewTemplate_pyside6.py deleted file mode 100644 index e26e602..0000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyside6.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'ImageViewTemplate.ui' -## -## Created by: Qt User Interface Compiler version 6.0.0 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import * -from PySide6.QtGui import * -from PySide6.QtWidgets import * - -from ..widgets.PlotWidget import PlotWidget -from ..widgets.GraphicsView import GraphicsView -from ..widgets.HistogramLUTWidget import HistogramLUTWidget - - -class Ui_Form(object): - def setupUi(self, Form): - if not Form.objectName(): - Form.setObjectName(u"Form") - Form.resize(726, 588) - self.gridLayout_3 = QGridLayout(Form) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.splitter = QSplitter(Form) - self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Vertical) - self.layoutWidget = QWidget(self.splitter) - self.layoutWidget.setObjectName(u"layoutWidget") - self.gridLayout = QGridLayout(self.layoutWidget) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(u"gridLayout") - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName(u"graphicsView") - - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName(u"histogram") - - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - - self.roiBtn = QPushButton(self.layoutWidget) - self.roiBtn.setObjectName(u"roiBtn") - sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - - self.menuBtn = QPushButton(self.layoutWidget) - self.menuBtn.setObjectName(u"menuBtn") - sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) - self.menuBtn.setSizePolicy(sizePolicy) - - self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) - - self.splitter.addWidget(self.layoutWidget) - self.roiPlot = PlotWidget(self.splitter) - self.roiPlot.setObjectName(u"roiPlot") - sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy1) - self.roiPlot.setMinimumSize(QSize(0, 40)) - self.splitter.addWidget(self.roiPlot) - - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - - self.normGroup = QGroupBox(Form) - self.normGroup.setObjectName(u"normGroup") - self.gridLayout_2 = QGridLayout(self.normGroup) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.normSubtractRadio = QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName(u"normSubtractRadio") - - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - - self.normDivideRadio = QRadioButton(self.normGroup) - self.normDivideRadio.setObjectName(u"normDivideRadio") - self.normDivideRadio.setChecked(False) - - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - - self.label_5 = QLabel(self.normGroup) - self.label_5.setObjectName(u"label_5") - font = QFont() - font.setBold(True) - self.label_5.setFont(font) - - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - - self.label_3 = QLabel(self.normGroup) - self.label_3.setObjectName(u"label_3") - self.label_3.setFont(font) - - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - - self.label_4 = QLabel(self.normGroup) - self.label_4.setObjectName(u"label_4") - self.label_4.setFont(font) - - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - - self.normROICheck = QCheckBox(self.normGroup) - self.normROICheck.setObjectName(u"normROICheck") - - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - - self.normXBlurSpin = QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName(u"normXBlurSpin") - - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - - self.label_8 = QLabel(self.normGroup) - self.label_8.setObjectName(u"label_8") - self.label_8.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - - self.label_9 = QLabel(self.normGroup) - self.label_9.setObjectName(u"label_9") - self.label_9.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - - self.normYBlurSpin = QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName(u"normYBlurSpin") - - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - - self.label_10 = QLabel(self.normGroup) - self.label_10.setObjectName(u"label_10") - self.label_10.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - - self.normOffRadio = QRadioButton(self.normGroup) - self.normOffRadio.setObjectName(u"normOffRadio") - self.normOffRadio.setChecked(True) - - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - - self.normTimeRangeCheck = QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName(u"normTimeRangeCheck") - - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - - self.normFrameCheck = QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName(u"normFrameCheck") - - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - - self.normTBlurSpin = QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName(u"normTBlurSpin") - - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - - - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - - self.retranslateUi(Form) - - QMetaObject.connectSlotsByName(Form) - # setupUi - - def retranslateUi(self, Form): - Form.setWindowTitle(QCoreApplication.translate("Form", u"PyQtGraph", None)) - self.roiBtn.setText(QCoreApplication.translate("Form", u"ROI", None)) - self.menuBtn.setText(QCoreApplication.translate("Form", u"Menu", None)) - self.normGroup.setTitle(QCoreApplication.translate("Form", u"Normalization", None)) - self.normSubtractRadio.setText(QCoreApplication.translate("Form", u"Subtract", None)) - self.normDivideRadio.setText(QCoreApplication.translate("Form", u"Divide", None)) - self.label_5.setText(QCoreApplication.translate("Form", u"Operation:", None)) - self.label_3.setText(QCoreApplication.translate("Form", u"Mean:", None)) - self.label_4.setText(QCoreApplication.translate("Form", u"Blur:", None)) - self.normROICheck.setText(QCoreApplication.translate("Form", u"ROI", None)) - self.label_8.setText(QCoreApplication.translate("Form", u"X", None)) - self.label_9.setText(QCoreApplication.translate("Form", u"Y", None)) - self.label_10.setText(QCoreApplication.translate("Form", u"T", None)) - self.normOffRadio.setText(QCoreApplication.translate("Form", u"Off", None)) - self.normTimeRangeCheck.setText(QCoreApplication.translate("Form", u"Time range", None)) - self.normFrameCheck.setText(QCoreApplication.translate("Form", u"Frame", None)) - # retranslateUi - diff --git a/pyqtgraph/imageview/__init__.py b/pyqtgraph/imageview/__init__.py deleted file mode 100644 index 0060e82..0000000 --- a/pyqtgraph/imageview/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Widget used for display and analysis of 2D and 3D image data. -Includes ROI plotting over time and image normalization. -""" - -from .ImageView import ImageView diff --git a/pyqtgraph/imageview/__pycache__/ImageView.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/ImageView.cpython-36.pyc deleted file mode 100644 index a1d2a24..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageView.cpython-37.pyc b/pyqtgraph/imageview/__pycache__/ImageView.cpython-37.pyc deleted file mode 100644 index ca36938..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageView.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt5.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt5.cpython-36.pyc deleted file mode 100644 index ba4c98e..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt5.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt6.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt6.cpython-36.pyc deleted file mode 100644 index d6596b3..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyqt6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside2.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside2.cpython-36.pyc deleted file mode 100644 index b897e44..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside2.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-36.pyc deleted file mode 100644 index df7d17a..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-37.pyc b/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-37.pyc deleted file mode 100644 index e091669..0000000 Binary files a/pyqtgraph/imageview/__pycache__/ImageViewTemplate_pyside6.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/imageview/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 0a70237..0000000 Binary files a/pyqtgraph/imageview/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/imageview/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/imageview/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 4d2ab29..0000000 Binary files a/pyqtgraph/imageview/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/metaarray/MetaArray.py b/pyqtgraph/metaarray/MetaArray.py deleted file mode 100644 index 169ff43..0000000 --- a/pyqtgraph/metaarray/MetaArray.py +++ /dev/null @@ -1,1373 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MetaArray.py - Class encapsulating ndarray with meta data -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -MetaArray is an array class based on numpy.ndarray that allows storage of per-axis meta data -such as axis values, names, units, column names, etc. It also enables several -new methods for slicing and indexing the array based on this meta data. -More info at http://www.scipy.org/Cookbook/MetaArray -""" - -import types, copy, threading, os, re -import pickle -import numpy as np -from ..python2_3 import basestring -import warnings - - -## By default, the library will use HDF5 when writing files. -## This can be overridden by setting USE_HDF5 = False -USE_HDF5 = True -try: - import h5py - - # Older h5py versions tucked Group and Dataset deeper inside the library: - if not hasattr(h5py, 'Group'): - import h5py.highlevel - h5py.Group = h5py.highlevel.Group - h5py.Dataset = h5py.highlevel.Dataset - - HAVE_HDF5 = True -except: - USE_HDF5 = False - HAVE_HDF5 = False - - -def axis(name=None, cols=None, values=None, units=None): - """Convenience function for generating axis descriptions when defining MetaArrays""" - ax = {} - cNameOrder = ['name', 'units', 'title'] - if name is not None: - ax['name'] = name - if values is not None: - ax['values'] = values - if units is not None: - ax['units'] = units - if cols is not None: - ax['cols'] = [] - for c in cols: - if type(c) != list and type(c) != tuple: - c = [c] - col = {} - for i in range(0,len(c)): - col[cNameOrder[i]] = c[i] - ax['cols'].append(col) - return ax - -class sliceGenerator(object): - """Just a compact way to generate tuples of slice objects.""" - def __getitem__(self, arg): - return arg - def __getslice__(self, arg): - return arg -SLICER = sliceGenerator() - - -class MetaArray(object): - """N-dimensional array with meta data such as axis titles, units, and column names. - - May be initialized with a file name, a tuple representing the dimensions of the array, - or any arguments that could be passed on to numpy.array() - - The info argument sets the metadata for the entire array. It is composed of a list - of axis descriptions where each axis may have a name, title, units, and a list of column - descriptions. An additional dict at the end of the axis list may specify parameters - that apply to values in the entire array. - - For example: - A 2D array of altitude values for a topographical map might look like - info=[ - {'name': 'lat', 'title': 'Lattitude'}, - {'name': 'lon', 'title': 'Longitude'}, - {'title': 'Altitude', 'units': 'm'} - ] - In this case, every value in the array represents the altitude in feet at the lat, lon - position represented by the array index. All of the following return the - value at lat=10, lon=5: - array[10, 5] - array['lon':5, 'lat':10] - array['lat':10][5] - Now suppose we want to combine this data with another array of equal dimensions that - represents the average rainfall for each location. We could easily store these as two - separate arrays or combine them into a 3D array with this description: - info=[ - {'name': 'vals', 'cols': [ - {'name': 'altitude', 'units': 'm'}, - {'name': 'rainfall', 'units': 'cm/year'} - ]}, - {'name': 'lat', 'title': 'Lattitude'}, - {'name': 'lon', 'title': 'Longitude'} - ] - We can now access the altitude values with array[0] or array['altitude'], and the - rainfall values with array[1] or array['rainfall']. All of the following return - the rainfall value at lat=10, lon=5: - array[1, 10, 5] - array['lon':5, 'lat':10, 'val': 'rainfall'] - array['rainfall', 'lon':5, 'lat':10] - Notice that in the second example, there is no need for an extra (4th) axis description - since the actual values are described (name and units) in the column info for the first axis. - """ - - version = u'2' - - # Default hdf5 compression to use when writing - # 'gzip' is widely available and somewhat slow - # 'lzf' is faster, but generally not available outside h5py - # 'szip' is also faster, but lacks write support on windows - # (so by default, we use no compression) - # May also be a tuple (filter, opts), such as ('gzip', 3) - defaultCompression = None - - ## Types allowed as axis or column names - nameTypes = [basestring, tuple] - @staticmethod - def isNameType(var): - return any([isinstance(var, t) for t in MetaArray.nameTypes]) - - - ## methods to wrap from embedded ndarray / HDF5 - wrapMethods = set(['__eq__', '__ne__', '__le__', '__lt__', '__ge__', '__gt__']) - - def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kwargs): - object.__init__(self) - self._isHDF = False - - if file is not None: - self._data = None - self.readFile(file, **kwargs) - if kwargs.get("readAllData", True) and self._data is None: - raise Exception("File read failed: %s" % file) - else: - self._info = info - if (hasattr(data, 'implements') and data.implements('MetaArray')): - self._info = data._info - self._data = data.asarray() - elif isinstance(data, tuple): ## create empty array with specified shape - self._data = np.empty(data, dtype=dtype) - else: - self._data = np.array(data, dtype=dtype, copy=copy) - - ## run sanity checks on info structure - self.checkInfo() - - def checkInfo(self): - info = self._info - if info is None: - if self._data is None: - return - else: - self._info = [{} for i in range(self.ndim + 1)] - return - else: - try: - info = list(info) - except: - raise Exception("Info must be a list of axis specifications") - if len(info) < self.ndim+1: - info.extend([{}]*(self.ndim+1-len(info))) - elif len(info) > self.ndim+1: - raise Exception("Info parameter must be list of length ndim+1 or less.") - for i in range(len(info)): - if not isinstance(info[i], dict): - if info[i] is None: - info[i] = {} - else: - raise Exception("Axis specification must be Dict or None") - if i < self.ndim and 'values' in info[i]: - if type(info[i]['values']) is list: - info[i]['values'] = np.array(info[i]['values']) - elif type(info[i]['values']) is not np.ndarray: - raise Exception("Axis values must be specified as list or ndarray") - if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != self.shape[i]: - raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % - (i, str(info[i]['values'].shape), str((self.shape[i],)))) - if i < self.ndim and 'cols' in info[i]: - if not isinstance(info[i]['cols'], list): - info[i]['cols'] = list(info[i]['cols']) - if len(info[i]['cols']) != self.shape[i]: - raise Exception('Length of column list for axis %d does not match data. (given %d, but should be %d)' % - (i, len(info[i]['cols']), self.shape[i])) - self._info = info - - def implements(self, name=None): - ## Rather than isinstance(obj, MetaArray) use object.implements('MetaArray') - if name is None: - return ['MetaArray'] - else: - return name == 'MetaArray' - - def __getitem__(self, ind): - nInd = self._interpretIndexes(ind) - - a = self._data[nInd] - if len(nInd) == self.ndim: - if np.all([not isinstance(ind, (slice, np.ndarray)) for ind in nInd]): ## no slices; we have requested a single value from the array - return a - - ## indexing returned a sub-array; generate new info array to go with it - info = [] - extraInfo = self._info[-1].copy() - for i in range(0, len(nInd)): ## iterate over all axes - if type(nInd[i]) in [slice, list] or isinstance(nInd[i], np.ndarray): ## If the axis is sliced, keep the info but chop if necessary - info.append(self._axisSlice(i, nInd[i])) - else: ## If the axis is indexed, then move the information from that single index to the last info dictionary - newInfo = self._axisSlice(i, nInd[i]) - name = None - colName = None - for k in newInfo: - if k == 'cols': - if 'cols' not in extraInfo: - extraInfo['cols'] = [] - extraInfo['cols'].append(newInfo[k]) - if 'units' in newInfo[k]: - extraInfo['units'] = newInfo[k]['units'] - if 'name' in newInfo[k]: - colName = newInfo[k]['name'] - elif k == 'name': - name = newInfo[k] - else: - if k not in extraInfo: - extraInfo[k] = newInfo[k] - extraInfo[k] = newInfo[k] - if 'name' not in extraInfo: - if name is None: - if colName is not None: - extraInfo['name'] = colName - else: - if colName is not None: - extraInfo['name'] = str(name) + ': ' + str(colName) - else: - extraInfo['name'] = name - - info.append(extraInfo) - - return MetaArray(a, info=info) - - @property - def ndim(self): - return len(self.shape) ## hdf5 objects do not have ndim property. - - @property - def shape(self): - return self._data.shape - - @property - def dtype(self): - return self._data.dtype - - def __len__(self): - return len(self._data) - - def __getslice__(self, *args): - return self.__getitem__(slice(*args)) - - def __setitem__(self, ind, val): - nInd = self._interpretIndexes(ind) - try: - self._data[nInd] = val - except: - print(self, nInd, val) - raise - - def __getattr__(self, attr): - if attr in self.wrapMethods: - return getattr(self._data, attr) - else: - raise AttributeError(attr) - - def __eq__(self, b): - return self._binop('__eq__', b) - - def __ne__(self, b): - return self._binop('__ne__', b) - - def __sub__(self, b): - return self._binop('__sub__', b) - - def __add__(self, b): - return self._binop('__add__', b) - - def __mul__(self, b): - return self._binop('__mul__', b) - - def __div__(self, b): - return self._binop('__div__', b) - - def __truediv__(self, b): - return self._binop('__truediv__', b) - - def _binop(self, op, b): - if isinstance(b, MetaArray): - b = b.asarray() - a = self.asarray() - c = getattr(a, op)(b) - if c.shape != a.shape: - raise Exception("Binary operators with MetaArray must return an array of the same shape (this shape is %s, result shape was %s)" % (a.shape, c.shape)) - return MetaArray(c, info=self.infoCopy()) - - def asarray(self): - if isinstance(self._data, np.ndarray): - return self._data - else: - return np.array(self._data) - - def __array__(self, dtype=None): - ## supports np.array(metaarray_instance) - if dtype is None: - return self.asarray() - else: - return self.asarray().astype(dtype) - - def view(self, typ): - warnings.warn( - 'MetaArray.view is deprecated and will be removed in 0.13. ' - 'Use MetaArray.asarray() instead.', - DeprecationWarning, stacklevel=2 - ) - if typ is np.ndarray: - return self.asarray() - else: - raise Exception('invalid view type: %s' % str(typ)) - - def axisValues(self, axis): - """Return the list of values for an axis""" - ax = self._interpretAxis(axis) - if 'values' in self._info[ax]: - return self._info[ax]['values'] - else: - raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) - - def xvals(self, axis): - """Synonym for axisValues()""" - return self.axisValues(axis) - - def axisHasValues(self, axis): - ax = self._interpretAxis(axis) - return 'values' in self._info[ax] - - def axisHasColumns(self, axis): - ax = self._interpretAxis(axis) - return 'cols' in self._info[ax] - - def axisUnits(self, axis): - """Return the units for axis""" - ax = self._info[self._interpretAxis(axis)] - if 'units' in ax: - return ax['units'] - - def hasColumn(self, axis, col): - ax = self._info[self._interpretAxis(axis)] - if 'cols' in ax: - for c in ax['cols']: - if c['name'] == col: - return True - return False - - def listColumns(self, axis=None): - """Return a list of column names for axis. If axis is not specified, then return a dict of {axisName: (column names), ...}.""" - if axis is None: - ret = {} - for i in range(self.ndim): - if 'cols' in self._info[i]: - cols = [c['name'] for c in self._info[i]['cols']] - else: - cols = [] - ret[self.axisName(i)] = cols - return ret - else: - axis = self._interpretAxis(axis) - return [c['name'] for c in self._info[axis]['cols']] - - def columnName(self, axis, col): - ax = self._info[self._interpretAxis(axis)] - return ax['cols'][col]['name'] - - def axisName(self, n): - return self._info[n].get('name', n) - - def columnUnits(self, axis, column): - """Return the units for column in axis""" - ax = self._info[self._interpretAxis(axis)] - if 'cols' in ax: - for c in ax['cols']: - if c['name'] == column: - return c['units'] - raise Exception("Axis %s has no column named %s" % (str(axis), str(column))) - else: - raise Exception("Axis %s has no column definitions" % str(axis)) - - def rowsort(self, axis, key=0): - """Return this object with all records sorted along axis using key as the index to the values to compare. Does not yet modify meta info.""" - ## make sure _info is copied locally before modifying it! - - keyList = self[key] - order = keyList.argsort() - if type(axis) == int: - ind = [slice(None)]*axis - ind.append(order) - elif isinstance(axis, basestring): - ind = (slice(axis, order),) - return self[tuple(ind)] - - def append(self, val, axis): - """Return this object with val appended along axis. Does not yet combine meta info.""" - ## make sure _info is copied locally before modifying it! - - s = list(self.shape) - axis = self._interpretAxis(axis) - s[axis] += 1 - n = MetaArray(tuple(s), info=self._info, dtype=self.dtype) - ind = [slice(None)]*self.ndim - ind[axis] = slice(None,-1) - n[tuple(ind)] = self - ind[axis] = -1 - n[tuple(ind)] = val - return n - - def extend(self, val, axis): - """Return the concatenation along axis of this object and val. Does not yet combine meta info.""" - ## make sure _info is copied locally before modifying it! - - axis = self._interpretAxis(axis) - return MetaArray(np.concatenate(self, val, axis), info=self._info) - - def infoCopy(self, axis=None): - """Return a deep copy of the axis meta info for this object""" - if axis is None: - return copy.deepcopy(self._info) - else: - return copy.deepcopy(self._info[self._interpretAxis(axis)]) - - def copy(self): - return MetaArray(self._data.copy(), info=self.infoCopy()) - - - def _interpretIndexes(self, ind): - #print "interpret", ind - if not isinstance(ind, tuple): - ## a list of slices should be interpreted as a tuple of slices. - if isinstance(ind, list) and len(ind) > 0 and isinstance(ind[0], slice): - ind = tuple(ind) - ## everything else can just be converted to a length-1 tuple - else: - ind = (ind,) - - nInd = [slice(None)]*self.ndim - numOk = True ## Named indices not started yet; numbered sill ok - for i in range(0,len(ind)): - (axis, index, isNamed) = self._interpretIndex(ind[i], i, numOk) - nInd[axis] = index - if isNamed: - numOk = False - return tuple(nInd) - - def _interpretAxis(self, axis): - if isinstance(axis, basestring) or isinstance(axis, tuple): - return self._getAxis(axis) - else: - return axis - - def _interpretIndex(self, ind, pos, numOk): - #print "Interpreting index", ind, pos, numOk - - ## should probably check for int first to speed things up.. - if type(ind) is int: - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " normal numerical index" - return (pos, ind, False) - if MetaArray.isNameType(ind): - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " String index, column is ", self._getIndex(pos, ind) - return (pos, self._getIndex(pos, ind), False) - elif type(ind) is slice: - #print " Slice index" - if MetaArray.isNameType(ind.start) or MetaArray.isNameType(ind.stop): ## Not an actual slice! - #print " ..not a real slice" - axis = self._interpretAxis(ind.start) - #print " axis is", axis - - ## x[Axis:Column] - if MetaArray.isNameType(ind.stop): - #print " column name, column is ", self._getIndex(axis, ind.stop) - index = self._getIndex(axis, ind.stop) - - ## x[Axis:min:max] - elif (isinstance(ind.stop, float) or isinstance(ind.step, float)) and ('values' in self._info[axis]): - #print " axis value range" - if ind.stop is None: - mask = self.xvals(axis) < ind.step - elif ind.step is None: - mask = self.xvals(axis) >= ind.stop - else: - mask = (self.xvals(axis) >= ind.stop) * (self.xvals(axis) < ind.step) - ##print "mask:", mask - index = mask - - ## x[Axis:columnIndex] - elif isinstance(ind.stop, int) or isinstance(ind.step, int): - #print " normal slice after named axis" - if ind.step is None: - index = ind.stop - else: - index = slice(ind.stop, ind.step) - - ## x[Axis: [list]] - elif type(ind.stop) is list: - #print " list of indexes from named axis" - index = [] - for i in ind.stop: - if type(i) is int: - index.append(i) - elif MetaArray.isNameType(i): - index.append(self._getIndex(axis, i)) - else: - ## unrecognized type, try just passing on to array - index = ind.stop - break - - else: - #print " other type.. forward on to array for handling", type(ind.stop) - index = ind.stop - #print "Axis %s (%s) : %s" % (ind.start, str(axis), str(type(index))) - #if type(index) is np.ndarray: - #print " ", index.shape - return (axis, index, True) - else: - #print " Looks like a real slice, passing on to array" - return (pos, ind, False) - elif type(ind) is list: - #print " List index., interpreting each element individually" - indList = [self._interpretIndex(i, pos, numOk)[1] for i in ind] - return (pos, indList, False) - else: - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " normal numerical index" - return (pos, ind, False) - - def _getAxis(self, name): - for i in range(0, len(self._info)): - axis = self._info[i] - if 'name' in axis and axis['name'] == name: - return i - raise Exception("No axis named %s.\n info=%s" % (name, self._info)) - - def _getIndex(self, axis, name): - ax = self._info[axis] - if ax is not None and 'cols' in ax: - for i in range(0, len(ax['cols'])): - if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: - return i - raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) - - def _axisCopy(self, i): - return copy.deepcopy(self._info[i]) - - def _axisSlice(self, i, cols): - #print "axisSlice", i, cols - if 'cols' in self._info[i] or 'values' in self._info[i]: - ax = self._axisCopy(i) - if 'cols' in ax: - #print " slicing columns..", array(ax['cols']), cols - sl = np.array(ax['cols'])[cols] - if isinstance(sl, np.ndarray): - sl = list(sl) - ax['cols'] = sl - #print " result:", ax['cols'] - if 'values' in ax: - ax['values'] = np.array(ax['values'])[cols] - else: - ax = self._info[i] - #print " ", ax - return ax - - def prettyInfo(self): - s = '' - titles = [] - maxl = 0 - for i in range(len(self._info)-1): - ax = self._info[i] - axs = '' - if 'name' in ax: - axs += '"%s"' % str(ax['name']) - else: - axs += "%d" % i - if 'units' in ax: - axs += " (%s)" % str(ax['units']) - titles.append(axs) - if len(axs) > maxl: - maxl = len(axs) - - for i in range(min(self.ndim, len(self._info) - 1)): - ax = self._info[i] - axs = titles[i] - axs += '%s[%d] :' % (' ' * (maxl - len(axs) + 5 - len(str(self.shape[i]))), self.shape[i]) - if 'values' in ax: - if self.shape[i] > 0: - v0 = ax['values'][0] - axs += " values: [%g" % (v0) - if self.shape[i] > 1: - v1 = ax['values'][-1] - axs += " ... %g] (step %g)" % (v1, (v1 - v0) / (self.shape[i] - 1)) - else: - axs += "]" - else: - axs += " values: []" - if 'cols' in ax: - axs += " columns: " - colstrs = [] - for c in range(len(ax['cols'])): - col = ax['cols'][c] - cs = str(col.get('name', c)) - if 'units' in col: - cs += " (%s)" % col['units'] - colstrs.append(cs) - axs += '[' + ', '.join(colstrs) + ']' - s += axs + "\n" - s += str(self._info[-1]) - return s - - def __repr__(self): - return "%s\n-----------------------------------------------\n%s" % (self.view(np.ndarray).__repr__(), self.prettyInfo()) - - def __str__(self): - return self.__repr__() - - def axisCollapsingFn(self, fn, axis=None, *args, **kargs): - fn = getattr(self._data, fn) - if axis is None: - return fn(axis, *args, **kargs) - else: - info = self.infoCopy() - axis = self._interpretAxis(axis) - info.pop(axis) - return MetaArray(fn(axis, *args, **kargs), info=info) - - def mean(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('mean', axis, *args, **kargs) - - - def min(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('min', axis, *args, **kargs) - - def max(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('max', axis, *args, **kargs) - - def transpose(self, *args): - if len(args) == 1 and hasattr(args[0], '__iter__'): - order = args[0] - else: - order = args - - order = [self._interpretAxis(ax) for ax in order] - infoOrder = order + list(range(len(order), len(self._info))) - info = [self._info[i] for i in infoOrder] - order = order + list(range(len(order), self.ndim)) - - try: - if self._isHDF: - return MetaArray(np.array(self._data).transpose(order), info=info) - else: - return MetaArray(self._data.transpose(order), info=info) - except: - print(order) - raise - - #### File I/O Routines - def readFile(self, filename, **kwargs): - """Load the data and meta info stored in *filename* - Different arguments are allowed depending on the type of file. - For HDF5 files: - - *writable* (bool) if True, then any modifications to data in the array will be stored to disk. - *readAllData* (bool) if True, then all data in the array is immediately read from disk - and the file is closed (this is the default for files < 500MB). Otherwise, the file will - be left open and data will be read only as requested (this is - the default for files >= 500MB). - - - """ - ## decide which read function to use - with open(filename, 'rb') as fd: - magic = fd.read(8) - if magic == b'\x89HDF\r\n\x1a\n': - fd.close() - self._readHDF5(filename, **kwargs) - self._isHDF = True - else: - fd.seek(0) - meta = MetaArray._readMeta(fd) - if not kwargs.get("readAllData", True): - self._data = np.empty(meta['shape'], dtype=meta['type']) - if 'version' in meta: - ver = meta['version'] - else: - ver = 1 - rFuncName = '_readData%s' % str(ver) - if not hasattr(MetaArray, rFuncName): - raise Exception("This MetaArray library does not support array version '%s'" % ver) - rFunc = getattr(self, rFuncName) - rFunc(fd, meta, **kwargs) - self._isHDF = False - - @staticmethod - def _readMeta(fd): - """Read meta array from the top of a file. Read lines until a blank line is reached. - This function should ideally work for ALL versions of MetaArray. - """ - meta = u'' - ## Read meta information until the first blank line - while True: - line = fd.readline().strip() - if line == '': - break - meta += line - ret = eval(meta) - #print ret - return ret - - def _readData1(self, fd, meta, mmap=False, **kwds): - ## Read array data from the file descriptor for MetaArray v1 files - ## read in axis values for any axis that specifies a length - frameSize = 1 - for ax in meta['info']: - if 'values_len' in ax: - ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) - frameSize *= ax['values_len'] - del ax['values_len'] - del ax['values_type'] - self._info = meta['info'] - if not kwds.get("readAllData", True): - return - ## the remaining data is the actual array - if mmap: - subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) - else: - subarr = np.fromstring(fd.read(), dtype=meta['type']) - subarr.shape = meta['shape'] - self._data = subarr - - def _readData2(self, fd, meta, mmap=False, subset=None, **kwds): - ## read in axis values - dynAxis = None - frameSize = 1 - ## read in axis values for any axis that specifies a length - for i in range(len(meta['info'])): - ax = meta['info'][i] - if 'values_len' in ax: - if ax['values_len'] == 'dynamic': - if dynAxis is not None: - raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") - dynAxis = i - else: - ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) - frameSize *= ax['values_len'] - del ax['values_len'] - del ax['values_type'] - self._info = meta['info'] - if not kwds.get("readAllData", True): - return - - ## No axes are dynamic, just read the entire array in at once - if dynAxis is None: - if meta['type'] == 'object': - if mmap: - raise Exception('memmap not supported for arrays with dtype=object') - subarr = pickle.loads(fd.read()) - else: - if mmap: - subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) - else: - subarr = np.fromstring(fd.read(), dtype=meta['type']) - subarr.shape = meta['shape'] - ## One axis is dynamic, read in a frame at a time - else: - if mmap: - raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') - ax = meta['info'][dynAxis] - xVals = [] - frames = [] - frameShape = list(meta['shape']) - frameShape[dynAxis] = 1 - frameSize = np.prod(frameShape) - n = 0 - while True: - ## Extract one non-blank line - while True: - line = fd.readline() - if line != '\n': - break - if line == '': - break - - ## evaluate line - inf = eval(line) - - ## read data block - #print "read %d bytes as %s" % (inf['len'], meta['type']) - if meta['type'] == 'object': - data = pickle.loads(fd.read(inf['len'])) - else: - data = np.fromstring(fd.read(inf['len']), dtype=meta['type']) - - if data.size != frameSize * inf['numFrames']: - #print data.size, frameSize, inf['numFrames'] - raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) - - ## read in data block - shape = list(frameShape) - shape[dynAxis] = inf['numFrames'] - data.shape = shape - if subset is not None: - dSlice = subset[dynAxis] - if dSlice.start is None: - dStart = 0 - else: - dStart = max(0, dSlice.start - n) - if dSlice.stop is None: - dStop = data.shape[dynAxis] - else: - dStop = min(data.shape[dynAxis], dSlice.stop - n) - newSubset = list(subset[:]) - newSubset[dynAxis] = slice(dStart, dStop) - if dStop > dStart: - frames.append(data[tuple(newSubset)].copy()) - else: - frames.append(data) - - n += inf['numFrames'] - if 'xVals' in inf: - xVals.extend(inf['xVals']) - subarr = np.concatenate(frames, axis=dynAxis) - if len(xVals)> 0: - ax['values'] = np.array(xVals, dtype=ax['values_type']) - del ax['values_len'] - del ax['values_type'] - self._info = meta['info'] - self._data = subarr - - def _readHDF5(self, fileName, readAllData=None, writable=False, **kargs): - if 'close' in kargs and readAllData is None: ## for backward compatibility - readAllData = kargs['close'] - - if readAllData is True and writable is True: - raise Exception("Incompatible arguments: readAllData=True and writable=True") - - if not HAVE_HDF5: - try: - assert writable==False - assert readAllData != False - self._readHDF5Remote(fileName) - return - except: - raise Exception("The file '%s' is HDF5-formatted, but the HDF5 library (h5py) was not found." % fileName) - - ## by default, readAllData=True for files < 500MB - if readAllData is None: - size = os.stat(fileName).st_size - readAllData = (size < 500e6) - - if writable is True: - mode = 'r+' - else: - mode = 'r' - f = h5py.File(fileName, mode) - - ver = f.attrs['MetaArray'] - try: - ver = ver.decode('utf-8') - except: - pass - if ver > MetaArray.version: - print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) - meta = MetaArray.readHDF5Meta(f['info']) - self._info = meta - - if writable or not readAllData: ## read all data, convert to ndarray, close file - self._data = f['data'] - self._openFile = f - else: - self._data = f['data'][:] - f.close() - - def _readHDF5Remote(self, fileName): - ## Used to read HDF5 files via remote process. - ## This is needed in the case that HDF5 is not importable due to the use of python-dbg. - proc = getattr(MetaArray, '_hdf5Process', None) - - if proc == False: - raise Exception('remote read failed') - if proc == None: - from .. import multiprocess as mp - #print "new process" - proc = mp.Process(executable='/usr/bin/python') - proc.setProxyOptions(deferGetattr=True) - MetaArray._hdf5Process = proc - MetaArray._h5py_metaarray = proc._import('pyqtgraph.metaarray') - ma = MetaArray._h5py_metaarray.MetaArray(file=fileName) - self._data = ma.asarray()._getValue() - self._info = ma._info._getValue() - - @staticmethod - def mapHDF5Array(data, writable=False): - off = data.id.get_offset() - if writable: - mode = 'r+' - else: - mode = 'r' - if off is None: - raise Exception("This dataset uses chunked storage; it can not be memory-mapped. (store using mappable=True)") - return np.memmap(filename=data.file.filename, offset=off, dtype=data.dtype, shape=data.shape, mode=mode) - - @staticmethod - def readHDF5Meta(root, mmap=False): - data = {} - - ## Pull list of values from attributes and child objects - for k in root.attrs: - val = root.attrs[k] - if isinstance(val, bytes): - val = val.decode() - if isinstance(val, basestring): ## strings need to be re-evaluated to their original types - try: - val = eval(val) - except: - raise Exception('Can not evaluate string: "%s"' % val) - data[k] = val - for k in root: - obj = root[k] - if isinstance(obj, h5py.Group): - val = MetaArray.readHDF5Meta(obj) - elif isinstance(obj, h5py.Dataset): - if mmap: - val = MetaArray.mapHDF5Array(obj) - else: - val = obj[:] - else: - raise Exception("Don't know what to do with type '%s'" % str(type(obj))) - data[k] = val - - typ = root.attrs['_metaType_'] - try: - typ = typ.decode('utf-8') - except: - pass - del data['_metaType_'] - - if typ == 'dict': - return data - elif typ == 'list' or typ == 'tuple': - d2 = [None]*len(data) - for k in data: - d2[int(k)] = data[k] - if typ == 'tuple': - d2 = tuple(d2) - return d2 - else: - raise Exception("Don't understand metaType '%s'" % typ) - - def write(self, fileName, **opts): - """Write this object to a file. The object can be restored by calling MetaArray(file=fileName) - opts: - appendAxis: the name (or index) of the appendable axis. Allows the array to grow. - appendKeys: a list of keys (other than "values") for metadata to append to on the appendable axis. - compression: None, 'gzip' (good compression), 'lzf' (fast compression), etc. - chunks: bool or tuple specifying chunk shape - """ - if USE_HDF5 is False: - return self.writeMa(fileName, **opts) - elif HAVE_HDF5 is True: - return self.writeHDF5(fileName, **opts) - else: - raise Exception("h5py is required for writing .ma hdf5 files, but it could not be imported.") - - def writeMeta(self, fileName): - """Used to re-write meta info to the given file. - This feature is only available for HDF5 files.""" - f = h5py.File(fileName, 'r+') - if f.attrs['MetaArray'] != MetaArray.version: - raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) - del f['info'] - - self.writeHDF5Meta(f, 'info', self._info) - f.close() - - def writeHDF5(self, fileName, **opts): - ## default options for writing datasets - comp = self.defaultCompression - if isinstance(comp, tuple): - comp, copts = comp - else: - copts = None - - dsOpts = { - 'compression': comp, - 'chunks': True, - } - if copts is not None: - dsOpts['compression_opts'] = copts - - ## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending) - appAxis = opts.get('appendAxis', None) - if appAxis is not None: - appAxis = self._interpretAxis(appAxis) - cs = [min(100000, x) for x in self.shape] - cs[appAxis] = 1 - dsOpts['chunks'] = tuple(cs) - - ## if there are columns, then we can guess a different chunk shape - ## (read one column at a time) - else: - cs = [min(100000, x) for x in self.shape] - for i in range(self.ndim): - if 'cols' in self._info[i]: - cs[i] = 1 - dsOpts['chunks'] = tuple(cs) - - ## update options if they were passed in - for k in dsOpts: - if k in opts: - dsOpts[k] = opts[k] - - ## If mappable is in options, it disables chunking/compression - if opts.get('mappable', False): - dsOpts = { - 'chunks': None, - 'compression': None - } - - ## set maximum shape to allow expansion along appendAxis - append = False - if appAxis is not None: - maxShape = list(self.shape) - ax = self._interpretAxis(appAxis) - maxShape[ax] = None - if os.path.exists(fileName): - append = True - dsOpts['maxshape'] = tuple(maxShape) - else: - dsOpts['maxshape'] = None - - if append: - f = h5py.File(fileName, 'r+') - if f.attrs['MetaArray'] != MetaArray.version: - raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) - - ## resize data and write in new values - data = f['data'] - shape = list(data.shape) - shape[ax] += self.shape[ax] - data.resize(tuple(shape)) - sl = [slice(None)] * len(data.shape) - sl[ax] = slice(-self.shape[ax], None) - data[tuple(sl)] = self.view(np.ndarray) - - ## add axis values if they are present. - axKeys = ["values"] - axKeys.extend(opts.get("appendKeys", [])) - axInfo = f['info'][str(ax)] - for key in axKeys: - if key in axInfo: - v = axInfo[key] - v2 = self._info[ax][key] - shape = list(v.shape) - shape[0] += v2.shape[0] - v.resize(shape) - v[-v2.shape[0]:] = v2 - else: - raise TypeError('Cannot append to axis info key "%s"; this key is not present in the target file.' % key) - f.close() - else: - f = h5py.File(fileName, 'w') - f.attrs['MetaArray'] = MetaArray.version - #print dsOpts - f.create_dataset('data', data=self.view(np.ndarray), **dsOpts) - - ## dsOpts is used when storing meta data whenever an array is encountered - ## however, 'chunks' will no longer be valid for these arrays if it specifies a chunk shape. - ## 'maxshape' is right-out. - if isinstance(dsOpts['chunks'], tuple): - dsOpts['chunks'] = True - if 'maxshape' in dsOpts: - del dsOpts['maxshape'] - self.writeHDF5Meta(f, 'info', self._info, **dsOpts) - f.close() - - def writeHDF5Meta(self, root, name, data, **dsOpts): - if isinstance(data, np.ndarray): - dsOpts['maxshape'] = (None,) + data.shape[1:] - root.create_dataset(name, data=data, **dsOpts) - elif isinstance(data, list) or isinstance(data, tuple): - gr = root.create_group(name) - if isinstance(data, list): - gr.attrs['_metaType_'] = 'list' - else: - gr.attrs['_metaType_'] = 'tuple' - #n = int(np.log10(len(data))) + 1 - for i in range(len(data)): - self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) - elif isinstance(data, dict): - gr = root.create_group(name) - gr.attrs['_metaType_'] = 'dict' - for k, v in data.items(): - self.writeHDF5Meta(gr, k, v, **dsOpts) - elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): - root.attrs[name] = data - else: - try: ## strings, bools, None are stored as repr() strings - root.attrs[name] = repr(data) - except: - print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) - raise - - - def writeMa(self, fileName, appendAxis=None, newFile=False): - """Write an old-style .ma file""" - meta = {'shape':self.shape, 'type':str(self.dtype), 'info':self.infoCopy(), 'version':MetaArray.version} - axstrs = [] - - ## copy out axis values for dynamic axis if requested - if appendAxis is not None: - if MetaArray.isNameType(appendAxis): - appendAxis = self._interpretAxis(appendAxis) - - - ax = meta['info'][appendAxis] - ax['values_len'] = 'dynamic' - if 'values' in ax: - ax['values_type'] = str(ax['values'].dtype) - dynXVals = ax['values'] - del ax['values'] - else: - dynXVals = None - - ## Generate axis data string, modify axis info so we know how to read it back in later - for ax in meta['info']: - if 'values' in ax: - axstrs.append(ax['values'].tostring()) - ax['values_len'] = len(axstrs[-1]) - ax['values_type'] = str(ax['values'].dtype) - del ax['values'] - - ## Decide whether to output the meta block for a new file - if not newFile: - ## If the file does not exist or its size is 0, then we must write the header - newFile = (not os.path.exists(fileName)) or (os.stat(fileName).st_size == 0) - - ## write data to file - if appendAxis is None or newFile: - fd = open(fileName, 'wb') - fd.write(str(meta) + '\n\n') - for ax in axstrs: - fd.write(ax) - else: - fd = open(fileName, 'ab') - - if self.dtype != object: - dataStr = self.view(np.ndarray).tostring() - else: - dataStr = pickle.dumps(self.view(np.ndarray)) - #print self.size, len(dataStr), self.dtype - if appendAxis is not None: - frameInfo = {'len':len(dataStr), 'numFrames':self.shape[appendAxis]} - if dynXVals is not None: - frameInfo['xVals'] = list(dynXVals) - fd.write('\n'+str(frameInfo)+'\n') - fd.write(dataStr) - fd.close() - - def writeCsv(self, fileName=None): - """Write 2D array to CSV file or return the string if no filename is given""" - if self.ndim > 2: - raise Exception("CSV Export is only for 2D arrays") - if fileName is not None: - file = open(fileName, 'w') - ret = '' - if 'cols' in self._info[0]: - s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' - if fileName is not None: - file.write(s) - else: - ret += s - for row in range(0, self.shape[1]): - s = ','.join(["%g" % x for x in self[:, row]]) + '\n' - if fileName is not None: - file.write(s) - else: - ret += s - if fileName is not None: - file.close() - else: - return ret - - -if __name__ == '__main__': - ## Create an array with every option possible - - arr = np.zeros((2, 5, 3, 5), dtype=int) - for i in range(arr.shape[0]): - for j in range(arr.shape[1]): - for k in range(arr.shape[2]): - for l in range(arr.shape[3]): - arr[i,j,k,l] = (i+1)*1000 + (j+1)*100 + (k+1)*10 + (l+1) - - info = [ - axis('Axis1'), - axis('Axis2', values=[1,2,3,4,5]), - axis('Axis3', cols=[ - ('Ax3Col1'), - ('Ax3Col2', 'mV', 'Axis3 Column2'), - (('Ax3','Col3'), 'A', 'Axis3 Column3')]), - {'name': 'Axis4', 'values': np.array([1.1, 1.2, 1.3, 1.4, 1.5]), 'units': 's'}, - {'extra': 'info'} - ] - - ma = MetaArray(arr, info=info) - - print("==== Original Array =======") - print(ma) - print("\n\n") - - #### Tests follow: - - - #### Index/slice tests: check that all values and meta info are correct after slice - print("\n -- normal integer indexing\n") - - print("\n ma[1]") - print(ma[1]) - - print("\n ma[1, 2:4]") - print(ma[1, 2:4]) - - print("\n ma[1, 1:5:2]") - print(ma[1, 1:5:2]) - - print("\n -- named axis indexing\n") - - print("\n ma['Axis2':3]") - print(ma['Axis2':3]) - - print("\n ma['Axis2':3:5]") - print(ma['Axis2':3:5]) - - print("\n ma[1, 'Axis2':3]") - print(ma[1, 'Axis2':3]) - - print("\n ma[:, 'Axis2':3]") - print(ma[:, 'Axis2':3]) - - print("\n ma['Axis2':3, 'Axis4':0:2]") - print(ma['Axis2':3, 'Axis4':0:2]) - - - print("\n -- column name indexing\n") - - print("\n ma['Axis3':'Ax3Col1']") - print(ma['Axis3':'Ax3Col1']) - - print("\n ma['Axis3':('Ax3','Col3')]") - print(ma['Axis3':('Ax3','Col3')]) - - print("\n ma[:, :, 'Ax3Col2']") - print(ma[:, :, 'Ax3Col2']) - - print("\n ma[:, :, ('Ax3','Col3')]") - print(ma[:, :, ('Ax3','Col3')]) - - - print("\n -- axis value range indexing\n") - - print("\n ma['Axis2':1.5:4.5]") - print(ma['Axis2':1.5:4.5]) - - print("\n ma['Axis4':1.15:1.45]") - print(ma['Axis4':1.15:1.45]) - - print("\n ma['Axis4':1.15:1.25]") - print(ma['Axis4':1.15:1.25]) - - - - print("\n -- list indexing\n") - - print("\n ma[:, [0,2,4]]") - print(ma[:, [0,2,4]]) - - print("\n ma['Axis4':[0,2,4]]") - print(ma['Axis4':[0,2,4]]) - - print("\n ma['Axis3':[0, ('Ax3','Col3')]]") - print(ma['Axis3':[0, ('Ax3','Col3')]]) - - - - print("\n -- boolean indexing\n") - - print("\n ma[:, array([True, True, False, True, False])]") - print(ma[:, np.array([True, True, False, True, False])]) - - print("\n ma['Axis4':array([True, False, False, False])]") - print(ma['Axis4':np.array([True, False, False, False])]) - - - - - - #### Array operations - # - Concatenate - # - Append - # - Extend - # - Rowsort - - - - - #### File I/O tests - - print("\n================ File I/O Tests ===================\n") - import tempfile - tf = tempfile.mktemp() - tf = 'test.ma' - # write whole array - - print("\n -- write/read test") - ma.write(tf) - ma2 = MetaArray(file=tf) - - #print ma2 - print("\nArrays are equivalent:", (ma == ma2).all()) - #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() - os.remove(tf) - - # CSV write - - # append mode - - - print("\n================append test (%s)===============" % tf) - ma['Axis2':0:2].write(tf, appendAxis='Axis2') - for i in range(2,ma.shape[1]): - ma['Axis2':[i]].write(tf, appendAxis='Axis2') - - ma2 = MetaArray(file=tf) - - #print ma2 - print("\nArrays are equivalent:", (ma == ma2).all()) - #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() - - os.remove(tf) - - - - ## memmap test - print("\n==========Memmap test============") - ma.write(tf, mappable=True) - ma2 = MetaArray(file=tf, mmap=True) - print("\nArrays are equivalent:", (ma == ma2).all()) - os.remove(tf) - diff --git a/pyqtgraph/metaarray/__init__.py b/pyqtgraph/metaarray/__init__.py deleted file mode 100644 index a12f40d..0000000 --- a/pyqtgraph/metaarray/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .MetaArray import * diff --git a/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-36.pyc b/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-36.pyc deleted file mode 100644 index 2a77f77..0000000 Binary files a/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-37.pyc b/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-37.pyc deleted file mode 100644 index 8f9d93b..0000000 Binary files a/pyqtgraph/metaarray/__pycache__/MetaArray.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/metaarray/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/metaarray/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index bf7e236..0000000 Binary files a/pyqtgraph/metaarray/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/metaarray/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/metaarray/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index c4b77c1..0000000 Binary files a/pyqtgraph/metaarray/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__init__.py b/pyqtgraph/multiprocess/__init__.py deleted file mode 100644 index 32a250c..0000000 --- a/pyqtgraph/multiprocess/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Multiprocessing utility library -(parallelization done the way I like it) - -Luke Campagnola -2012.06.10 - -This library provides: - - - simple mechanism for starting a new python interpreter process that can be controlled from the original process - (this allows, for example, displaying and manipulating plots in a remote process - while the parent process is free to do other work) - - proxy system that allows objects hosted in the remote process to be used as if they were local - - Qt signal connection between processes - - very simple in-line parallelization (fork only; does not work on windows) for number-crunching - -TODO: - allow remote processes to serve as rendering engines that pass pixmaps back to the parent process for display - (RemoteGraphicsView class) -""" - -from .processes import * -from .parallelizer import Parallelize, CanceledError -from .remoteproxy import proxy, ClosedError, NoResultError diff --git a/pyqtgraph/multiprocess/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/multiprocess/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 34d0d2f..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/multiprocess/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index bbae69b..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/bootstrap.cpython-36.pyc b/pyqtgraph/multiprocess/__pycache__/bootstrap.cpython-36.pyc deleted file mode 100644 index d07c8dd..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/bootstrap.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-36.pyc b/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-36.pyc deleted file mode 100644 index 8bd6a94..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-37.pyc b/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-37.pyc deleted file mode 100644 index be077a7..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/parallelizer.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/processes.cpython-36.pyc b/pyqtgraph/multiprocess/__pycache__/processes.cpython-36.pyc deleted file mode 100644 index d6b57fe..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/processes.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/processes.cpython-37.pyc b/pyqtgraph/multiprocess/__pycache__/processes.cpython-37.pyc deleted file mode 100644 index 77195e9..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/processes.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-36.pyc b/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-36.pyc deleted file mode 100644 index 0c56f73..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-37.pyc b/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-37.pyc deleted file mode 100644 index 45c0a23..0000000 Binary files a/pyqtgraph/multiprocess/__pycache__/remoteproxy.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/multiprocess/bootstrap.py b/pyqtgraph/multiprocess/bootstrap.py deleted file mode 100644 index a20c3e2..0000000 --- a/pyqtgraph/multiprocess/bootstrap.py +++ /dev/null @@ -1,47 +0,0 @@ -"""For starting up remote processes""" -import sys, pickle, os -import importlib - -if __name__ == '__main__': - if hasattr(os, 'setpgrp'): - os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process - if sys.version[0] == '3': - #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer) - opts = pickle.load(sys.stdin.buffer) - else: - #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin) - opts = pickle.load(sys.stdin) - #print "key:", ' '.join([str(ord(x)) for x in authkey]) - path = opts.pop('path', None) - if path is not None: - if isinstance(path, str): - # if string, just insert this into the path - sys.path.insert(0, path) - else: - # if list, then replace the entire sys.path - ## modify sys.path in place--no idea who already has a reference to the existing list. - while len(sys.path) > 0: - sys.path.pop() - sys.path.extend(path) - - pyqtapis = opts.pop('pyqtapis', None) - if pyqtapis is not None: - try: - from PyQt5 import sip - except ImportError: - import sip - for k,v in pyqtapis.items(): - sip.setapi(k, v) - - qt_lib = opts.pop('qt_lib', None) - if qt_lib is not None: - globals()[qt_lib] = importlib.import_module(qt_lib) - - targetStr = opts.pop('targetStr') - try: - target = pickle.loads(targetStr) ## unpickling the target should import everything we need - except: - print("Current sys.path:", sys.path) - raise - target(**opts) ## Send all other options to the target function - sys.exit(0) diff --git a/pyqtgraph/multiprocess/parallelizer.py b/pyqtgraph/multiprocess/parallelizer.py deleted file mode 100644 index b0f064b..0000000 --- a/pyqtgraph/multiprocess/parallelizer.py +++ /dev/null @@ -1,338 +0,0 @@ -# -*- coding: utf-8 -*- -import os, sys, time, multiprocessing, re -from .processes import ForkedProcess -from .remoteproxy import ClosedError -from ..python2_3 import basestring, xrange - - -class CanceledError(Exception): - """Raised when the progress dialog is canceled during a processing operation.""" - pass - -class Parallelize(object): - """ - Class for ultra-simple inline parallelization on multi-core CPUs - - Example:: - - ## Here is the serial (single-process) task: - - tasks = [1, 2, 4, 8] - results = [] - for task in tasks: - result = processTask(task) - results.append(result) - print(results) - - - ## Here is the parallelized version: - - tasks = [1, 2, 4, 8] - results = [] - with Parallelize(tasks, workers=4, results=results) as tasker: - for task in tasker: - result = processTask(task) - tasker.results.append(result) - print(results) - - - The only major caveat is that *result* in the example above must be picklable, - since it is automatically sent via pipe back to the parent process. - """ - - def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds): - """ - =============== =================================================================== - **Arguments:** - tasks list of objects to be processed (Parallelize will determine how to - distribute the tasks). If unspecified, then each worker will receive - a single task with a unique id number. - workers number of worker processes or None to use number of CPUs in the - system - progressDialog optional dict of arguments for ProgressDialog - to update while tasks are processed - randomReseed If True, each forked process will reseed its random number generator - to ensure independent results. Works with the built-in random - and numpy.random. - kwds objects to be shared by proxy with child processes (they will - appear as attributes of the tasker) - =============== =================================================================== - """ - - ## Generate progress dialog. - ## Note that we want to avoid letting forked child processes play with progress dialogs.. - self.showProgress = False - if progressDialog is not None: - self.showProgress = True - if isinstance(progressDialog, basestring): - progressDialog = {'labelText': progressDialog} - from ..widgets.ProgressDialog import ProgressDialog - self.progressDlg = ProgressDialog(**progressDialog) - - if workers is None: - workers = self.suggestedWorkerCount() - if not hasattr(os, 'fork'): - workers = 1 - self.workers = workers - if tasks is None: - tasks = range(workers) - self.tasks = list(tasks) - self.reseed = randomReseed - self.kwds = kwds.copy() - self.kwds['_taskStarted'] = self._taskStarted - - def __enter__(self): - self.proc = None - if self.workers == 1: - return self.runSerial() - else: - return self.runParallel() - - def __exit__(self, *exc_info): - - if self.proc is not None: ## worker - exceptOccurred = exc_info[0] is not None ## hit an exception during processing. - - try: - if exceptOccurred: - sys.excepthook(*exc_info) - finally: - #print os.getpid(), 'exit' - os._exit(1 if exceptOccurred else 0) - - else: ## parent - if self.showProgress: - try: - self.progressDlg.__exit__(None, None, None) - except Exception: - pass - - def runSerial(self): - if self.showProgress: - self.progressDlg.__enter__() - self.progressDlg.setMaximum(len(self.tasks)) - self.progress = {os.getpid(): []} - return Tasker(self, None, self.tasks, self.kwds) - - - def runParallel(self): - self.childs = [] - - ## break up tasks into one set per worker - workers = self.workers - chunks = [[] for i in xrange(workers)] - i = 0 - for i in range(len(self.tasks)): - chunks[i%workers].append(self.tasks[i]) - - ## fork and assign tasks to each worker - for i in range(workers): - proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed) - if not proc.isParent: - self.proc = proc - return Tasker(self, proc, chunks[i], proc.forkedProxies) - else: - self.childs.append(proc) - - ## Keep track of the progress of each worker independently. - self.progress = dict([(ch.childPid, []) for ch in self.childs]) - ## for each child process, self.progress[pid] is a list - ## of task indexes. The last index is the task currently being - ## processed; all others are finished. - - - try: - if self.showProgress: - self.progressDlg.__enter__() - self.progressDlg.setMaximum(len(self.tasks)) - ## process events from workers until all have exited. - - activeChilds = self.childs[:] - self.exitCodes = [] - pollInterval = 0.01 - while len(activeChilds) > 0: - waitingChildren = 0 - rem = [] - for ch in activeChilds: - try: - n = ch.processRequests() - if n > 0: - waitingChildren += 1 - except ClosedError: - #print ch.childPid, 'process finished' - rem.append(ch) - if self.showProgress: - self.progressDlg += 1 - #print "remove:", [ch.childPid for ch in rem] - for ch in rem: - activeChilds.remove(ch) - while True: - try: - pid, exitcode = os.waitpid(ch.childPid, 0) - self.exitCodes.append(exitcode) - break - except OSError as ex: - if ex.errno == 4: ## If we get this error, just try again - continue - #print "Ignored system call interruption" - else: - raise - - #print [ch.childPid for ch in activeChilds] - - if self.showProgress and self.progressDlg.wasCanceled(): - for ch in activeChilds: - ch.kill() - raise CanceledError() - - ## adjust polling interval--prefer to get exactly 1 event per poll cycle. - if waitingChildren > 1: - pollInterval *= 0.7 - elif waitingChildren == 0: - pollInterval /= 0.7 - pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits - - time.sleep(pollInterval) - finally: - if self.showProgress: - self.progressDlg.__exit__(None, None, None) - for ch in self.childs: - ch.join() - if len(self.exitCodes) < len(self.childs): - raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes))) - for code in self.exitCodes: - if code != 0: - raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).") - return [] ## no tasks for parent process. - - - @staticmethod - def suggestedWorkerCount(): - if 'linux' in sys.platform: - ## I think we can do a little better here.. - ## cpu_count does not consider that there is little extra benefit to using hyperthreaded cores. - try: - cores = {} - pid = None - with open('/proc/cpuinfo') as fd: - for line in fd: - m = re.match(r'physical id\s+:\s+(\d+)', line) - if m is not None: - pid = m.groups()[0] - m = re.match(r'cpu cores\s+:\s+(\d+)', line) - if m is not None: - cores[pid] = int(m.groups()[0]) - return sum(cores.values()) - except: - return multiprocessing.cpu_count() - - else: - return multiprocessing.cpu_count() - - def _taskStarted(self, pid, i, **kwds): - ## called remotely by tasker to indicate it has started working on task i - #print pid, 'reported starting task', i - if self.showProgress: - if len(self.progress[pid]) > 0: - self.progressDlg += 1 - if pid == os.getpid(): ## single-worker process - if self.progressDlg.wasCanceled(): - raise CanceledError() - self.progress[pid].append(i) - - -class Tasker(object): - def __init__(self, parallelizer, process, tasks, kwds): - self.proc = process - self.par = parallelizer - self.tasks = tasks - for k, v in kwds.items(): - setattr(self, k, v) - - def __iter__(self): - ## we could fix this up such that tasks are retrieved from the parent process one at a time.. - for i, task in enumerate(self.tasks): - self.index = i - #print os.getpid(), 'starting task', i - self._taskStarted(os.getpid(), i, _callSync='off') - yield task - if self.proc is not None: - #print os.getpid(), 'no more tasks' - self.proc.close() - - def process(self): - """ - Process requests from parent. - Usually it is not necessary to call this unless you would like to - receive messages (such as exit requests) during an iteration. - """ - if self.proc is not None: - self.proc.processRequests() - - def numWorkers(self): - """ - Return the number of parallel workers - """ - return self.par.workers - -#class Parallelizer: - #""" - #Use:: - - #p = Parallelizer() - #with p(4) as i: - #p.finish(do_work(i)) - #print p.results() - - #""" - #def __init__(self): - #pass - - #def __call__(self, n): - #self.replies = [] - #self.conn = None ## indicates this is the parent process - #return Session(self, n) - - #def finish(self, data): - #if self.conn is None: - #self.replies.append((self.i, data)) - #else: - ##print "send", self.i, data - #self.conn.send((self.i, data)) - #os._exit(0) - - #def result(self): - #print self.replies - -#class Session: - #def __init__(self, par, n): - #self.par = par - #self.n = n - - #def __enter__(self): - #self.childs = [] - #for i in range(1, self.n): - #c1, c2 = multiprocessing.Pipe() - #pid = os.fork() - #if pid == 0: ## child - #self.par.i = i - #self.par.conn = c2 - #self.childs = None - #c1.close() - #return i - #else: - #self.childs.append(c1) - #c2.close() - #self.par.i = 0 - #return 0 - - - - #def __exit__(self, *exc_info): - #if exc_info[0] is not None: - #sys.excepthook(*exc_info) - #if self.childs is not None: - #self.par.replies.extend([conn.recv() for conn in self.childs]) - #else: - #self.par.finish(None) - diff --git a/pyqtgraph/multiprocess/processes.py b/pyqtgraph/multiprocess/processes.py deleted file mode 100644 index 24fdebf..0000000 --- a/pyqtgraph/multiprocess/processes.py +++ /dev/null @@ -1,504 +0,0 @@ -import subprocess, atexit, os, sys, time, random, socket, signal, inspect -import multiprocessing.connection -try: - import cPickle as pickle -except ImportError: - import pickle - -from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy -from ..Qt import QT_LIB, mkQApp -from ..util import cprint # color printing for debugging - - -__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] - -class Process(RemoteEventHandler): - """ - Bases: RemoteEventHandler - - This class is used to spawn and control a new python interpreter. - It uses subprocess.Popen to start the new process and communicates with it - using multiprocessing.Connection objects over a network socket. - - By default, the remote process will immediately enter an event-processing - loop that carries out requests send from the parent process. - - Remote control works mainly through proxy objects:: - - proc = Process() ## starts process, returns handle - rsys = proc._import('sys') ## asks remote process to import 'sys', returns - ## a proxy which references the imported module - rsys.stdout.write('hello\n') ## This message will be printed from the remote - ## process. Proxy objects can usually be used - ## exactly as regular objects are. - proc.close() ## Request the remote process shut down - - Requests made via proxy objects may be synchronous or asynchronous and may - return objects either by proxy or by value (if they are picklable). See - ProxyObject for more information. - """ - _process_count = 1 # just used for assigning colors to each process for debugging - - def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None, pyqtapis=None): - """ - ============== ============================================================= - **Arguments:** - name Optional name for this process used when printing messages - from the remote process. - target Optional function to call after starting remote process. - By default, this is startEventLoop(), which causes the remote - process to handle requests from the parent process until it - is asked to quit. If you wish to specify a different target, - it must be picklable (bound methods are not). - copySysPath If True, copy the contents of sys.path to the remote process. - If False, then only the path required to import pyqtgraph is - added. - debug If True, print detailed information about communication - with the child process. - wrapStdout If True (default on windows) then stdout and stderr from the - child process will be caught by the parent process and - forwarded to its stdout/stderr. This provides a workaround - for a python bug: http://bugs.python.org/issue3905 - but has the side effect that child output is significantly - delayed relative to the parent output. - pyqtapis Optional dictionary of PyQt API version numbers to set before - importing pyqtgraph in the remote process. - ============== ============================================================= - """ - if target is None: - target = startEventLoop - if name is None: - name = str(self) - if executable is None: - executable = sys.executable - self.debug = 7 if debug is True else False # 7 causes printing in white - - ## random authentication key - authkey = os.urandom(20) - - ## Windows seems to have a hard time with hmac - if sys.platform.startswith('win'): - authkey = None - - #print "key:", ' '.join([str(ord(x)) for x in authkey]) - ## Listen for connection from remote process (and find free port number) - l = multiprocessing.connection.Listener(('localhost', 0), authkey=authkey) - port = l.address[1] - - ## start remote process, instruct it to run target function - if copySysPath: - sysPath = sys.path - else: - # what path do we need to make target importable? - mod = inspect.getmodule(target) - modroot = sys.modules[mod.__name__.split('.')[0]] - sysPath = os.path.abspath(os.path.join(os.path.dirname(modroot.__file__), '..')) - bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py')) - self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap)) - - # Decide on printing color for this process - if debug: - procDebug = (Process._process_count%6) + 1 # pick a color for this process to print in - Process._process_count += 1 - else: - procDebug = False - - if wrapStdout is None: - wrapStdout = sys.platform.startswith('win') - - if wrapStdout: - ## note: we need all three streams to have their own PIPE due to this bug: - ## http://bugs.python.org/issue3905 - stdout = subprocess.PIPE - stderr = subprocess.PIPE - self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) - ## to circumvent the bug and still make the output visible, we use - ## background threads to pass data from pipes to stdout/stderr - self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout", procDebug) - self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr", procDebug) - else: - self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) - - targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to - ## set its sys.path properly before unpickling the target - pid = os.getpid() # we must send pid to child because windows does not have getppid - - ## Send everything the remote process needs to start correctly - data = dict( - name=name+'_child', - port=port, - authkey=authkey, - ppid=pid, - targetStr=targetStr, - path=sysPath, - qt_lib=QT_LIB, - debug=procDebug, - pyqtapis=pyqtapis, - ) - pickle.dump(data, self.proc.stdin) - self.proc.stdin.close() - - ## open connection for remote process - self.debugMsg('Listening for child process on port %d, authkey=%s..' % (port, repr(authkey))) - while True: - try: - conn = l.accept() - break - except IOError as err: - if err.errno == 4: # interrupted; try again - continue - else: - raise - - RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=self.debug) - self.debugMsg('Connected to child process.') - - atexit.register(self.join) - - - def join(self, timeout=10): - self.debugMsg('Joining child process..') - if self.proc.poll() is None: - self.close() - start = time.time() - while self.proc.poll() is None: - if timeout is not None and time.time() - start > timeout: - raise Exception('Timed out waiting for remote process to end.') - time.sleep(0.05) - self.conn.close() - - # Close remote polling threads, otherwise they will spin continuously - if hasattr(self, "_stdoutForwarder"): - self._stdoutForwarder.finish.set() - self._stderrForwarder.finish.set() - self._stdoutForwarder.join() - self._stderrForwarder.join() - - self.debugMsg('Child process exited. (%d)' % self.proc.returncode) - - def debugMsg(self, msg, *args): - if hasattr(self, '_stdoutForwarder'): - ## Lock output from subprocess to make sure we do not get line collisions - with self._stdoutForwarder.lock: - with self._stderrForwarder.lock: - RemoteEventHandler.debugMsg(self, msg, *args) - else: - RemoteEventHandler.debugMsg(self, msg, *args) - - -def startEventLoop(name, port, authkey, ppid, debug=False): - if debug: - import os - cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' - % (os.getpid(), port, repr(authkey)), -1) - conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) - if debug: - cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) - global HANDLER - #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() - HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug) - while True: - try: - HANDLER.processRequests() # exception raised when the loop should exit - time.sleep(0.01) - except ClosedError: - HANDLER.debugMsg('Exiting server loop.') - sys.exit(0) - - -class ForkedProcess(RemoteEventHandler): - """ - ForkedProcess is a substitute for Process that uses os.fork() to generate a new process. - This is much faster than starting a completely new interpreter and child processes - automatically have a copy of the entire program state from before the fork. This - makes it an appealing approach when parallelizing expensive computations. (see - also Parallelizer) - - However, fork() comes with some caveats and limitations: - - - fork() is not available on Windows. - - It is not possible to have a QApplication in both parent and child process - (unless both QApplications are created _after_ the call to fork()) - Attempts by the forked process to access Qt GUI elements created by the parent - will most likely cause the child to crash. - - Likewise, database connections are unlikely to function correctly in a forked child. - - Threads are not copied by fork(); the new process - will have only one thread that starts wherever fork() was called in the parent process. - - Forked processes are unceremoniously terminated when join() is called; they are not - given any opportunity to clean up. (This prevents them calling any cleanup code that - was only intended to be used by the parent process) - - Normally when fork()ing, open file handles are shared with the parent process, - which is potentially dangerous. ForkedProcess is careful to close all file handles - that are not explicitly needed--stdout, stderr, and a single pipe to the parent - process. - - """ - - def __init__(self, name=None, target=0, preProxy=None, randomReseed=True): - """ - When initializing, an optional target may be given. - If no target is specified, self.eventLoop will be used. - If None is given, no target will be called (and it will be up - to the caller to properly shut down the forked process) - - preProxy may be a dict of values that will appear as ObjectProxy - in the remote process (but do not need to be sent explicitly since - they are available immediately before the call to fork(). - Proxies will be availabe as self.proxies[name]. - - If randomReseed is True, the built-in random and numpy.random generators - will be reseeded in the child process. - """ - self.hasJoined = False - if target == 0: - target = self.eventLoop - if name is None: - name = str(self) - - conn, remoteConn = multiprocessing.Pipe() - - proxyIDs = {} - if preProxy is not None: - for k, v in preProxy.items(): - proxyId = LocalObjectProxy.registerObject(v) - proxyIDs[k] = proxyId - - ppid = os.getpid() # write this down now; windows doesn't have getppid - pid = os.fork() - if pid == 0: - self.isParent = False - ## We are now in the forked process; need to be extra careful what we touch while here. - ## - no reading/writing file handles/sockets owned by parent process (stdout is ok) - ## - don't touch QtGui or QApplication at all; these are landmines. - ## - don't let the process call exit handlers - - os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process - - ## close all file handles we do not want shared with parent - conn.close() - sys.stdin.close() ## otherwise we screw with interactive prompts. - fid = remoteConn.fileno() - os.closerange(3, fid) - os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count.. - - ## Override any custom exception hooks - def excepthook(*args): - import traceback - traceback.print_exception(*args) - sys.excepthook = excepthook - - ## Make it harder to access QApplication instance - for qtlib in ('PyQt4', 'PySide', 'PyQt5'): - if qtlib in sys.modules: - sys.modules[qtlib+'.QtGui'].QApplication = None - sys.modules.pop(qtlib+'.QtGui', None) - sys.modules.pop(qtlib+'.QtCore', None) - - ## sabotage atexit callbacks - atexit._exithandlers = [] - atexit.register(lambda: os._exit(0)) - - if randomReseed: - if 'numpy.random' in sys.modules: - sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000)) - if 'random' in sys.modules: - sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) - - #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() - RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid) - - self.forkedProxies = {} - for name, proxyId in proxyIDs.items(): - self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) - - if target is not None: - target() - - else: - self.isParent = True - self.childPid = pid - remoteConn.close() - RemoteEventHandler.handlers = {} ## don't want to inherit any of this from the parent. - - RemoteEventHandler.__init__(self, conn, name+'_parent', pid=pid) - atexit.register(self.join) - - - def eventLoop(self): - while True: - try: - self.processRequests() # exception raised when the loop should exit - time.sleep(0.01) - except ClosedError: - break - except: - print("Error occurred in forked event loop:") - sys.excepthook(*sys.exc_info()) - sys.exit(0) - - def join(self, timeout=10): - if self.hasJoined: - return - #os.kill(pid, 9) - try: - self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation. - except IOError: ## probably remote process has already quit - pass - - try: - os.waitpid(self.childPid, 0) - except OSError: ## probably remote process has already quit - pass - - self.conn.close() # don't leak file handles! - self.hasJoined = True - - def kill(self): - """Immediately kill the forked remote process. - This is generally safe because forked processes are already - expected to _avoid_ any cleanup at exit.""" - os.kill(self.childPid, signal.SIGKILL) - self.hasJoined = True - - - -##Special set of subclasses that implement a Qt event loop instead. - -class RemoteQtEventHandler(RemoteEventHandler): - def __init__(self, *args, **kwds): - RemoteEventHandler.__init__(self, *args, **kwds) - - def startEventTimer(self): - from ..Qt import QtGui, QtCore - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.processRequests) - self.timer.start(10) - - def processRequests(self): - try: - RemoteEventHandler.processRequests(self) - except ClosedError: - from ..Qt import QtGui, QtCore - QtGui.QApplication.instance().quit() - self.timer.stop() - #raise SystemExit - -class QtProcess(Process): - """ - QtProcess is essentially the same as Process, with two major differences: - - - The remote process starts by running startQtEventLoop() which creates a - QApplication in the remote process and uses a QTimer to trigger - remote event processing. This allows the remote process to have its own - GUI. - - A QTimer is also started on the parent process which polls for requests - from the child process. This allows Qt signals emitted within the child - process to invoke slots on the parent process and vice-versa. This can - be disabled using processRequests=False in the constructor. - - Example:: - - proc = QtProcess() - rQtGui = proc._import('PyQt4.QtGui') - btn = rQtGui.QPushButton('button on child process') - btn.show() - - def slot(): - print('slot invoked on parent process') - btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot - """ - - def __init__(self, **kwds): - if 'target' not in kwds: - kwds['target'] = startQtEventLoop - from ..Qt import QtGui ## avoid module-level import to keep bootstrap snappy. - self._processRequests = kwds.pop('processRequests', True) - if self._processRequests and QtGui.QApplication.instance() is None: - raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") - Process.__init__(self, **kwds) - self.startEventTimer() - - def startEventTimer(self): - from ..Qt import QtCore ## avoid module-level import to keep bootstrap snappy. - self.timer = QtCore.QTimer() - if self._processRequests: - self.startRequestProcessing() - - def startRequestProcessing(self, interval=0.01): - """Start listening for requests coming from the child process. - This allows signals to be connected from the child process to the parent. - """ - self.timer.timeout.connect(self.processRequests) - self.timer.start(int(interval*1000)) - - def stopRequestProcessing(self): - self.timer.stop() - - def processRequests(self): - try: - Process.processRequests(self) - except ClosedError: - self.timer.stop() - -def startQtEventLoop(name, port, authkey, ppid, debug=False): - if debug: - import os - cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' % (os.getpid(), port, repr(authkey)), -1) - conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) - if debug: - cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) - from ..Qt import QtGui, QtCore - app = QtGui.QApplication.instance() - #print app - if app is None: - app = mkQApp() - app.setQuitOnLastWindowClosed(False) ## generally we want the event loop to stay open - ## until it is explicitly closed by the parent process. - - global HANDLER - HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug) - HANDLER.startEventTimer() - app.exec_() - -import threading -class FileForwarder(threading.Thread): - """ - Background thread that forwards data from one pipe to another. - This is used to catch data from stdout/stderr of the child process - and print it back out to stdout/stderr. We need this because this - bug: http://bugs.python.org/issue3905 _requires_ us to catch - stdout/stderr. - - *output* may be a file or 'stdout' or 'stderr'. In the latter cases, - sys.stdout/stderr are retrieved once for every line that is output, - which ensures that the correct behavior is achieved even if - sys.stdout/stderr are replaced at runtime. - """ - def __init__(self, input, output, color): - threading.Thread.__init__(self) - self.input = input - self.output = output - self.lock = threading.Lock() - self.daemon = True - self.color = color - self.finish = threading.Event() - self.start() - - def run(self): - if self.output == 'stdout' and self.color is not False: - while not self.finish.is_set(): - line = self.input.readline() - with self.lock: - cprint.cout(self.color, line, -1) - elif self.output == 'stderr' and self.color is not False: - while not self.finish.is_set(): - line = self.input.readline() - with self.lock: - cprint.cerr(self.color, line, -1) - else: - if isinstance(self.output, str): - self.output = getattr(sys, self.output) - while not self.finish.is_set(): - line = self.input.readline() - with self.lock: - self.output.write(line.decode('utf8')) diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py deleted file mode 100644 index f0d993c..0000000 --- a/pyqtgraph/multiprocess/remoteproxy.py +++ /dev/null @@ -1,1131 +0,0 @@ -import os, time, sys, traceback, weakref -import numpy as np -import threading -import warnings -try: - import __builtin__ as builtins - import cPickle as pickle -except ImportError: - import builtins - import pickle - -# color printing for debugging -from ..util import cprint - -class ClosedError(Exception): - """Raised when an event handler receives a request to close the connection - or discovers that the connection has been closed.""" - pass - -class NoResultError(Exception): - """Raised when a request for the return value of a remote call fails - because the call has not yet returned.""" - pass - -class RemoteExceptionWarning(UserWarning): - """Emitted when a request to a remote object results in an Exception """ - pass - -class RemoteEventHandler(object): - """ - This class handles communication between two processes. One instance is present on - each process and listens for communication from the other process. This enables - (amongst other things) ObjectProxy instances to look up their attributes and call - their methods. - - This class is responsible for carrying out actions on behalf of the remote process. - Each instance holds one end of a Connection which allows python - objects to be passed between processes. - - For the most common operations, see _import(), close(), and transfer() - - To handle and respond to incoming requests, RemoteEventHandler requires that its - processRequests method is called repeatedly (this is usually handled by the Process - classes defined in multiprocess.processes). - - - - - """ - handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process - ## an object proxy belongs to - - def __init__(self, connection, name, pid, debug=False): - self.debug = debug - self.conn = connection - self.name = name - self.results = {} ## reqId: (status, result); cache of request results received from the remote process - ## status is either 'result' or 'error' - ## if 'error', then result will be (exception, formatted exceprion) - ## where exception may be None if it could not be passed through the Connection. - self.resultLock = threading.RLock() - - self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted. - self.proxyLock = threading.RLock() - - ## attributes that affect the behavior of the proxy. - ## See ObjectProxy._setProxyOptions for description - self.proxyOptions = { - 'callSync': 'sync', ## 'sync', 'async', 'off' - 'timeout': 10, ## float - 'returnType': 'auto', ## 'proxy', 'value', 'auto' - 'autoProxy': False, ## bool - 'deferGetattr': False, ## True, False - 'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ], - } - if int(sys.version[0]) < 3: - self.proxyOptions['noProxyTypes'].append(unicode) - else: - self.proxyOptions['noProxyTypes'].append(bytes) - - self.optsLock = threading.RLock() - - self.nextRequestId = 0 - self.exited = False - - # Mutexes to help prevent issues when multiple threads access the same RemoteEventHandler - self.processLock = threading.RLock() - self.sendLock = threading.RLock() - - RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid - - @classmethod - def getHandler(cls, pid): - try: - return cls.handlers[pid] - except: - print(pid, cls.handlers) - raise - - def debugMsg(self, msg, *args): - if not self.debug: - return - cprint.cout(self.debug, "[%d] %s\n" % (os.getpid(), str(msg)%args), -1) - - def getProxyOption(self, opt): - with self.optsLock: - return self.proxyOptions[opt] - - def setProxyOptions(self, **kwds): - """ - Set the default behavior options for object proxies. - See ObjectProxy._setProxyOptions for more info. - """ - with self.optsLock: - self.proxyOptions.update(kwds) - - def processRequests(self): - """Process all pending requests from the pipe, return - after no more events are immediately available. (non-blocking) - Returns the number of events processed. - """ - with self.processLock: - - if self.exited: - self.debugMsg(' processRequests: exited already; raise ClosedError.') - raise ClosedError() - - numProcessed = 0 - - while self.conn.poll(): - #try: - #poll = self.conn.poll() - #if not poll: - #break - #except IOError: # this can happen if the remote process dies. - ## might it also happen in other circumstances? - #raise ClosedError() - - try: - self.handleRequest() - numProcessed += 1 - except ClosedError: - self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.') - self.exited = True - raise - #except IOError as err: ## let handleRequest take care of this. - #self.debugMsg(' got IOError from handleRequest; try again.') - #if err.errno == 4: ## interrupted system call; try again - #continue - #else: - #raise - except: - print("Error in process %s" % self.name) - sys.excepthook(*sys.exc_info()) - - if numProcessed > 0: - self.debugMsg('processRequests: finished %d requests', numProcessed) - return numProcessed - - def handleRequest(self): - """Handle a single request from the remote process. - Blocks until a request is available.""" - result = None - while True: - try: - ## args, kwds are double-pickled to ensure this recv() call never fails - cmd, reqId, nByteMsgs, optStr = self.conn.recv() - break - except EOFError: - self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.') - ## remote process has shut down; end event loop - raise ClosedError() - except IOError as err: - if err.errno == 4: ## interrupted system call; try again - self.debugMsg(' handleRequest: got IOError 4 from recv; try again.') - continue - else: - self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.', err.errno, err.strerror) - raise ClosedError() - - self.debugMsg(" handleRequest: received %s %s", cmd, reqId) - - ## read byte messages following the main request - byteData = [] - if nByteMsgs > 0: - self.debugMsg(" handleRequest: reading %d byte messages", nByteMsgs) - for i in range(nByteMsgs): - while True: - try: - byteData.append(self.conn.recv_bytes()) - break - except EOFError: - self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.") - raise ClosedError() - except IOError as err: - if err.errno == 4: - self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.") - continue - else: - self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.") - raise ClosedError() - - - try: - if cmd == 'result' or cmd == 'error': - resultId = reqId - reqId = None ## prevents attempt to return information from this request - ## (this is already a return from a previous request) - - opts = pickle.loads(optStr) - self.debugMsg(" handleRequest: id=%s opts=%s", reqId, opts) - #print os.getpid(), "received request:", cmd, reqId, opts - returnType = opts.get('returnType', 'auto') - - if cmd == 'result': - with self.resultLock: - self.results[resultId] = ('result', opts['result']) - elif cmd == 'error': - with self.resultLock: - self.results[resultId] = ('error', (opts['exception'], opts['excString'])) - elif cmd == 'getObjAttr': - result = getattr(opts['obj'], opts['attr']) - elif cmd == 'callObj': - obj = opts['obj'] - fnargs = opts['args'] - fnkwds = opts['kwds'] - - ## If arrays were sent as byte messages, they must be re-inserted into the - ## arguments - if len(byteData) > 0: - for i,arg in enumerate(fnargs): - if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': - ind = arg[1] - dtype, shape = arg[2] - fnargs[i] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) - for k,arg in fnkwds.items(): - if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': - ind = arg[1] - dtype, shape = arg[2] - fnkwds[k] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) - - if len(fnkwds) == 0: ## need to do this because some functions do not allow keyword arguments. - try: - result = obj(*fnargs) - except: - print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:])) - raise - else: - result = obj(*fnargs, **fnkwds) - - elif cmd == 'getObjValue': - result = opts['obj'] ## has already been unpickled into its local value - returnType = 'value' - elif cmd == 'transfer': - result = opts['obj'] - returnType = 'proxy' - elif cmd == 'transferArray': - ## read array data from next message: - result = np.fromstring(byteData[0], dtype=opts['dtype']).reshape(opts['shape']) - returnType = 'proxy' - elif cmd == 'import': - name = opts['module'] - fromlist = opts.get('fromlist', []) - mod = builtins.__import__(name, fromlist=fromlist) - - if len(fromlist) == 0: - parts = name.lstrip('.').split('.') - result = mod - for part in parts[1:]: - result = getattr(result, part) - else: - result = map(mod.__getattr__, fromlist) - - elif cmd == 'del': - LocalObjectProxy.releaseProxyId(opts['proxyId']) - #del self.proxiedObjects[opts['objId']] - - elif cmd == 'close': - if reqId is not None: - result = True - returnType = 'value' - - exc = None - except: - exc = sys.exc_info() - - - - if reqId is not None: - if exc is None: - self.debugMsg(" handleRequest: sending return value for %d: %s", reqId, result) - #print "returnValue:", returnValue, result - if returnType == 'auto': - with self.optsLock: - noProxyTypes = self.proxyOptions['noProxyTypes'] - result = self.autoProxy(result, noProxyTypes) - elif returnType == 'proxy': - result = LocalObjectProxy(result) - - try: - self.replyResult(reqId, result) - except: - sys.excepthook(*sys.exc_info()) - self.replyError(reqId, *sys.exc_info()) - else: - self.debugMsg(" handleRequest: returning exception for %d", reqId) - self.replyError(reqId, *exc) - - elif exc is not None: - sys.excepthook(*exc) - - if cmd == 'close': - if opts.get('noCleanup', False) is True: - os._exit(0) ## exit immediately, do not pass GO, do not collect $200. - ## (more importantly, do not call any code that would - ## normally be invoked at exit) - else: - raise ClosedError() - - - - def replyResult(self, reqId, result): - self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result)) - - def replyError(self, reqId, *exc): - print("error: %s %s %s" % (self.name, str(reqId), str(exc[1]))) - excStr = traceback.format_exception(*exc) - try: - self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr)) - except: - self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=None, excString=excStr)) - - def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, returnType=None, byteData=None, **kwds): - """Send a request or return packet to the remote process. - Generally it is not necessary to call this method directly; it is for internal use. - (The docstring has information that is nevertheless useful to the programmer - as it describes the internal protocol used to communicate between processes) - - ============== ==================================================================== - **Arguments:** - request String describing the type of request being sent (see below) - reqId Integer uniquely linking a result back to the request that generated - it. (most requests leave this blank) - callSync 'sync': return the actual result of the request - 'async': return a Request object which can be used to look up the - result later - 'off': return no result - timeout Time in seconds to wait for a response when callSync=='sync' - opts Extra arguments sent to the remote process that determine the way - the request will be handled (see below) - returnType 'proxy', 'value', or 'auto' - byteData If specified, this is a list of objects to be sent as byte messages - to the remote process. - This is used to send large arrays without the cost of pickling. - ============== ==================================================================== - - Description of request strings and options allowed for each: - - ============= ============= ======================================================== - request option description - ------------- ------------- -------------------------------------------------------- - getObjAttr Request the remote process return (proxy to) an - attribute of an object. - obj reference to object whose attribute should be - returned - attr string name of attribute to return - returnValue bool or 'auto' indicating whether to return a proxy or - the actual value. - - callObj Request the remote process call a function or - method. If a request ID is given, then the call's - return value will be sent back (or information - about the error that occurred while running the - function) - obj the (reference to) object to call - args tuple of arguments to pass to callable - kwds dict of keyword arguments to pass to callable - returnValue bool or 'auto' indicating whether to return a proxy or - the actual value. - - getObjValue Request the remote process return the value of - a proxied object (must be picklable) - obj reference to object whose value should be returned - - transfer Copy an object to the remote process and request - it return a proxy for the new object. - obj The object to transfer. - - import Request the remote process import new symbols - and return proxy(ies) to the imported objects - module the string name of the module to import - fromlist optional list of string names to import from module - - del Inform the remote process that a proxy has been - released (thus the remote process may be able to - release the original object) - proxyId id of proxy which is no longer referenced by - remote host - - close Instruct the remote process to stop its event loop - and exit. Optionally, this request may return a - confirmation. - - result Inform the remote process that its request has - been processed - result return value of a request - - error Inform the remote process that its request failed - exception the Exception that was raised (or None if the - exception could not be pickled) - excString string-formatted version of the exception and - traceback - ============= ===================================================================== - """ - if self.exited: - self.debugMsg(' send: exited already; raise ClosedError.') - raise ClosedError() - - with self.sendLock: - #if len(kwds) > 0: - #print "Warning: send() ignored args:", kwds - - if opts is None: - opts = {} - - assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async" (got %r)' % callSync - if reqId is None: - if callSync != 'off': ## requested return value; use the next available request ID - reqId = self.nextRequestId - self.nextRequestId += 1 - else: - ## If requestId is provided, this _must_ be a response to a previously received request. - assert request in ['result', 'error'] - - if returnType is not None: - opts['returnType'] = returnType - - #print os.getpid(), "send request:", request, reqId, opts - - ## double-pickle args to ensure that at least status and request ID get through - try: - optStr = pickle.dumps(opts) - except: - print("==== Error pickling this object: ====") - print(opts) - print("=======================================") - raise - - nByteMsgs = 0 - if byteData is not None: - nByteMsgs = len(byteData) - - ## Send primary request - request = (request, reqId, nByteMsgs, optStr) - self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s', request[0], nByteMsgs, reqId, opts) - self.conn.send(request) - - ## follow up by sending byte messages - if byteData is not None: - for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages! - self.conn.send_bytes(bytes(obj)) - self.debugMsg(' sent %d byte messages', len(byteData)) - - self.debugMsg(' call sync: %s', callSync) - if callSync == 'off': - return - - req = Request(self, reqId, description=str(request), timeout=timeout) - if callSync == 'async': - return req - - if callSync == 'sync': - return req.result() - - def close(self, callSync='off', noCleanup=False, **kwds): - try: - self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds) - self.exited = True - except ClosedError: - pass - - def getResult(self, reqId): - ## raises NoResultError if the result is not available yet - #print self.results.keys(), os.getpid() - with self.resultLock: - haveResult = reqId in self.results - - if not haveResult: - try: - self.processRequests() - except ClosedError: ## even if remote connection has closed, we may have - ## received new data during this call to processRequests() - pass - - with self.resultLock: - if reqId not in self.results: - raise NoResultError() - status, result = self.results.pop(reqId) - - if status == 'result': - return result - elif status == 'error': - #print ''.join(result) - exc, excStr = result - if exc is not None: - warnings.warn("===== Remote process raised exception on request: =====", RemoteExceptionWarning) - warnings.warn(''.join(excStr), RemoteExceptionWarning) - warnings.warn("===== Local Traceback to request follows: =====", RemoteExceptionWarning) - raise exc - else: - print(''.join(excStr)) - raise Exception("Error getting result. See above for exception from remote process.") - - else: - raise Exception("Internal error.") - - def _import(self, mod, **kwds): - """ - Request the remote process import a module (or symbols from a module) - and return the proxied results. Uses built-in __import__() function, but - adds a bit more processing: - - _import('module') => returns module - _import('module.submodule') => returns submodule - (note this differs from behavior of __import__) - _import('module', fromlist=[name1, name2, ...]) => returns [module.name1, module.name2, ...] - (this also differs from behavior of __import__) - - """ - return self.send(request='import', callSync='sync', opts=dict(module=mod), **kwds) - - def getObjAttr(self, obj, attr, **kwds): - return self.send(request='getObjAttr', opts=dict(obj=obj, attr=attr), **kwds) - - def getObjValue(self, obj, **kwds): - return self.send(request='getObjValue', opts=dict(obj=obj), **kwds) - - def callObj(self, obj, args, kwds, **opts): - opts = opts.copy() - args = list(args) - - ## Decide whether to send arguments by value or by proxy - with self.optsLock: - noProxyTypes = opts.pop('noProxyTypes', None) - if noProxyTypes is None: - noProxyTypes = self.proxyOptions['noProxyTypes'] - - autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy']) - - if autoProxy is True: - args = [self.autoProxy(v, noProxyTypes) for v in args] - for k, v in kwds.items(): - opts[k] = self.autoProxy(v, noProxyTypes) - - byteMsgs = [] - - ## If there are arrays in the arguments, send those as byte messages. - ## We do this because pickling arrays is too expensive. - for i,arg in enumerate(args): - if arg.__class__ == np.ndarray: - args[i] = ("__byte_message__", len(byteMsgs), (arg.dtype, arg.shape)) - byteMsgs.append(arg) - for k,v in kwds.items(): - if v.__class__ == np.ndarray: - kwds[k] = ("__byte_message__", len(byteMsgs), (v.dtype, v.shape)) - byteMsgs.append(v) - - return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts) - - def registerProxy(self, proxy): - with self.proxyLock: - ref = weakref.ref(proxy, self.deleteProxy) - self.proxies[ref] = proxy._proxyId - - def deleteProxy(self, ref): - if self.send is None: - # this can happen during shutdown - return - - with self.proxyLock: - proxyId = self.proxies.pop(ref) - - try: - self.send(request='del', opts=dict(proxyId=proxyId), callSync='off') - except ClosedError: ## if remote process has closed down, there is no need to send delete requests anymore - pass - - def transfer(self, obj, **kwds): - """ - Transfer an object by value to the remote host (the object must be picklable) - and return a proxy for the new remote object. - """ - if obj.__class__ is np.ndarray: - opts = {'dtype': obj.dtype, 'shape': obj.shape} - return self.send(request='transferArray', opts=opts, byteData=[obj], **kwds) - else: - return self.send(request='transfer', opts=dict(obj=obj), **kwds) - - def autoProxy(self, obj, noProxyTypes): - ## Return object wrapped in LocalObjectProxy _unless_ its type is in noProxyTypes. - for typ in noProxyTypes: - if isinstance(obj, typ): - return obj - return LocalObjectProxy(obj) - - -class Request(object): - """ - Request objects are returned when calling an ObjectProxy in asynchronous mode - or if a synchronous call has timed out. Use hasResult() to ask whether - the result of the call has been returned yet. Use result() to get - the returned value. - """ - def __init__(self, process, reqId, description=None, timeout=10): - self.proc = process - self.description = description - self.reqId = reqId - self.gotResult = False - self._result = None - self.timeout = timeout - - def result(self, block=True, timeout=None): - """ - Return the result for this request. - - If block is True, wait until the result has arrived or *timeout* seconds passes. - If the timeout is reached, raise NoResultError. (use timeout=None to disable) - If block is False, raise NoResultError immediately if the result has not arrived yet. - - If the process's connection has closed before the result arrives, raise ClosedError. - """ - - if self.gotResult: - return self._result - - if timeout is None: - timeout = self.timeout - - if block: - start = time.time() - while not self.hasResult(): - if self.proc.exited: - raise ClosedError() - time.sleep(0.005) - if timeout >= 0 and time.time() - start > timeout: - print("Request timed out: %s" % self.description) - import traceback - traceback.print_stack() - raise NoResultError() - return self._result - else: - self._result = self.proc.getResult(self.reqId) ## raises NoResultError if result is not available yet - self.gotResult = True - return self._result - - def hasResult(self): - """Returns True if the result for this request has arrived.""" - try: - self.result(block=False) - except NoResultError: - pass - - return self.gotResult - -class LocalObjectProxy(object): - """ - Used for wrapping local objects to ensure that they are send by proxy to a remote host. - Note that 'proxy' is just a shorter alias for LocalObjectProxy. - - For example:: - - data = [1,2,3,4,5] - remotePlot.plot(data) ## by default, lists are pickled and sent by value - remotePlot.plot(proxy(data)) ## force the object to be sent by proxy - - """ - nextProxyId = 0 - proxiedObjects = {} ## maps {proxyId: object} - - - @classmethod - def registerObject(cls, obj): - ## assign it a unique ID so we can keep a reference to the local object - - pid = cls.nextProxyId - cls.nextProxyId += 1 - cls.proxiedObjects[pid] = obj - #print "register:", cls.proxiedObjects - return pid - - @classmethod - def lookupProxyId(cls, pid): - return cls.proxiedObjects[pid] - - @classmethod - def releaseProxyId(cls, pid): - del cls.proxiedObjects[pid] - #print "release:", cls.proxiedObjects - - def __init__(self, obj, **opts): - """ - Create a 'local' proxy object that, when sent to a remote host, - will appear as a normal ObjectProxy to *obj*. - Any extra keyword arguments are passed to proxy._setProxyOptions() - on the remote side. - """ - self.processId = os.getpid() - #self.objectId = id(obj) - self.typeStr = repr(obj) - #self.handler = handler - self.obj = obj - self.opts = opts - - def __reduce__(self): - ## a proxy is being pickled and sent to a remote process. - ## every time this happens, a new proxy will be generated in the remote process, - ## so we keep a new ID so we can track when each is released. - pid = LocalObjectProxy.registerObject(self.obj) - return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts)) - -## alias -proxy = LocalObjectProxy - -def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None): - if processId == os.getpid(): - obj = LocalObjectProxy.lookupProxyId(proxyId) - if attributes is not None: - for attr in attributes: - obj = getattr(obj, attr) - return obj - else: - proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr) - if opts is not None: - proxy._setProxyOptions(**opts) - return proxy - -class ObjectProxy(object): - """ - Proxy to an object stored by the remote process. Proxies are created - by calling Process._import(), Process.transfer(), or by requesting/calling - attributes on existing proxy objects. - - For the most part, this object can be used exactly as if it - were a local object:: - - rsys = proc._import('sys') # returns proxy to sys module on remote process - rsys.stdout # proxy to remote sys.stdout - rsys.stdout.write # proxy to remote sys.stdout.write - rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine - # and returns the result (None) - - When calling a proxy to a remote function, the call can be made synchronous - (result of call is returned immediately), asynchronous (result is returned later), - or return can be disabled entirely:: - - ros = proc._import('os') - - ## synchronous call; result is returned immediately - pid = ros.getpid() - - ## asynchronous call - request = ros.getpid(_callSync='async') - while not request.hasResult(): - time.sleep(0.01) - pid = request.result() - - ## disable return when we know it isn't needed - rsys.stdout.write('hello', _callSync='off') - - Additionally, values returned from a remote function call are automatically - returned either by value (must be picklable) or by proxy. - This behavior can be forced:: - - rnp = proc._import('numpy') - arrProxy = rnp.array([1,2,3,4], _returnType='proxy') - arrValue = rnp.array([1,2,3,4], _returnType='value') - - The default callSync and returnType behaviors (as well as others) can be set - for each proxy individually using ObjectProxy._setProxyOptions() or globally using - proc.setProxyOptions(). - - """ - def __init__(self, processId, proxyId, typeStr='', parent=None): - object.__init__(self) - ## can't set attributes directly because setattr is overridden. - self.__dict__['_processId'] = processId - self.__dict__['_typeStr'] = typeStr - self.__dict__['_proxyId'] = proxyId - self.__dict__['_attributes'] = () - ## attributes that affect the behavior of the proxy. - ## in all cases, a value of None causes the proxy to ask - ## its parent event handler to make the decision - self.__dict__['_proxyOptions'] = { - 'callSync': None, ## 'sync', 'async', None - 'timeout': None, ## float, None - 'returnType': None, ## 'proxy', 'value', 'auto', None - 'deferGetattr': None, ## True, False, None - 'noProxyTypes': None, ## list of types to send by value instead of by proxy - 'autoProxy': None, - } - - self.__dict__['_handler'] = RemoteEventHandler.getHandler(processId) - self.__dict__['_handler'].registerProxy(self) ## handler will watch proxy; inform remote process when the proxy is deleted. - - def _setProxyOptions(self, **kwds): - """ - Change the behavior of this proxy. For all options, a value of None - will cause the proxy to instead use the default behavior defined - by its parent Process. - - Options are: - - ============= ============================================================= - callSync 'sync', 'async', 'off', or None. - If 'async', then calling methods will return a Request object - which can be used to inquire later about the result of the - method call. - If 'sync', then calling a method - will block until the remote process has returned its result - or the timeout has elapsed (in this case, a Request object - is returned instead). - If 'off', then the remote process is instructed _not_ to - reply and the method call will return None immediately. - returnType 'auto', 'proxy', 'value', or None. - If 'proxy', then the value returned when calling a method - will be a proxy to the object on the remote process. - If 'value', then attempt to pickle the returned object and - send it back. - If 'auto', then the decision is made by consulting the - 'noProxyTypes' option. - autoProxy bool or None. If True, arguments to __call__ are - automatically converted to proxy unless their type is - listed in noProxyTypes (see below). If False, arguments - are left untouched. Use proxy(obj) to manually convert - arguments before sending. - timeout float or None. Length of time to wait during synchronous - requests before returning a Request object instead. - deferGetattr True, False, or None. - If False, all attribute requests will be sent to the remote - process immediately and will block until a response is - received (or timeout has elapsed). - If True, requesting an attribute from the proxy returns a - new proxy immediately. The remote process is _not_ contacted - to make this request. This is faster, but it is possible to - request an attribute that does not exist on the proxied - object. In this case, AttributeError will not be raised - until an attempt is made to look up the attribute on the - remote process. - noProxyTypes List of object types that should _not_ be proxied when - sent to the remote process. - ============= ============================================================= - """ - for k in kwds: - if k not in self._proxyOptions: - raise KeyError("Unrecognized proxy option '%s'" % k) - self._proxyOptions.update(kwds) - - def _getValue(self): - """ - Return the value of the proxied object - (the remote object must be picklable) - """ - return self._handler.getObjValue(self) - - def _getProxyOption(self, opt): - val = self._proxyOptions[opt] - if val is None: - return self._handler.getProxyOption(opt) - return val - - def _getProxyOptions(self): - return dict([(k, self._getProxyOption(k)) for k in self._proxyOptions]) - - def __reduce__(self): - return (unpickleObjectProxy, (self._processId, self._proxyId, self._typeStr, self._attributes)) - - def __repr__(self): - #objRepr = self.__getattr__('__repr__')(callSync='value') - return "" % (self._processId, self._proxyId, self._typeStr) - - - def __getattr__(self, attr, **kwds): - """ - Calls __getattr__ on the remote object and returns the attribute - by value or by proxy depending on the options set (see - ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions) - - If the option 'deferGetattr' is True for this proxy, then a new proxy object - is returned _without_ asking the remote object whether the named attribute exists. - This can save time when making multiple chained attribute requests, - but may also defer a possible AttributeError until later, making - them more difficult to debug. - """ - opts = self._getProxyOptions() - for k in opts: - if '_'+k in kwds: - opts[k] = kwds.pop('_'+k) - if opts['deferGetattr'] is True: - return self._deferredAttr(attr) - else: - #opts = self._getProxyOptions() - return self._handler.getObjAttr(self, attr, **opts) - - def _deferredAttr(self, attr): - return DeferredObjectProxy(self, attr) - - def __call__(self, *args, **kwds): - """ - Attempts to call the proxied object from the remote process. - Accepts extra keyword arguments: - - _callSync 'off', 'sync', or 'async' - _returnType 'value', 'proxy', or 'auto' - - If the remote call raises an exception on the remote process, - it will be re-raised on the local process. - - """ - opts = self._getProxyOptions() - for k in opts: - if '_'+k in kwds: - opts[k] = kwds.pop('_'+k) - return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts) - - - ## Explicitly proxy special methods. Is there a better way to do this?? - - def _getSpecialAttr(self, attr): - ## this just gives us an easy way to change the behavior of the special methods - return self._deferredAttr(attr) - - def __getitem__(self, *args): - return self._getSpecialAttr('__getitem__')(*args) - - def __setitem__(self, *args): - return self._getSpecialAttr('__setitem__')(*args, _callSync='off') - - def __setattr__(self, *args): - return self._getSpecialAttr('__setattr__')(*args, _callSync='off') - - def __str__(self, *args): - return self._getSpecialAttr('__str__')(*args, _returnType='value') - - def __len__(self, *args): - return self._getSpecialAttr('__len__')(*args) - - def __add__(self, *args): - return self._getSpecialAttr('__add__')(*args) - - def __sub__(self, *args): - return self._getSpecialAttr('__sub__')(*args) - - def __div__(self, *args): - return self._getSpecialAttr('__div__')(*args) - - def __truediv__(self, *args): - return self._getSpecialAttr('__truediv__')(*args) - - def __floordiv__(self, *args): - return self._getSpecialAttr('__floordiv__')(*args) - - def __mul__(self, *args): - return self._getSpecialAttr('__mul__')(*args) - - def __pow__(self, *args): - return self._getSpecialAttr('__pow__')(*args) - - def __iadd__(self, *args): - return self._getSpecialAttr('__iadd__')(*args, _callSync='off') - - def __isub__(self, *args): - return self._getSpecialAttr('__isub__')(*args, _callSync='off') - - def __idiv__(self, *args): - return self._getSpecialAttr('__idiv__')(*args, _callSync='off') - - def __itruediv__(self, *args): - return self._getSpecialAttr('__itruediv__')(*args, _callSync='off') - - def __ifloordiv__(self, *args): - return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off') - - def __imul__(self, *args): - return self._getSpecialAttr('__imul__')(*args, _callSync='off') - - def __ipow__(self, *args): - return self._getSpecialAttr('__ipow__')(*args, _callSync='off') - - def __rshift__(self, *args): - return self._getSpecialAttr('__rshift__')(*args) - - def __lshift__(self, *args): - return self._getSpecialAttr('__lshift__')(*args) - - def __irshift__(self, *args): - return self._getSpecialAttr('__irshift__')(*args, _callSync='off') - - def __ilshift__(self, *args): - return self._getSpecialAttr('__ilshift__')(*args, _callSync='off') - - def __eq__(self, *args): - return self._getSpecialAttr('__eq__')(*args) - - def __ne__(self, *args): - return self._getSpecialAttr('__ne__')(*args) - - def __lt__(self, *args): - return self._getSpecialAttr('__lt__')(*args) - - def __gt__(self, *args): - return self._getSpecialAttr('__gt__')(*args) - - def __le__(self, *args): - return self._getSpecialAttr('__le__')(*args) - - def __ge__(self, *args): - return self._getSpecialAttr('__ge__')(*args) - - def __and__(self, *args): - return self._getSpecialAttr('__and__')(*args) - - def __or__(self, *args): - return self._getSpecialAttr('__or__')(*args) - - def __xor__(self, *args): - return self._getSpecialAttr('__xor__')(*args) - - def __iand__(self, *args): - return self._getSpecialAttr('__iand__')(*args, _callSync='off') - - def __ior__(self, *args): - return self._getSpecialAttr('__ior__')(*args, _callSync='off') - - def __ixor__(self, *args): - return self._getSpecialAttr('__ixor__')(*args, _callSync='off') - - def __mod__(self, *args): - return self._getSpecialAttr('__mod__')(*args) - - def __radd__(self, *args): - return self._getSpecialAttr('__radd__')(*args) - - def __rsub__(self, *args): - return self._getSpecialAttr('__rsub__')(*args) - - def __rdiv__(self, *args): - return self._getSpecialAttr('__rdiv__')(*args) - - def __rfloordiv__(self, *args): - return self._getSpecialAttr('__rfloordiv__')(*args) - - def __rtruediv__(self, *args): - return self._getSpecialAttr('__rtruediv__')(*args) - - def __rmul__(self, *args): - return self._getSpecialAttr('__rmul__')(*args) - - def __rpow__(self, *args): - return self._getSpecialAttr('__rpow__')(*args) - - def __rrshift__(self, *args): - return self._getSpecialAttr('__rrshift__')(*args) - - def __rlshift__(self, *args): - return self._getSpecialAttr('__rlshift__')(*args) - - def __rand__(self, *args): - return self._getSpecialAttr('__rand__')(*args) - - def __ror__(self, *args): - return self._getSpecialAttr('__ror__')(*args) - - def __rxor__(self, *args): - return self._getSpecialAttr('__ror__')(*args) - - def __rmod__(self, *args): - return self._getSpecialAttr('__rmod__')(*args) - - def __hash__(self): - ## Required for python3 since __eq__ is defined. - return id(self) - -class DeferredObjectProxy(ObjectProxy): - """ - This class represents an attribute (or sub-attribute) of a proxied object. - It is used to speed up attribute requests. Take the following scenario:: - - rsys = proc._import('sys') - rsys.stdout.write('hello') - - For this simple example, a total of 4 synchronous requests are made to - the remote process: - - 1) import sys - 2) getattr(sys, 'stdout') - 3) getattr(stdout, 'write') - 4) write('hello') - - This takes a lot longer than running the equivalent code locally. To - speed things up, we can 'defer' the two attribute lookups so they are - only carried out when neccessary:: - - rsys = proc._import('sys') - rsys._setProxyOptions(deferGetattr=True) - rsys.stdout.write('hello') - - This example only makes two requests to the remote process; the two - attribute lookups immediately return DeferredObjectProxy instances - immediately without contacting the remote process. When the call - to write() is made, all attribute requests are processed at the same time. - - Note that if the attributes requested do not exist on the remote object, - making the call to write() will raise an AttributeError. - """ - def __init__(self, parentProxy, attribute): - ## can't set attributes directly because setattr is overridden. - for k in ['_processId', '_typeStr', '_proxyId', '_handler']: - self.__dict__[k] = getattr(parentProxy, k) - self.__dict__['_parent'] = parentProxy ## make sure parent stays alive - self.__dict__['_attributes'] = parentProxy._attributes + (attribute,) - self.__dict__['_proxyOptions'] = parentProxy._proxyOptions.copy() - - def __repr__(self): - return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes) - - def _undefer(self): - """ - Return a non-deferred ObjectProxy referencing the same object - """ - return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False) - diff --git a/pyqtgraph/opengl/GLGraphicsItem.py b/pyqtgraph/opengl/GLGraphicsItem.py deleted file mode 100644 index a2c2708..0000000 --- a/pyqtgraph/opengl/GLGraphicsItem.py +++ /dev/null @@ -1,300 +0,0 @@ -from OpenGL.GL import * -from OpenGL import GL -from ..Qt import QtGui, QtCore -from .. import Transform3D -from ..python2_3 import basestring - - -GLOptions = { - 'opaque': { - GL_DEPTH_TEST: True, - GL_BLEND: False, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - }, - 'translucent': { - GL_DEPTH_TEST: True, - GL_BLEND: True, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), - }, - 'additive': { - GL_DEPTH_TEST: False, - GL_BLEND: True, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE), - }, -} - - -class GLGraphicsItem(QtCore.QObject): - _nextId = 0 - - def __init__(self, parentItem=None): - QtCore.QObject.__init__(self) - self._id = GLGraphicsItem._nextId - GLGraphicsItem._nextId += 1 - - self.__parent = None - self.__view = None - self.__children = set() - self.__transform = Transform3D() - self.__visible = True - self.setParentItem(parentItem) - self.setDepthValue(0) - self.__glOpts = {} - - def setParentItem(self, item): - """Set this item's parent in the scenegraph hierarchy.""" - if self.__parent is not None: - self.__parent.__children.remove(self) - if item is not None: - item.__children.add(self) - self.__parent = item - - if self.__parent is not None and self.view() is not self.__parent.view(): - if self.view() is not None: - self.view().removeItem(self) - self.__parent.view().addItem(self) - - def setGLOptions(self, opts): - """ - Set the OpenGL state options to use immediately before drawing this item. - (Note that subclasses must call setupGLState before painting for this to work) - - The simplest way to invoke this method is to pass in the name of - a predefined set of options (see the GLOptions variable): - - ============= ====================================================== - opaque Enables depth testing and disables blending - translucent Enables depth testing and blending - Elements must be drawn sorted back-to-front for - translucency to work correctly. - additive Disables depth testing, enables blending. - Colors are added together, so sorting is not required. - ============= ====================================================== - - It is also possible to specify any arbitrary settings as a dictionary. - This may consist of {'functionName': (args...)} pairs where functionName must - be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs - which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR). - - For example:: - - { - GL_ALPHA_TEST: True, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), - } - - - """ - if isinstance(opts, basestring): - opts = GLOptions[opts] - self.__glOpts = opts.copy() - self.update() - - def updateGLOptions(self, opts): - """ - Modify the OpenGL state options to use immediately before drawing this item. - *opts* must be a dictionary as specified by setGLOptions. - Values may also be None, in which case the key will be ignored. - """ - self.__glOpts.update(opts) - - - def parentItem(self): - """Return a this item's parent in the scenegraph hierarchy.""" - return self.__parent - - def childItems(self): - """Return a list of this item's children in the scenegraph hierarchy.""" - return list(self.__children) - - def _setView(self, v): - self.__view = v - - def view(self): - return self.__view - - def setDepthValue(self, value): - """ - Sets the depth value of this item. Default is 0. - This controls the order in which items are drawn--those with a greater depth value will be drawn later. - Items with negative depth values are drawn before their parent. - (This is analogous to QGraphicsItem.zValue) - The depthValue does NOT affect the position of the item or the values it imparts to the GL depth buffer. - """ - self.__depthValue = value - - def depthValue(self): - """Return the depth value of this item. See setDepthValue for more information.""" - return self.__depthValue - - def setTransform(self, tr): - """Set the local transform for this object. - Must be a :class:`Transform3D ` instance. This transform - determines how the local coordinate system of the item is mapped to the coordinate - system of its parent.""" - self.__transform = Transform3D(tr) - self.update() - - def resetTransform(self): - """Reset this item's transform to an identity transformation.""" - self.__transform.setToIdentity() - self.update() - - def applyTransform(self, tr, local): - """ - Multiply this object's transform by *tr*. - If local is True, then *tr* is multiplied on the right of the current transform:: - - newTransform = transform * tr - - If local is False, then *tr* is instead multiplied on the left:: - - newTransform = tr * transform - """ - if local: - self.setTransform(self.transform() * tr) - else: - self.setTransform(tr * self.transform()) - - def transform(self): - """Return this item's transform object.""" - return self.__transform - - def viewTransform(self): - """Return the transform mapping this item's local coordinate system to the - view coordinate system.""" - tr = self.__transform - p = self - while True: - p = p.parentItem() - if p is None: - break - tr = p.transform() * tr - return Transform3D(tr) - - def translate(self, dx, dy, dz, local=False): - """ - Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. - If *local* is True, then translation takes place in local coordinates. - """ - tr = Transform3D() - tr.translate(dx, dy, dz) - self.applyTransform(tr, local=local) - - def rotate(self, angle, x, y, z, local=False): - """ - Rotate the object around the axis specified by (x,y,z). - *angle* is in degrees. - - """ - tr = Transform3D() - tr.rotate(angle, x, y, z) - self.applyTransform(tr, local=local) - - def scale(self, x, y, z, local=True): - """ - Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. - If *local* is False, then scale takes place in the parent's coordinates. - """ - tr = Transform3D() - tr.scale(x, y, z) - self.applyTransform(tr, local=local) - - - def hide(self): - """Hide this item. - This is equivalent to setVisible(False).""" - self.setVisible(False) - - def show(self): - """Make this item visible if it was previously hidden. - This is equivalent to setVisible(True).""" - self.setVisible(True) - - def setVisible(self, vis): - """Set the visibility of this item.""" - self.__visible = vis - self.update() - - def visible(self): - """Return True if the item is currently set to be visible. - Note that this does not guarantee that the item actually appears in the - view, as it may be obscured or outside of the current view area.""" - return self.__visible - - - def initializeGL(self): - """ - Called after an item is added to a GLViewWidget. - The widget's GL context is made current before this method is called. - (So this would be an appropriate time to generate lists, upload textures, etc.) - """ - pass - - def setupGLState(self): - """ - This method is responsible for preparing the GL state options needed to render - this item (blending, depth testing, etc). The method is called immediately before painting the item. - """ - for k,v in self.__glOpts.items(): - if v is None: - continue - if isinstance(k, basestring): - func = getattr(GL, k) - func(*v) - else: - if v is True: - glEnable(k) - else: - glDisable(k) - - def paint(self): - """ - Called by the GLViewWidget to draw this item. - It is the responsibility of the item to set up its own modelview matrix, - but the caller will take care of pushing/popping. - """ - self.setupGLState() - - def update(self): - """ - Indicates that this item needs to be redrawn, and schedules an update - with the view it is displayed in. - """ - v = self.view() - if v is None: - return - v.update() - - def mapToParent(self, point): - tr = self.transform() - if tr is None: - return point - return tr.map(point) - - def mapFromParent(self, point): - tr = self.transform() - if tr is None: - return point - return tr.inverted()[0].map(point) - - def mapToView(self, point): - tr = self.viewTransform() - if tr is None: - return point - return tr.map(point) - - def mapFromView(self, point): - tr = self.viewTransform() - if tr is None: - return point - return tr.inverted()[0].map(point) - - - \ No newline at end of file diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py deleted file mode 100644 index 6dcbf8b..0000000 --- a/pyqtgraph/opengl/GLViewWidget.py +++ /dev/null @@ -1,641 +0,0 @@ -from ..Qt import QtCore, QtGui, QtWidgets, QT_LIB -from OpenGL.GL import * -import OpenGL.GL.framebufferobjects as glfbo -import numpy as np -from .. import Vector -from .. import functions as fn -import warnings -##Vector = QtGui.QVector3D - -ShareWidget = None - -class GLViewWidget(QtWidgets.QOpenGLWidget): - - def __init__(self, parent=None, devicePixelRatio=None, rotationMethod='euler'): - """ - Basic widget for displaying 3D data - - Rotation/scale controls - - Axis/grid display - - Export options - - ================ ============================================================== - **Arguments:** - parent (QObject, optional): Parent QObject. Defaults to None. - devicePixelRatio (float, optional): High-DPI displays Qt5 should automatically - detect the correct resolution. For Qt4, specify the - ``devicePixelRatio`` argument when initializing the widget - (usually this value is 1-2). Defaults to None. - rotationMethod (str): Mechanimsm to drive the rotation method, options are - 'euler' and 'quaternion'. Defaults to 'euler'. - ================ ============================================================== - """ - - QtWidgets.QOpenGLWidget.__init__(self, parent) - - self.setFocusPolicy(QtCore.Qt.ClickFocus) - - if rotationMethod not in {"euler", "quaternion"}: - raise RuntimeError("Rotation method should be either 'euler' or 'quaternion'") - - self.opts = { - 'center': Vector(0,0,0), ## will always appear at the center of the widget - 'rotation' : QtGui.QQuaternion(1,0,0,0), ## camera rotation (quaternion:wxyz) - 'distance': 10.0, ## distance of camera from center - 'fov': 60, ## horizontal field of view in degrees - 'elevation': 30, ## camera's angle of elevation in degrees - 'azimuth': 45, ## camera's azimuthal angle in degrees - ## (rotation around z-axis 0 points along x-axis) - 'viewport': None, ## glViewport params; None == whole widget - 'devicePixelRatio': devicePixelRatio, - 'rotationMethod': rotationMethod - } - self.reset() - self.items = [] - - self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] - self.keysPressed = {} - self.keyTimer = QtCore.QTimer() - self.keyTimer.timeout.connect(self.evalKeyState) - self.makeCurrent() - - - @property - def _dpiRatio(self): - return self.devicePixelRatioF() or 1 - - def _updateScreen(self, screen): - self._updatePixelRatio() - if screen is not None: - screen.physicalDotsPerInchChanged.connect(self._updatePixelRatio) - screen.logicalDotsPerInchChanged.connect(self._updatePixelRatio) - - def _updatePixelRatio(self): - event = QtGui.QResizeEvent(self.size(), self.size()) - self.resizeEvent(event) - - def showEvent(self, event): - window = self.window().windowHandle() - window.screenChanged.connect(self._updateScreen) - self._updateScreen(window.screen()) - - def width(self): - if self._dpiRatio.is_integer(): - return super().width() - else: - return int(super().width() * self._dpiRatio) - - def height(self): - if self._dpiRatio.is_integer(): - return super().height() - else: - return int(super().height() * self._dpiRatio) - - - def reset(self): - """ - Initialize the widget state or reset the current state to the original state. - """ - self.opts['center'] = Vector(0,0,0) ## will always appear at the center of the widget - self.opts['distance'] = 10.0 ## distance of camera from center - self.opts['fov'] = 60 ## horizontal field of view in degrees - self.opts['elevation'] = 30 ## camera's angle of elevation in degrees - self.opts['azimuth'] = 45 ## camera's azimuthal angle in degrees - ## (rotation around z-axis 0 points along x-axis) - self.opts['viewport'] = None ## glViewport params; None == whole widget - self.setBackgroundColor('k') - - def addItem(self, item): - self.items.append(item) - if hasattr(item, 'initializeGL'): - self.makeCurrent() - try: - item.initializeGL() - except: - self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item)) - - item._setView(self) - #print "set view", item, self, item.view() - self.update() - - def removeItem(self, item): - """ - Remove the item from the scene. - """ - self.items.remove(item) - item._setView(None) - self.update() - - def clear(self): - """ - Remove all items from the scene. - """ - for item in self.items: - item._setView(None) - self.items = [] - self.update() - - def initializeGL(self): - self.resizeGL(self.width(), self.height()) - - def setBackgroundColor(self, *args, **kwds): - """ - Set the background color of the widget. Accepts the same arguments as - pg.mkColor() and pg.glColor(). - """ - self.opts['bgcolor'] = fn.glColor(*args, **kwds) - self.update() - - def getViewport(self): - vp = self.opts['viewport'] - dpr = self.devicePixelRatio() - if vp is None: - return (0, 0, int(self.width() * dpr), int(self.height() * dpr)) - else: - return tuple([int(x * dpr) for x in vp]) - - def devicePixelRatio(self): - dpr = self.opts['devicePixelRatio'] - if dpr is not None: - return dpr - - return QtWidgets.QOpenGLWidget.devicePixelRatio(self) - - def resizeGL(self, w, h): - pass - #glViewport(*self.getViewport()) - #self.update() - - def setProjection(self, region=None): - m = self.projectionMatrix(region) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - - def projectionMatrix(self, region=None): - if region is None: - dpr = self.devicePixelRatio() - region = (0, 0, self.width() * dpr, self.height() * dpr) - - x0, y0, w, h = self.getViewport() - dist = self.opts['distance'] - fov = self.opts['fov'] - nearClip = dist * 0.001 - farClip = dist * 1000. - - r = nearClip * np.tan(fov * 0.5 * np.pi / 180.) - t = r * h / w - - ## Note that X0 and width in these equations must be the values used in viewport - left = r * ((region[0]-x0) * (2.0/w) - 1) - right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1) - bottom = t * ((region[1]-y0) * (2.0/h) - 1) - top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1) - - tr = QtGui.QMatrix4x4() - tr.frustum(left, right, bottom, top, nearClip, farClip) - return tr - - def setModelview(self): - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - m = self.viewMatrix() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - - def viewMatrix(self): - tr = QtGui.QMatrix4x4() - tr.translate( 0.0, 0.0, -self.opts['distance']) - if self.opts['rotationMethod'] == 'quaternion': - tr.rotate(self.opts['rotation']) - else: - # default rotation method - tr.rotate(self.opts['elevation']-90, 1, 0, 0) - tr.rotate(self.opts['azimuth']+90, 0, 0, -1) - center = self.opts['center'] - tr.translate(-center.x(), -center.y(), -center.z()) - return tr - - def itemsAt(self, region=None): - """ - Return a list of the items displayed in the region (x, y, w, h) - relative to the widget. - """ - region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) - - #buf = np.zeros(100000, dtype=np.uint) - buf = glSelectBuffer(100000) - try: - glRenderMode(GL_SELECT) - glInitNames() - glPushName(0) - self._itemNames = {} - self.paintGL(region=region, useItemNames=True) - - finally: - hits = glRenderMode(GL_RENDER) - - items = [(h.near, h.names[0]) for h in hits] - items.sort(key=lambda i: i[0]) - return [self._itemNames[i[1]] for i in items] - - def paintGL(self, region=None, viewport=None, useItemNames=False): - """ - viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] - region specifies the sub-region of self.opts['viewport'] that should be rendered. - Note that we may use viewport != self.opts['viewport'] when exporting. - """ - if viewport is None: - glViewport(*self.getViewport()) - else: - glViewport(*viewport) - self.setProjection(region=region) - self.setModelview() - bgcolor = self.opts['bgcolor'] - glClearColor(*bgcolor) - glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) - self.drawItemTree(useItemNames=useItemNames) - - def drawItemTree(self, item=None, useItemNames=False): - if item is None: - items = [x for x in self.items if x.parentItem() is None] - else: - items = item.childItems() - items.append(item) - items.sort(key=lambda a: a.depthValue()) - for i in items: - if not i.visible(): - continue - if i is item: - try: - glPushAttrib(GL_ALL_ATTRIB_BITS) - if useItemNames: - glLoadName(i._id) - self._itemNames[i._id] = i - i.paint() - except: - from .. import debug - debug.printExc() - msg = "Error while drawing item %s." % str(item) - ver = glGetString(GL_VERSION) - if ver is not None: - ver = ver.split()[0] - if int(ver.split(b'.')[0]) < 2: - print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) - else: - print(msg) - - finally: - glPopAttrib() - else: - glMatrixMode(GL_MODELVIEW) - glPushMatrix() - try: - tr = i.transform() - a = np.array(tr.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - self.drawItemTree(i, useItemNames=useItemNames) - finally: - glMatrixMode(GL_MODELVIEW) - glPopMatrix() - - def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None, rotation=None): - if pos is not None: - self.opts['center'] = pos - if distance is not None: - self.opts['distance'] = distance - if rotation is not None: - # set with quaternion - self.opts['rotation'] = rotation - else: - # set with elevation-azimuth, restored for compatibility - eu = self.opts['rotation'].toEulerAngles() - if azimuth is not None: - eu.setZ(-azimuth-90) - if elevation is not None: - eu.setX(elevation-90) - self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu) - self.update() - - def cameraPosition(self): - """Return current position of camera based on center, dist, elevation, and azimuth""" - center = self.opts['center'] - dist = self.opts['distance'] - if self.opts['rotationMethod'] == "quaternion": - pos = center - self.opts['rotation'].rotatedVector( Vector(0,0,dist) ) - else: - # using 'euler' rotation method - elev = self.opts['elevation'] * np.pi / 180 - azim = self.opts['azimuth'] * np.pi / 180 - pos = Vector( - center.x() + dist * np.cos(elev) * np.cos(azim), - center.y() + dist * np.cos(elev) * np.sin(azim), - center.z() + dist * np.sin(elev) - ) - return pos - - def orbit(self, azim, elev): - """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" - if self.opts['rotationMethod'] == 'quaternion': - q = QtGui.QQuaternion.fromEulerAngles( - elev, -azim, 0 - ) # rx-ry-rz - q *= self.opts['rotation'] - self.opts['rotation'] = q - else: # default euler rotation method - self.opts['azimuth'] += azim - self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90) - self.update() - - def pan(self, dx, dy, dz, relative='global'): - """ - Moves the center (look-at) position while holding the camera in place. - - ============== ======================================================= - **Arguments:** - *dx* Distance to pan in x direction - *dy* Distance to pan in y direction - *dz* Distance to pan in z direction - *relative* String that determines the direction of dx,dy,dz. - If "global", then the global coordinate system is used. - If "view", then the z axis is aligned with the view - direction, and x and y axes are in the plane of the - view: +x points right, +y points up. - If "view-upright", then x is in the global xy plane and - points to the right side of the view, y is in the - global xy plane and orthogonal to x, and z points in - the global z direction. - ============== ======================================================= - - Distances are scaled roughly such that a value of 1.0 moves - by one pixel on screen. - - Prior to version 0.11, *relative* was expected to be either True (x-aligned) or - False (global). These values are deprecated but still recognized. - """ - # for backward compatibility: - if isinstance(relative, bool): - warnings.warn( - "'relative' as a boolean is deprecated, and will not be recognized in 0.13. " - "Acceptable values are 'global', 'view', or 'view-upright'", - DeprecationWarning, stacklevel=2 - ) - relative = {True: "view-upright", False: "global"}.get(relative, relative) - if relative == 'global': - self.opts['center'] += QtGui.QVector3D(dx, dy, dz) - elif relative == 'view-upright': - cPos = self.cameraPosition() - cVec = self.opts['center'] - cPos - dist = cVec.length() ## distance from camera to center - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point - xScale = xDist / self.width() - zVec = QtGui.QVector3D(0,0,1) - xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() - yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() - self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz - elif relative == 'view': - # pan in plane of camera - - if self.opts['rotationMethod'] == 'quaternion': - # obtain basis vectors - qc = self.opts['rotation'].conjugated() - xv = qc.rotatedVector( Vector(1,0,0) ) - yv = qc.rotatedVector( Vector(0,1,0) ) - zv = qc.rotatedVector( Vector(0,0,1) ) - - scale_factor = self.pixelSize( self.opts['center'] ) - - # apply translation - self.opts['center'] += scale_factor * (xv*-dx + yv*dy + zv*dz) - else: # use default euler rotation method - elev = np.radians(self.opts['elevation']) - azim = np.radians(self.opts['azimuth']) - fov = np.radians(self.opts['fov']) - dist = (self.opts['center'] - self.cameraPosition()).length() - fov_factor = np.tan(fov / 2) * 2 - scale_factor = dist * fov_factor / self.width() - z = scale_factor * np.cos(elev) * dy - x = scale_factor * (np.sin(azim) * dx - np.sin(elev) * np.cos(azim) * dy) - y = scale_factor * (np.cos(azim) * dx + np.sin(elev) * np.sin(azim) * dy) - self.opts['center'] += QtGui.QVector3D(x, -y, z) - else: - raise ValueError("relative argument must be global, view, or view-upright") - - self.update() - - def pixelSize(self, pos): - """ - Return the approximate size of a screen pixel at the location pos - Pos may be a Vector or an (N,3) array of locations - """ - cam = self.cameraPosition() - if isinstance(pos, np.ndarray): - cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,)) - dist = ((pos-cam)**2).sum(axis=-1)**0.5 - else: - dist = (pos-cam).length() - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) - return xDist / self.width() - - def mousePressEvent(self, ev): - self.mousePos = ev.localPos() - - def mouseMoveEvent(self, ev): - lpos = ev.localPos() - diff = lpos - self.mousePos - self.mousePos = lpos - - if ev.buttons() == QtCore.Qt.LeftButton: - if (ev.modifiers() & QtCore.Qt.ControlModifier): - self.pan(diff.x(), diff.y(), 0, relative='view') - else: - self.orbit(-diff.x(), diff.y()) - elif ev.buttons() == QtCore.Qt.MiddleButton: - if (ev.modifiers() & QtCore.Qt.ControlModifier): - self.pan(diff.x(), 0, diff.y(), relative='view-upright') - else: - self.pan(diff.x(), diff.y(), 0, relative='view-upright') - - def mouseReleaseEvent(self, ev): - pass - # Example item selection code: - #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10) - #print(self.itemsAt(region)) - - ## debugging code: draw the picking region - #glViewport(*self.getViewport()) - #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) - #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) - #self.paintGL(region=region) - #self.swapBuffers() - - def wheelEvent(self, ev): - delta = 0 - if QT_LIB in ['PyQt4', 'PySide']: - delta = ev.delta() - else: - delta = ev.angleDelta().x() - if delta == 0: - delta = ev.angleDelta().y() - if (ev.modifiers() & QtCore.Qt.ControlModifier): - self.opts['fov'] *= 0.999**delta - else: - self.opts['distance'] *= 0.999**delta - self.update() - - def keyPressEvent(self, ev): - if ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - self.keysPressed[ev.key()] = 1 - self.evalKeyState() - - def keyReleaseEvent(self, ev): - if ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - try: - del self.keysPressed[ev.key()] - except: - self.keysPressed = {} - self.evalKeyState() - - def evalKeyState(self): - speed = 2.0 - if len(self.keysPressed) > 0: - for key in self.keysPressed: - if key == QtCore.Qt.Key_Right: - self.orbit(azim=-speed, elev=0) - elif key == QtCore.Qt.Key_Left: - self.orbit(azim=speed, elev=0) - elif key == QtCore.Qt.Key_Up: - self.orbit(azim=0, elev=-speed) - elif key == QtCore.Qt.Key_Down: - self.orbit(azim=0, elev=speed) - elif key == QtCore.Qt.Key_PageUp: - pass - elif key == QtCore.Qt.Key_PageDown: - pass - self.keyTimer.start(16) - else: - self.keyTimer.stop() - - def checkOpenGLVersion(self, msg): - """ - Give exception additional context about version support. - - Only to be called from within exception handler. - As this check is only performed on error, - unsupported versions might still work! - """ - - # Check for unsupported version - verString = glGetString(GL_VERSION) - ver = verString.split()[0] - # If not OpenGL ES... - if str(ver.split(b'.')[0]).isdigit(): - verNumber = int(ver.split(b'.')[0]) - # ...and version is supported: - if verNumber >= 2: - # OpenGL version is fine, raise the original exception - raise - - # Print original exception - from .. import debug - debug.printExc() - - # Notify about unsupported version - raise Exception( - msg + "\n" + \ - "pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString - ) - - def readQImage(self): - """ - Read the current buffer pixels out as a QImage. - """ - w = self.width() - h = self.height() - self.repaint() - pixels = np.empty((h, w, 4), dtype=np.ubyte) - pixels[:] = 128 - pixels[...,0] = 50 - pixels[...,3] = 255 - - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels) - - # swap B,R channels for Qt - tmp = pixels[...,0].copy() - pixels[...,0] = pixels[...,2] - pixels[...,2] = tmp - pixels = pixels[::-1] # flip vertical - - img = fn.makeQImage(pixels, transpose=False) - return img - - def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): - w,h = map(int, size) - - self.makeCurrent() - tex = None - fb = None - depth_buf = None - try: - output = np.empty((w, h, 4), dtype=np.ubyte) - fb = glfbo.glGenFramebuffers(1) - glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) - - glEnable(GL_TEXTURE_2D) - tex = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, tex) - texwidth = textureSize - data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte) - - ## Test texture dimensions first - glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - ## create teture - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) - - # Create depth buffer - depth_buf = glGenRenderbuffers(1) - glBindRenderbuffer(GL_RENDERBUFFER, depth_buf) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, texwidth, texwidth) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf) - - self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) - # is interpreted correctly. - p2 = 2 * padding - for x in range(-padding, w-padding, texwidth-p2): - for y in range(-padding, h-padding, texwidth-p2): - x2 = min(x+texwidth, w+padding) - y2 = min(y+texwidth, h+padding) - w2 = x2-x - h2 = y2-y - - ## render to texture - glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0) - - self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region - glBindTexture(GL_TEXTURE_2D, tex) # fixes issue #366 - - ## read texture back to array - data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) - data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1] - output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] - - finally: - self.opts['viewport'] = None - glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) - glBindTexture(GL_TEXTURE_2D, 0) - if tex is not None: - glDeleteTextures([tex]) - if fb is not None: - glfbo.glDeleteFramebuffers([fb]) - if depth_buf is not None: - glDeleteRenderbuffers(1, [depth_buf]) - - return output diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py deleted file mode 100644 index 95ba88a..0000000 --- a/pyqtgraph/opengl/MeshData.py +++ /dev/null @@ -1,506 +0,0 @@ -import numpy as np -from ..Qt import QtGui -from .. import functions as fn -from ..python2_3 import xrange - - -class MeshData(object): - """ - Class for storing and operating on 3D mesh data. May contain: - - - list of vertex locations - - list of edges - - list of triangles - - colors per vertex, edge, or tri - - normals per vertex or tri - - This class handles conversion between the standard [list of vertexes, list of faces] - format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format - (suitable for use with glDrawArrays). It will automatically compute face normal - vectors as well as averaged vertex normal vectors. - - The class attempts to be as efficient as possible in caching conversion results and - avoiding unnecessary conversions. - """ - - def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None): - """ - ============== ===================================================== - **Arguments:** - vertexes (Nv, 3) array of vertex coordinates. - If faces is not specified, then this will instead be - interpreted as (Nf, 3, 3) array of coordinates. - faces (Nf, 3) array of indexes into the vertex array. - edges [not available yet] - vertexColors (Nv, 4) array of vertex colors. - If faces is not specified, then this will instead be - interpreted as (Nf, 3, 4) array of colors. - faceColors (Nf, 4) array of face colors. - ============== ===================================================== - - All arguments are optional. - """ - self._vertexes = None # (Nv,3) array of vertex coordinates - self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates - self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates - - ## mappings between vertexes, faces, and edges - self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face - self._edges = None # Nx2 array of indexes into self._vertexes specifying two vertexes per edge - self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces) - self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges) - - ## Per-vertex data - self._vertexNormals = None # (Nv, 3) array of normals, one per vertex - self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals - self._vertexColors = None # (Nv, 3) array of colors - self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors - self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors - - ## Per-face data - self._faceNormals = None # (Nf, 3) array of face normals - self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals - self._faceColors = None # (Nf, 4) array of face colors - self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors - self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors - - ## Per-edge data - self._edgeColors = None # (Ne, 4) array of edge colors - self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors - #self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given - - - - if vertexes is not None: - if faces is None: - self.setVertexes(vertexes, indexed='faces') - if vertexColors is not None: - self.setVertexColors(vertexColors, indexed='faces') - if faceColors is not None: - self.setFaceColors(faceColors, indexed='faces') - else: - self.setVertexes(vertexes) - self.setFaces(faces) - if vertexColors is not None: - self.setVertexColors(vertexColors) - if faceColors is not None: - self.setFaceColors(faceColors) - - def faces(self): - """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh. - - If faces have not been computed for this mesh, the function returns None. - """ - return self._faces - - def edges(self): - """Return an array (Nf, 3) of vertex indexes, two per edge in the mesh.""" - if self._edges is None: - self._computeEdges() - return self._edges - - def setFaces(self, faces): - """Set the (Nf, 3) array of faces. Each rown in the array contains - three indexes into the vertex array, specifying the three corners - of a triangular face.""" - self._faces = faces - self._edges = None - self._vertexFaces = None - self._vertexesIndexedByFaces = None - self.resetNormals() - self._vertexColorsIndexedByFaces = None - self._faceColorsIndexedByFaces = None - - def vertexes(self, indexed=None): - """Return an array (N,3) of the positions of vertexes in the mesh. - By default, each unique vertex appears only once in the array. - If indexed is 'faces', then the array will instead contain three vertexes - per face in the mesh (and a single vertex may appear more than once in the array).""" - if indexed is None: - if self._vertexes is None and self._vertexesIndexedByFaces is not None: - self._computeUnindexedVertexes() - return self._vertexes - elif indexed == 'faces': - if self._vertexesIndexedByFaces is None and self._vertexes is not None: - self._vertexesIndexedByFaces = self._vertexes[self.faces()] - return self._vertexesIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setVertexes(self, verts=None, indexed=None, resetNormals=True): - """ - Set the array (Nv, 3) of vertex coordinates. - If indexed=='faces', then the data must have shape (Nf, 3, 3) and is - assumed to be already indexed as a list of faces. - This will cause any pre-existing normal vectors to be cleared - unless resetNormals=False. - """ - if indexed is None: - if verts is not None: - self._vertexes = verts - self._vertexesIndexedByFaces = None - elif indexed=='faces': - self._vertexes = None - if verts is not None: - self._vertexesIndexedByFaces = verts - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - if resetNormals: - self.resetNormals() - - def resetNormals(self): - self._vertexNormals = None - self._vertexNormalsIndexedByFaces = None - self._faceNormals = None - self._faceNormalsIndexedByFaces = None - - def hasFaceIndexedData(self): - """Return True if this object already has vertex positions indexed by face""" - return self._vertexesIndexedByFaces is not None - - def hasEdgeIndexedData(self): - return self._vertexesIndexedByEdges is not None - - def hasVertexColor(self): - """Return True if this data set has vertex color information""" - for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges): - if v is not None: - return True - return False - - def hasFaceColor(self): - """Return True if this data set has face color information""" - for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges): - if v is not None: - return True - return False - - def faceNormals(self, indexed=None): - """ - Return an array (Nf, 3) of normal vectors for each face. - If indexed='faces', then instead return an indexed array - (Nf, 3, 3) (this is just the same array with each vector - copied three times). - """ - if self._faceNormals is None: - v = self.vertexes(indexed='faces') - self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0]) - - if indexed is None: - return self._faceNormals - elif indexed == 'faces': - if self._faceNormalsIndexedByFaces is None: - norms = np.empty((self._faceNormals.shape[0], 3, 3)) - norms[:] = self._faceNormals[:,np.newaxis,:] - self._faceNormalsIndexedByFaces = norms - return self._faceNormalsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def vertexNormals(self, indexed=None): - """ - Return an array of normal vectors. - By default, the array will be (N, 3) with one entry per unique vertex in the mesh. - If indexed is 'faces', then the array will contain three normal vectors per face - (and some vertexes may be repeated). - """ - if self._vertexNormals is None: - faceNorms = self.faceNormals() - vertFaces = self.vertexFaces() - self._vertexNormals = np.empty(self._vertexes.shape, dtype=float) - for vindex in xrange(self._vertexes.shape[0]): - faces = vertFaces[vindex] - if len(faces) == 0: - self._vertexNormals[vindex] = (0,0,0) - continue - norms = faceNorms[faces] ## get all face normals - norm = norms.sum(axis=0) ## sum normals - norm /= (norm**2).sum()**0.5 ## and re-normalize - self._vertexNormals[vindex] = norm - - if indexed is None: - return self._vertexNormals - elif indexed == 'faces': - return self._vertexNormals[self.faces()] - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def vertexColors(self, indexed=None): - """ - Return an array (Nv, 4) of vertex colors. - If indexed=='faces', then instead return an indexed array - (Nf, 3, 4). - """ - if indexed is None: - return self._vertexColors - elif indexed == 'faces': - if self._vertexColorsIndexedByFaces is None: - self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()] - return self._vertexColorsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setVertexColors(self, colors, indexed=None): - """ - Set the vertex color array (Nv, 4). - If indexed=='faces', then the array will be interpreted - as indexed and should have shape (Nf, 3, 4) - """ - if indexed is None: - self._vertexColors = colors - self._vertexColorsIndexedByFaces = None - elif indexed == 'faces': - self._vertexColors = None - self._vertexColorsIndexedByFaces = colors - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def faceColors(self, indexed=None): - """ - Return an array (Nf, 4) of face colors. - If indexed=='faces', then instead return an indexed array - (Nf, 3, 4) (note this is just the same array with each color - repeated three times). - """ - if indexed is None: - return self._faceColors - elif indexed == 'faces': - if self._faceColorsIndexedByFaces is None and self._faceColors is not None: - Nf = self._faceColors.shape[0] - self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype) - self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4) - return self._faceColorsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setFaceColors(self, colors, indexed=None): - """ - Set the face color array (Nf, 4). - If indexed=='faces', then the array will be interpreted - as indexed and should have shape (Nf, 3, 4) - """ - if indexed is None: - self._faceColors = colors - self._faceColorsIndexedByFaces = None - elif indexed == 'faces': - self._faceColors = None - self._faceColorsIndexedByFaces = colors - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def faceCount(self): - """ - Return the number of faces in the mesh. - """ - if self._faces is not None: - return self._faces.shape[0] - elif self._vertexesIndexedByFaces is not None: - return self._vertexesIndexedByFaces.shape[0] - - def edgeColors(self): - return self._edgeColors - - #def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None): - #self._vertexesIndexedByFaces = faces - #self._vertexColorsIndexedByFaces = vertexColors - #self._faceColorsIndexedByFaces = faceColors - - def _computeUnindexedVertexes(self): - ## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes - ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) - - ## I think generally this should be discouraged.. - faces = self._vertexesIndexedByFaces - verts = {} ## used to remember the index of each vertex position - self._faces = np.empty(faces.shape[:2], dtype=np.uint) - self._vertexes = [] - self._vertexFaces = [] - self._faceNormals = None - self._vertexNormals = None - for i in xrange(faces.shape[0]): - face = faces[i] - inds = [] - for j in range(face.shape[0]): - pt = face[j] - pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged - index = verts.get(pt2, None) - if index is None: - #self._vertexes.append(QtGui.QVector3D(*pt)) - self._vertexes.append(pt) - self._vertexFaces.append([]) - index = len(self._vertexes)-1 - verts[pt2] = index - self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces - self._faces[i,j] = index - self._vertexes = np.array(self._vertexes, dtype=float) - - #def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None): - #self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes] - #self._faces = faces.astype(np.uint) - #self._edges = None - #self._vertexFaces = None - #self._faceNormals = None - #self._vertexNormals = None - #self._vertexColors = vertexColors - #self._faceColors = faceColors - - def vertexFaces(self): - """ - Return list mapping each vertex index to a list of face indexes that use the vertex. - """ - if self._vertexFaces is None: - self._vertexFaces = [[] for i in xrange(len(self.vertexes()))] - for i in xrange(self._faces.shape[0]): - face = self._faces[i] - for ind in face: - self._vertexFaces[ind].append(i) - return self._vertexFaces - - #def reverseNormals(self): - #""" - #Reverses the direction of all normal vectors. - #""" - #pass - - #def generateEdgesFromFaces(self): - #""" - #Generate a set of edges by listing all the edges of faces and removing any duplicates. - #Useful for displaying wireframe meshes. - #""" - #pass - - def _computeEdges(self): - if not self.hasFaceIndexedData(): - ## generate self._edges from self._faces - nf = len(self._faces) - edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) - edges['i'][0:nf] = self._faces[:,:2] - edges['i'][nf:2*nf] = self._faces[:,1:3] - edges['i'][-nf:,0] = self._faces[:,2] - edges['i'][-nf:,1] = self._faces[:,0] - - # sort per-edge - mask = edges['i'][:,0] > edges['i'][:,1] - edges['i'][mask] = edges['i'][mask][:,::-1] - - # remove duplicate entries - self._edges = np.unique(edges)['i'] - #print self._edges - elif self._vertexesIndexedByFaces is not None: - verts = self._vertexesIndexedByFaces - edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint) - nf = verts.shape[0] - edges[:,0,0] = np.arange(nf) * 3 - edges[:,0,1] = edges[:,0,0] + 1 - edges[:,1,0] = edges[:,0,1] - edges[:,1,1] = edges[:,1,0] + 1 - edges[:,2,0] = edges[:,1,1] - edges[:,2,1] = edges[:,0,0] - self._edges = edges - else: - raise Exception("MeshData cannot generate edges--no faces in this data.") - - - def save(self): - """Serialize this mesh to a string appropriate for disk storage""" - import pickle - if self._faces is not None: - names = ['_vertexes', '_faces'] - else: - names = ['_vertexesIndexedByFaces'] - - if self._vertexColors is not None: - names.append('_vertexColors') - elif self._vertexColorsIndexedByFaces is not None: - names.append('_vertexColorsIndexedByFaces') - - if self._faceColors is not None: - names.append('_faceColors') - elif self._faceColorsIndexedByFaces is not None: - names.append('_faceColorsIndexedByFaces') - - state = dict([(n,getattr(self, n)) for n in names]) - return pickle.dumps(state) - - def restore(self, state): - """Restore the state of a mesh previously saved using save()""" - import pickle - state = pickle.loads(state) - for k in state: - if isinstance(state[k], list): - if isinstance(state[k][0], QtGui.QVector3D): - state[k] = [[v.x(), v.y(), v.z()] for v in state[k]] - state[k] = np.array(state[k]) - setattr(self, k, state[k]) - - - - @staticmethod - def sphere(rows, cols, radius=1.0, offset=True): - """ - Return a MeshData instance with vertexes and faces computed - for a spherical surface. - """ - verts = np.empty((rows+1, cols, 3), dtype=float) - - ## compute vertexes - phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) - s = radius * np.sin(phi) - verts[...,2] = radius * np.cos(phi) - th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) - if offset: - th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column - verts[...,0] = s * np.cos(th) - verts[...,1] = s * np.sin(th) - verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom - - ## compute faces - faces = np.empty((rows*cols*2, 3), dtype=np.uint) - rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) - rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) - for row in range(rows): - start = row * cols * 2 - faces[start:start+cols] = rowtemplate1 + row * cols - faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols - faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom - - ## adjust for redundant vertexes that were removed from top and bottom - vmin = cols-1 - faces[facesvmax] = vmax - - return MeshData(vertexes=verts, faces=faces) - - @staticmethod - def cylinder(rows, cols, radius=[1.0, 1.0], length=1.0, offset=False): - """ - Return a MeshData instance with vertexes and faces computed - for a cylindrical surface. - The cylinder may be tapered with different radii at each end (truncated cone) - """ - verts = np.empty((rows+1, cols, 3), dtype=float) - if isinstance(radius, int): - radius = [radius, radius] # convert to list - ## compute vertexes - th = np.linspace(2 * np.pi, (2 * np.pi)/cols, cols).reshape(1, cols) - r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z - verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z - if offset: - th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column - verts[...,0] = r * np.cos(th) # x = r cos(th) - verts[...,1] = r * np.sin(th) # y = r sin(th) - verts = verts.reshape((rows+1)*cols, 3) # just reshape: no redundant vertices... - ## compute faces - faces = np.empty((rows*cols*2, 3), dtype=np.uint) - rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) - rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) - for row in range(rows): - start = row * cols * 2 - faces[start:start+cols] = rowtemplate1 + row * cols - faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols - - return MeshData(vertexes=verts, faces=faces) - diff --git a/pyqtgraph/opengl/__init__.py b/pyqtgraph/opengl/__init__.py deleted file mode 100644 index 931003e..0000000 --- a/pyqtgraph/opengl/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -from .GLViewWidget import GLViewWidget - -## dynamic imports cause too many problems. -#from .. import importAll -#importAll('items', globals(), locals()) - -from .items.GLGridItem import * -from .items.GLBarGraphItem import * -from .items.GLScatterPlotItem import * -from .items.GLMeshItem import * -from .items.GLLinePlotItem import * -from .items.GLAxisItem import * -from .items.GLImageItem import * -from .items.GLSurfacePlotItem import * -from .items.GLBoxItem import * -from .items.GLVolumeItem import * - -from .MeshData import MeshData -## for backward compatibility: -#MeshData.MeshData = MeshData ## breaks autodoc. - -from . import shaders diff --git a/pyqtgraph/opengl/__pycache__/GLGraphicsItem.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/GLGraphicsItem.cpython-36.pyc deleted file mode 100644 index 36caffa..0000000 Binary files a/pyqtgraph/opengl/__pycache__/GLGraphicsItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/__pycache__/GLViewWidget.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/GLViewWidget.cpython-36.pyc deleted file mode 100644 index f61e256..0000000 Binary files a/pyqtgraph/opengl/__pycache__/GLViewWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/__pycache__/MeshData.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/MeshData.cpython-36.pyc deleted file mode 100644 index 30bbe5c..0000000 Binary files a/pyqtgraph/opengl/__pycache__/MeshData.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index d480436..0000000 Binary files a/pyqtgraph/opengl/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/__pycache__/glInfo.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/glInfo.cpython-36.pyc deleted file mode 100644 index 0e8330d..0000000 Binary files a/pyqtgraph/opengl/__pycache__/glInfo.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/__pycache__/shaders.cpython-36.pyc b/pyqtgraph/opengl/__pycache__/shaders.cpython-36.pyc deleted file mode 100644 index 7372f41..0000000 Binary files a/pyqtgraph/opengl/__pycache__/shaders.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/glInfo.py b/pyqtgraph/opengl/glInfo.py deleted file mode 100644 index 4120e93..0000000 --- a/pyqtgraph/opengl/glInfo.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..Qt import QtWidgets -from OpenGL.GL import * - -class GLTest(QtWidgets.QOpenGLWidget): - def initializeGL(self): - print("GL version:" + glGetString(GL_VERSION).decode("utf-8")) - print("MAX_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_TEXTURE_SIZE)) - print("MAX_3D_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)) - print("Extensions: " + glGetString(GL_EXTENSIONS).decode("utf-8").replace(" ", "\n")) - -app = QtWidgets.QApplication([]) -GLTest().show() diff --git a/pyqtgraph/opengl/items/GLAxisItem.py b/pyqtgraph/opengl/items/GLAxisItem.py deleted file mode 100644 index 989a44c..0000000 --- a/pyqtgraph/opengl/items/GLAxisItem.py +++ /dev/null @@ -1,64 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from ... import QtGui - -__all__ = ['GLAxisItem'] - -class GLAxisItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays three lines indicating origin and orientation of local coordinate system. - - """ - - def __init__(self, size=None, antialias=True, glOptions='translucent'): - GLGraphicsItem.__init__(self) - if size is None: - size = QtGui.QVector3D(1,1,1) - self.antialias = antialias - self.setSize(size=size) - self.setGLOptions(glOptions) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the axes (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - - def paint(self): - - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - self.setupGLState() - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) - - glBegin( GL_LINES ) - - x,y,z = self.size() - glColor4f(0, 1, 0, .6) # z is green - glVertex3f(0, 0, 0) - glVertex3f(0, 0, z) - - glColor4f(1, 1, 0, .6) # y is yellow - glVertex3f(0, 0, 0) - glVertex3f(0, y, 0) - - glColor4f(0, 0, 1, .6) # x is blue - glVertex3f(0, 0, 0) - glVertex3f(x, 0, 0) - glEnd() diff --git a/pyqtgraph/opengl/items/GLBarGraphItem.py b/pyqtgraph/opengl/items/GLBarGraphItem.py deleted file mode 100644 index 42d05fb..0000000 --- a/pyqtgraph/opengl/items/GLBarGraphItem.py +++ /dev/null @@ -1,26 +0,0 @@ -from .GLMeshItem import GLMeshItem -from ..MeshData import MeshData -import numpy as np - -class GLBarGraphItem(GLMeshItem): - def __init__(self, pos, size): - """ - pos is (...,3) array of the bar positions (the corner of each bar) - size is (...,3) array of the sizes of each bar - """ - nCubes = np.prod(pos.shape[:-1]) - cubeVerts = np.mgrid[0:2,0:2,0:2].reshape(3,8).transpose().reshape(1,8,3) - cubeFaces = np.array([ - [0,1,2], [3,2,1], - [4,5,6], [7,6,5], - [0,1,4], [5,4,1], - [2,3,6], [7,6,3], - [0,2,4], [6,4,2], - [1,3,5], [7,5,3]]).reshape(1,12,3) - size = size.reshape((nCubes, 1, 3)) - pos = pos.reshape((nCubes, 1, 3)) - verts = cubeVerts * size + pos - faces = cubeFaces + (np.arange(nCubes) * 8).reshape(nCubes,1,1) - md = MeshData(verts.reshape(nCubes*8,3), faces.reshape(nCubes*12,3)) - - GLMeshItem.__init__(self, meshdata=md, shader='shaded', smooth=False) diff --git a/pyqtgraph/opengl/items/GLBoxItem.py b/pyqtgraph/opengl/items/GLBoxItem.py deleted file mode 100644 index f0a6ae6..0000000 --- a/pyqtgraph/opengl/items/GLBoxItem.py +++ /dev/null @@ -1,88 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from ...Qt import QtGui -from ... import functions as fn - -__all__ = ['GLBoxItem'] - -class GLBoxItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a wire-frame box. - """ - def __init__(self, size=None, color=None, glOptions='translucent'): - GLGraphicsItem.__init__(self) - if size is None: - size = QtGui.QVector3D(1,1,1) - self.setSize(size=size) - if color is None: - color = (255,255,255,80) - self.setColor(color) - self.setGLOptions(glOptions) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the box (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - def setColor(self, *args): - """Set the color of the box. Arguments are the same as those accepted by functions.mkColor()""" - self.__color = fn.Color(*args) - - def color(self): - return self.__color - - def paint(self): - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - ##glAlphaFunc( GL_ALWAYS,0.5 ) - #glEnable( GL_POINT_SMOOTH ) - #glDisable( GL_DEPTH_TEST ) - self.setupGLState() - - glBegin( GL_LINES ) - - glColor4f(*self.color().glColor()) - x,y,z = self.size() - glVertex3f(0, 0, 0) - glVertex3f(0, 0, z) - glVertex3f(x, 0, 0) - glVertex3f(x, 0, z) - glVertex3f(0, y, 0) - glVertex3f(0, y, z) - glVertex3f(x, y, 0) - glVertex3f(x, y, z) - - glVertex3f(0, 0, 0) - glVertex3f(0, y, 0) - glVertex3f(x, 0, 0) - glVertex3f(x, y, 0) - glVertex3f(0, 0, z) - glVertex3f(0, y, z) - glVertex3f(x, 0, z) - glVertex3f(x, y, z) - - glVertex3f(0, 0, 0) - glVertex3f(x, 0, 0) - glVertex3f(0, y, 0) - glVertex3f(x, y, 0) - glVertex3f(0, 0, z) - glVertex3f(x, 0, z) - glVertex3f(0, y, z) - glVertex3f(x, y, z) - - glEnd() - - \ No newline at end of file diff --git a/pyqtgraph/opengl/items/GLGridItem.py b/pyqtgraph/opengl/items/GLGridItem.py deleted file mode 100644 index 9dcff07..0000000 --- a/pyqtgraph/opengl/items/GLGridItem.py +++ /dev/null @@ -1,88 +0,0 @@ -import numpy as np - -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from ... import QtGui -from ... import functions as fn - -__all__ = ['GLGridItem'] - -class GLGridItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a wire-frame grid. - """ - - def __init__(self, size=None, color=(255, 255, 255, 76.5), antialias=True, glOptions='translucent'): - GLGraphicsItem.__init__(self) - self.setGLOptions(glOptions) - self.antialias = antialias - if size is None: - size = QtGui.QVector3D(20,20,1) - self.setSize(size=size) - self.setSpacing(1, 1, 1) - self.setColor(color) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the axes (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - def setSpacing(self, x=None, y=None, z=None, spacing=None): - """ - Set the spacing between grid lines. - Arguments can be x,y,z or spacing=QVector3D(). - """ - if spacing is not None: - x = spacing.x() - y = spacing.y() - z = spacing.z() - self.__spacing = [x,y,z] - self.update() - - def spacing(self): - return self.__spacing[:] - - def setColor(self, color): - """Set the color of the grid. Arguments are the same as those accepted by functions.mkColor()""" - self.__color = fn.Color(color) - self.update() - - def color(self): - return self.__color - - def paint(self): - self.setupGLState() - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) - - glBegin( GL_LINES ) - - x,y,z = self.size() - xs,ys,zs = self.spacing() - xvals = np.arange(-x/2., x/2. + xs*0.001, xs) - yvals = np.arange(-y/2., y/2. + ys*0.001, ys) - glColor4f(*self.color().glColor()) - for x in xvals: - glVertex3f(x, yvals[0], 0) - glVertex3f(x, yvals[-1], 0) - for y in yvals: - glVertex3f(xvals[0], y, 0) - glVertex3f(xvals[-1], y, 0) - - glEnd() diff --git a/pyqtgraph/opengl/items/GLImageItem.py b/pyqtgraph/opengl/items/GLImageItem.py deleted file mode 100644 index 7bd0ec0..0000000 --- a/pyqtgraph/opengl/items/GLImageItem.py +++ /dev/null @@ -1,103 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from ...Qt import QtGui -import numpy as np - -__all__ = ['GLImageItem'] - -class GLImageItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays image data as a textured quad. - """ - - - def __init__(self, data, smooth=False, glOptions='translucent'): - """ - - ============== ======================================================================================= - **Arguments:** - data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte. - (See functions.makeRGBA) - smooth (bool) If True, the volume slices are rendered with linear interpolation - ============== ======================================================================================= - """ - - self.smooth = smooth - self._needUpdate = False - GLGraphicsItem.__init__(self) - self.setData(data) - self.setGLOptions(glOptions) - self.texture = None - - def initializeGL(self): - if self.texture is not None: - return - glEnable(GL_TEXTURE_2D) - self.texture = glGenTextures(1) - - def setData(self, data): - self.data = data - self._needUpdate = True - self.update() - - def _updateTexture(self): - glBindTexture(GL_TEXTURE_2D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - shape = self.data.shape - - ## Test texture dimensions first - glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((1,0,2))) - glDisable(GL_TEXTURE_2D) - - #self.lists = {} - #for ax in [0,1,2]: - #for d in [-1, 1]: - #l = glGenLists(1) - #self.lists[(ax,d)] = l - #glNewList(l, GL_COMPILE) - #self.drawVolume(ax, d) - #glEndList() - - - def paint(self): - if self._needUpdate: - self._updateTexture() - self._needUpdate = False - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - - self.setupGLState() - - #glEnable(GL_DEPTH_TEST) - ##glDisable(GL_CULL_FACE) - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - glColor4f(1,1,1,1) - - glBegin(GL_QUADS) - glTexCoord2f(0,0) - glVertex3f(0,0,0) - glTexCoord2f(1,0) - glVertex3f(self.data.shape[0], 0, 0) - glTexCoord2f(1,1) - glVertex3f(self.data.shape[0], self.data.shape[1], 0) - glTexCoord2f(0,1) - glVertex3f(0, self.data.shape[1], 0) - glEnd() - glDisable(GL_TEXTURE_3D) - diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py deleted file mode 100644 index 2daf78b..0000000 --- a/pyqtgraph/opengl/items/GLLinePlotItem.py +++ /dev/null @@ -1,97 +0,0 @@ -from OpenGL.GL import * -from OpenGL.arrays import vbo -from .. GLGraphicsItem import GLGraphicsItem -from .. import shaders -from ... import QtGui -from ... import functions as fn -import numpy as np - -__all__ = ['GLLinePlotItem'] - -class GLLinePlotItem(GLGraphicsItem): - """Draws line plots in 3D.""" - - def __init__(self, **kwds): - """All keyword arguments are passed to setData()""" - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'additive') - self.setGLOptions(glopts) - self.pos = None - self.mode = 'line_strip' - self.width = 1. - self.color = (1.0,1.0,1.0,1.0) - self.setData(**kwds) - - def setData(self, **kwds): - """ - Update the data displayed by this item. All arguments are optional; - for example it is allowed to update vertex positions while leaving - colors unchanged, etc. - - ==================== ================================================== - **Arguments:** - ------------------------------------------------------------------------ - pos (N,3) array of floats specifying point locations. - color (N,4) array of floats (0.0-1.0) or - tuple of floats specifying - a single color for the entire item. - width float specifying line width - antialias enables smooth line drawing - mode 'lines': Each pair of vertexes draws a single line - segment. - 'line_strip': All vertexes are drawn as a - continuous set of line segments. - ==================== ================================================== - """ - args = ['pos', 'color', 'width', 'mode', 'antialias'] - for k in kwds.keys(): - if k not in args: - raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) - self.antialias = False - for arg in args: - if arg in kwds: - setattr(self, arg, kwds[arg]) - #self.vbo.pop(arg, None) - self.update() - - def initializeGL(self): - pass - - def paint(self): - if self.pos is None: - return - self.setupGLState() - - glEnableClientState(GL_VERTEX_ARRAY) - - try: - glVertexPointerf(self.pos) - - if isinstance(self.color, np.ndarray): - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(self.color) - else: - if isinstance(self.color, (str, QtGui.QColor)): - glColor4f(*fn.glColor(self.color)) - else: - glColor4f(*self.color) - glLineWidth(self.width) - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) - - if self.mode == 'line_strip': - glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) - elif self.mode == 'lines': - glDrawArrays(GL_LINES, 0, int(self.pos.size / self.pos.shape[-1])) - else: - raise Exception("Unknown line mode '%s'. (must be 'lines' or 'line_strip')" % self.mode) - - finally: - glDisableClientState(GL_COLOR_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - - diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/pyqtgraph/opengl/items/GLMeshItem.py deleted file mode 100644 index 55e7594..0000000 --- a/pyqtgraph/opengl/items/GLMeshItem.py +++ /dev/null @@ -1,227 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from .. MeshData import MeshData -from ...Qt import QtGui -from .. import shaders -from ... import functions as fn -import numpy as np - - - -__all__ = ['GLMeshItem'] - -class GLMeshItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a 3D triangle mesh. - """ - def __init__(self, **kwds): - """ - ============== ===================================================== - **Arguments:** - meshdata MeshData object from which to determine geometry for - this item. - color Default face color used if no vertex or face colors - are specified. - edgeColor Default edge color to use if no edge colors are - specified in the mesh data. - drawEdges If True, a wireframe mesh will be drawn. - (default=False) - drawFaces If True, mesh faces are drawn. (default=True) - shader Name of shader program to use when drawing faces. - (None for no shader) - smooth If True, normal vectors are computed for each vertex - and interpolated within each face. - computeNormals If False, then computation of normal vectors is - disabled. This can provide a performance boost for - meshes that do not make use of normals. - ============== ===================================================== - """ - self.opts = { - 'meshdata': None, - 'color': (1., 1., 1., 1.), - 'drawEdges': False, - 'drawFaces': True, - 'edgeColor': (0.5, 0.5, 0.5, 1.0), - 'shader': None, - 'smooth': True, - 'computeNormals': True, - } - - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'opaque') - self.setGLOptions(glopts) - shader = kwds.pop('shader', None) - self.setShader(shader) - - self.setMeshData(**kwds) - - ## storage for data compiled from MeshData object - self.vertexes = None - self.normals = None - self.colors = None - self.faces = None - - def setShader(self, shader): - """Set the shader used when rendering faces in the mesh. (see the GL shaders example)""" - self.opts['shader'] = shader - self.update() - - def shader(self): - shader = self.opts['shader'] - if isinstance(shader, shaders.ShaderProgram): - return shader - else: - return shaders.getShaderProgram(shader) - - def setColor(self, c): - """Set the default color to use when no vertex or face colors are specified.""" - self.opts['color'] = c - self.update() - - def setMeshData(self, **kwds): - """ - Set mesh data for this item. This can be invoked two ways: - - 1. Specify *meshdata* argument with a new MeshData object - 2. Specify keyword arguments to be passed to MeshData(..) to create a new instance. - """ - md = kwds.get('meshdata', None) - if md is None: - opts = {} - for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']: - try: - opts[k] = kwds.pop(k) - except KeyError: - pass - md = MeshData(**opts) - - self.opts['meshdata'] = md - self.opts.update(kwds) - self.meshDataChanged() - self.update() - - - def meshDataChanged(self): - """ - This method must be called to inform the item that the MeshData object - has been altered. - """ - - self.vertexes = None - self.faces = None - self.normals = None - self.colors = None - self.edges = None - self.edgeColors = None - self.update() - - def parseMeshData(self): - ## interpret vertex / normal data before drawing - ## This can: - ## - automatically generate normals if they were not specified - ## - pull vertexes/noormals/faces from MeshData if that was specified - - if self.vertexes is not None and self.normals is not None: - return - #if self.opts['normals'] is None: - #if self.opts['meshdata'] is None: - #self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces']) - if self.opts['meshdata'] is not None: - md = self.opts['meshdata'] - if self.opts['smooth'] and not md.hasFaceIndexedData(): - self.vertexes = md.vertexes() - if self.opts['computeNormals']: - self.normals = md.vertexNormals() - self.faces = md.faces() - if md.hasVertexColor(): - self.colors = md.vertexColors() - if md.hasFaceColor(): - self.colors = md.faceColors() - else: - self.vertexes = md.vertexes(indexed='faces') - if self.opts['computeNormals']: - if self.opts['smooth']: - self.normals = md.vertexNormals(indexed='faces') - else: - self.normals = md.faceNormals(indexed='faces') - self.faces = None - if md.hasVertexColor(): - self.colors = md.vertexColors(indexed='faces') - elif md.hasFaceColor(): - self.colors = md.faceColors(indexed='faces') - - if self.opts['drawEdges']: - if not md.hasFaceIndexedData(): - self.edges = md.edges() - self.edgeVerts = md.vertexes() - else: - self.edges = md.edges() - self.edgeVerts = md.vertexes(indexed='faces') - return - - def paint(self): - self.setupGLState() - - self.parseMeshData() - - if self.opts['drawFaces']: - with self.shader(): - verts = self.vertexes - norms = self.normals - color = self.colors - faces = self.faces - if verts is None: - return - glEnableClientState(GL_VERTEX_ARRAY) - try: - glVertexPointerf(verts) - - if self.colors is None: - color = self.opts['color'] - if isinstance(color, QtGui.QColor): - glColor4f(*fn.glColor(color)) - else: - glColor4f(*color) - else: - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(color) - - - if norms is not None: - glEnableClientState(GL_NORMAL_ARRAY) - glNormalPointerf(norms) - - if faces is None: - glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1])) - else: - faces = faces.astype(np.uint).flatten() - glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces) - finally: - glDisableClientState(GL_NORMAL_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - - if self.opts['drawEdges']: - verts = self.edgeVerts - edges = self.edges - glEnableClientState(GL_VERTEX_ARRAY) - try: - glVertexPointerf(verts) - - if self.edgeColors is None: - color = self.opts['edgeColor'] - if isinstance(color, QtGui.QColor): - glColor4f(*fn.glColor(color)) - else: - glColor4f(*color) - else: - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(color) - edges = edges.flatten() - glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) - finally: - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/pyqtgraph/opengl/items/GLScatterPlotItem.py deleted file mode 100644 index 5d81515..0000000 --- a/pyqtgraph/opengl/items/GLScatterPlotItem.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -from OpenGL.GL import * -from OpenGL.arrays import vbo -from .. GLGraphicsItem import GLGraphicsItem -from .. import shaders -from ... import QtGui -import numpy as np - -__all__ = ['GLScatterPlotItem'] - -class GLScatterPlotItem(GLGraphicsItem): - """Draws points at a list of 3D positions.""" - - def __init__(self, **kwds): - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'additive') - self.setGLOptions(glopts) - self.pos = None - self.size = 10 - self.color = [1.0,1.0,1.0,0.5] - self.pxMode = True - #self.vbo = {} ## VBO does not appear to improve performance very much. - self.setData(**kwds) - self.shader = None - - def setData(self, **kwds): - """ - Update the data displayed by this item. All arguments are optional; - for example it is allowed to update spot positions while leaving - colors unchanged, etc. - - ==================== ================================================== - **Arguments:** - pos (N,3) array of floats specifying point locations. - color (N,4) array of floats (0.0-1.0) specifying - spot colors OR a tuple of floats specifying - a single color for all spots. - size (N,) array of floats specifying spot sizes or - a single value to apply to all spots. - pxMode If True, spot sizes are expressed in pixels. - Otherwise, they are expressed in item coordinates. - ==================== ================================================== - """ - args = ['pos', 'color', 'size', 'pxMode'] - for k in kwds.keys(): - if k not in args: - raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) - - args.remove('pxMode') - for arg in args: - if arg in kwds: - setattr(self, arg, kwds[arg]) - #self.vbo.pop(arg, None) - - self.pxMode = kwds.get('pxMode', self.pxMode) - self.update() - - def initializeGL(self): - if self.shader is not None: - return - - ## Generate texture for rendering points - w = 64 - def fn(x,y): - r = ((x-(w-1)/2.)**2 + (y-(w-1)/2.)**2) ** 0.5 - return 255 * (w/2. - np.clip(r, w/2.-1.0, w/2.)) - pData = np.empty((w, w, 4)) - pData[:] = 255 - pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) - #print pData.shape, pData.min(), pData.max() - pData = pData.astype(np.ubyte) - - if getattr(self, "pointTexture", None) is None: - self.pointTexture = glGenTextures(1) - glActiveTexture(GL_TEXTURE0) - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.pointTexture) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) - - self.shader = shaders.getShaderProgram('pointSprite') - - #def getVBO(self, name): - #if name not in self.vbo: - #self.vbo[name] = vbo.VBO(getattr(self, name).astype('f')) - #return self.vbo[name] - - #def setupGLState(self): - #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" - ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. - #glBlendFunc(GL_SRC_ALPHA, GL_ONE) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - #glDisable( GL_DEPTH_TEST ) - - ##glEnable( GL_POINT_SMOOTH ) - - ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) - ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) - ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) - ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) - - def paint(self): - if self.pos is None: - return - - self.setupGLState() - - glEnable(GL_POINT_SPRITE) - - glActiveTexture(GL_TEXTURE0) - glEnable( GL_TEXTURE_2D ) - glBindTexture(GL_TEXTURE_2D, self.pointTexture) - - glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) - #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly - #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - glEnable(GL_PROGRAM_POINT_SIZE) - - - with self.shader: - #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use - glEnableClientState(GL_VERTEX_ARRAY) - try: - pos = self.pos - #if pos.ndim > 2: - #pos = pos.reshape((-1, pos.shape[-1])) - glVertexPointerf(pos) - - if isinstance(self.color, np.ndarray): - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(self.color) - else: - if isinstance(self.color, QtGui.QColor): - glColor4f(*fn.glColor(self.color)) - else: - glColor4f(*self.color) - - if not self.pxMode or isinstance(self.size, np.ndarray): - glEnableClientState(GL_NORMAL_ARRAY) - norm = np.empty(pos.shape) - if self.pxMode: - norm[...,0] = self.size - else: - gpos = self.mapToView(pos.transpose()).transpose() - pxSize = self.view().pixelSize(gpos) - norm[...,0] = self.size / pxSize - - glNormalPointerf(norm) - else: - glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size - #glPointSize(self.size) - glDrawArrays(GL_POINTS, 0, int(pos.size / pos.shape[-1])) - finally: - glDisableClientState(GL_NORMAL_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - #posVBO.unbind() - ##fixes #145 - glDisable( GL_TEXTURE_2D ) - - #for i in range(len(self.pos)): - #pos = self.pos[i] - - #if isinstance(self.color, np.ndarray): - #color = self.color[i] - #else: - #color = self.color - #if isinstance(self.color, QtGui.QColor): - #color = fn.glColor(self.color) - - #if isinstance(self.size, np.ndarray): - #size = self.size[i] - #else: - #size = self.size - - #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) - - #glPointSize(size / pxSize) - #glBegin( GL_POINTS ) - #glColor4f(*color) # x is blue - ##glNormal3f(size, 0, 0) - #glVertex3f(*pos) - #glEnd() - - - - - diff --git a/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/pyqtgraph/opengl/items/GLSurfacePlotItem.py deleted file mode 100644 index e39ef3b..0000000 --- a/pyqtgraph/opengl/items/GLSurfacePlotItem.py +++ /dev/null @@ -1,138 +0,0 @@ -from OpenGL.GL import * -from .GLMeshItem import GLMeshItem -from .. MeshData import MeshData -from ...Qt import QtGui -import numpy as np - - - -__all__ = ['GLSurfacePlotItem'] - -class GLSurfacePlotItem(GLMeshItem): - """ - **Bases:** :class:`GLMeshItem ` - - Displays a surface plot on a regular x,y grid - """ - def __init__(self, x=None, y=None, z=None, colors=None, **kwds): - """ - The x, y, z, and colors arguments are passed to setData(). - All other keyword arguments are passed to GLMeshItem.__init__(). - """ - - self._x = None - self._y = None - self._z = None - self._color = None - self._vertexes = None - self._meshdata = MeshData() - GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds) - - self.setData(x, y, z, colors) - - - - def setData(self, x=None, y=None, z=None, colors=None): - """ - Update the data in this surface plot. - - ============== ===================================================================== - **Arguments:** - x,y 1D arrays of values specifying the x,y positions of vertexes in the - grid. If these are omitted, then the values will be assumed to be - integers. - z 2D array of height values for each grid vertex. - colors (width, height, 4) array of vertex colors. - ============== ===================================================================== - - All arguments are optional. - - Note that if vertex positions are updated, the normal vectors for each triangle must - be recomputed. This is somewhat expensive if the surface was initialized with smooth=False - and very expensive if smooth=True. For faster performance, initialize with - computeNormals=False and use per-vertex colors or a normal-independent shader program. - """ - if x is not None: - if self._x is None or len(x) != len(self._x): - self._vertexes = None - self._x = x - - if y is not None: - if self._y is None or len(y) != len(self._y): - self._vertexes = None - self._y = y - - if z is not None: - #if self._x is None: - #self._x = np.arange(z.shape[0]) - #self._vertexes = None - #if self._y is None: - #self._y = np.arange(z.shape[1]) - #self._vertexes = None - - if self._x is not None and z.shape[0] != len(self._x): - raise Exception('Z values must have shape (len(x), len(y))') - if self._y is not None and z.shape[1] != len(self._y): - raise Exception('Z values must have shape (len(x), len(y))') - self._z = z - if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]: - self._vertexes = None - - if colors is not None: - self._colors = colors - self._meshdata.setVertexColors(colors) - - if self._z is None: - return - - updateMesh = False - newVertexes = False - - ## Generate vertex and face array - if self._vertexes is None: - newVertexes = True - self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float) - self.generateFaces() - self._meshdata.setFaces(self._faces) - updateMesh = True - - ## Copy x, y, z data into vertex array - if newVertexes or x is not None: - if x is None: - if self._x is None: - x = np.arange(self._z.shape[0]) - else: - x = self._x - self._vertexes[:, :, 0] = x.reshape(len(x), 1) - updateMesh = True - - if newVertexes or y is not None: - if y is None: - if self._y is None: - y = np.arange(self._z.shape[1]) - else: - y = self._y - self._vertexes[:, :, 1] = y.reshape(1, len(y)) - updateMesh = True - - if newVertexes or z is not None: - self._vertexes[...,2] = self._z - updateMesh = True - - ## Update MeshData - if updateMesh: - self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3)) - self.meshDataChanged() - - - def generateFaces(self): - cols = self._z.shape[1]-1 - rows = self._z.shape[0]-1 - faces = np.empty((cols*rows*2, 3), dtype=np.uint) - rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) - rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) - for row in range(rows): - start = row * cols * 2 - faces[start:start+cols] = rowtemplate1 + row * (cols+1) - faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1) - self._faces = faces diff --git a/pyqtgraph/opengl/items/GLVolumeItem.py b/pyqtgraph/opengl/items/GLVolumeItem.py deleted file mode 100644 index cbe22db..0000000 --- a/pyqtgraph/opengl/items/GLVolumeItem.py +++ /dev/null @@ -1,230 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from ...Qt import QtGui -import numpy as np -from ... import debug - -__all__ = ['GLVolumeItem'] - -class GLVolumeItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays volumetric data. - """ - - - def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'): - """ - ============== ======================================================================================= - **Arguments:** - data Volume data to be rendered. *Must* be 4D numpy array (x, y, z, RGBA) with dtype=ubyte. - sliceDensity Density of slices to render through the volume. A value of 1 means one slice per voxel. - smooth (bool) If True, the volume slices are rendered with linear interpolation - ============== ======================================================================================= - """ - - self.sliceDensity = sliceDensity - self.smooth = smooth - self.data = None - self._needUpload = False - self.texture = None - GLGraphicsItem.__init__(self) - self.setGLOptions(glOptions) - self.setData(data) - - def setData(self, data): - self.data = data - self._needUpload = True - self.update() - - def _uploadData(self): - glEnable(GL_TEXTURE_3D) - if self.texture is None: - self.texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_3D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - shape = self.data.shape - - ## Test texture dimensions first - glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3]) - - glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3))) - glDisable(GL_TEXTURE_3D) - - self.lists = {} - for ax in [0,1,2]: - for d in [-1, 1]: - l = glGenLists(1) - self.lists[(ax,d)] = l - glNewList(l, GL_COMPILE) - self.drawVolume(ax, d) - glEndList() - - self._needUpload = False - - def paint(self): - if self.data is None: - return - - if self._needUpload: - self._uploadData() - - self.setupGLState() - - glEnable(GL_TEXTURE_3D) - glBindTexture(GL_TEXTURE_3D, self.texture) - - #glEnable(GL_DEPTH_TEST) - #glDisable(GL_CULL_FACE) - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - glColor4f(1,1,1,1) - - view = self.view() - center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]]) - cam = self.mapFromParent(view.cameraPosition()) - center - #print "center", center, "cam", view.cameraPosition(), self.mapFromParent(view.cameraPosition()), "diff", cam - cam = np.array([cam.x(), cam.y(), cam.z()]) - ax = np.argmax(abs(cam)) - d = 1 if cam[ax] > 0 else -1 - glCallList(self.lists[(ax,d)]) ## draw axes - glDisable(GL_TEXTURE_3D) - - def drawVolume(self, ax, d): - N = 5 - - imax = [0,1,2] - imax.remove(ax) - - tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] - vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] - nudge = [0.5/x for x in self.data.shape] - tp[0][imax[0]] = 0+nudge[imax[0]] - tp[0][imax[1]] = 0+nudge[imax[1]] - tp[1][imax[0]] = 1-nudge[imax[0]] - tp[1][imax[1]] = 0+nudge[imax[1]] - tp[2][imax[0]] = 1-nudge[imax[0]] - tp[2][imax[1]] = 1-nudge[imax[1]] - tp[3][imax[0]] = 0+nudge[imax[0]] - tp[3][imax[1]] = 1-nudge[imax[1]] - - vp[0][imax[0]] = 0 - vp[0][imax[1]] = 0 - vp[1][imax[0]] = self.data.shape[imax[0]] - vp[1][imax[1]] = 0 - vp[2][imax[0]] = self.data.shape[imax[0]] - vp[2][imax[1]] = self.data.shape[imax[1]] - vp[3][imax[0]] = 0 - vp[3][imax[1]] = self.data.shape[imax[1]] - slices = self.data.shape[ax] * self.sliceDensity - r = list(range(slices)) - if d == -1: - r = r[::-1] - - glBegin(GL_QUADS) - tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices) - vzVals = np.linspace(0, self.data.shape[ax], slices) - for i in r: - z = tzVals[i] - w = vzVals[i] - - tp[0][ax] = z - tp[1][ax] = z - tp[2][ax] = z - tp[3][ax] = z - - vp[0][ax] = w - vp[1][ax] = w - vp[2][ax] = w - vp[3][ax] = w - - - glTexCoord3f(*tp[0]) - glVertex3f(*vp[0]) - glTexCoord3f(*tp[1]) - glVertex3f(*vp[1]) - glTexCoord3f(*tp[2]) - glVertex3f(*vp[2]) - glTexCoord3f(*tp[3]) - glVertex3f(*vp[3]) - glEnd() - - - - - - - - - - ## Interesting idea: - ## remove projection/modelview matrixes, recreate in texture coords. - ## it _sorta_ works, but needs tweaking. - #mvm = glGetDoublev(GL_MODELVIEW_MATRIX) - #pm = glGetDoublev(GL_PROJECTION_MATRIX) - #m = QtGui.QMatrix4x4(mvm.flatten()).inverted()[0] - #p = QtGui.QMatrix4x4(pm.flatten()).inverted()[0] - - #glMatrixMode(GL_PROJECTION) - #glPushMatrix() - #glLoadIdentity() - #N=1 - #glOrtho(-N,N,-N,N,-100,100) - - #glMatrixMode(GL_MODELVIEW) - #glLoadIdentity() - - - #glMatrixMode(GL_TEXTURE) - #glLoadIdentity() - #glMultMatrixf(m.copyDataTo()) - - #view = self.view() - #w = view.width() - #h = view.height() - #dist = view.opts['distance'] - #fov = view.opts['fov'] - #nearClip = dist * .1 - #farClip = dist * 5. - #r = nearClip * np.tan(fov) - #t = r * h / w - - #p = QtGui.QMatrix4x4() - #p.frustum( -r, r, -t, t, nearClip, farClip) - #glMultMatrixf(p.inverted()[0].copyDataTo()) - - - #glBegin(GL_QUADS) - - #M=1 - #for i in range(500): - #z = i/500. - #w = -i/500. - #glTexCoord3f(-M, -M, z) - #glVertex3f(-N, -N, w) - #glTexCoord3f(M, -M, z) - #glVertex3f(N, -N, w) - #glTexCoord3f(M, M, z) - #glVertex3f(N, N, w) - #glTexCoord3f(-M, M, z) - #glVertex3f(-N, N, w) - #glEnd() - #glDisable(GL_TEXTURE_3D) - - #glMatrixMode(GL_PROJECTION) - #glPopMatrix() - - - diff --git a/pyqtgraph/opengl/items/__init__.py b/pyqtgraph/opengl/items/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/opengl/items/__pycache__/GLAxisItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLAxisItem.cpython-36.pyc deleted file mode 100644 index d856065..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLAxisItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLBarGraphItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLBarGraphItem.cpython-36.pyc deleted file mode 100644 index 9b84822..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLBarGraphItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLBoxItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLBoxItem.cpython-36.pyc deleted file mode 100644 index bde07c6..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLBoxItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLGridItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLGridItem.cpython-36.pyc deleted file mode 100644 index 804af84..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLGridItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLImageItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLImageItem.cpython-36.pyc deleted file mode 100644 index 4c31b61..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLImageItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLLinePlotItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLLinePlotItem.cpython-36.pyc deleted file mode 100644 index a67dc57..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLLinePlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLMeshItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLMeshItem.cpython-36.pyc deleted file mode 100644 index 2027eda..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLMeshItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLScatterPlotItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLScatterPlotItem.cpython-36.pyc deleted file mode 100644 index 55ec2a3..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLScatterPlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLSurfacePlotItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLSurfacePlotItem.cpython-36.pyc deleted file mode 100644 index 092912f..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLSurfacePlotItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/GLVolumeItem.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/GLVolumeItem.cpython-36.pyc deleted file mode 100644 index 6a71cab..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/GLVolumeItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/items/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/opengl/items/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 5d94e66..0000000 Binary files a/pyqtgraph/opengl/items/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py deleted file mode 100644 index 7ada939..0000000 --- a/pyqtgraph/opengl/shaders.py +++ /dev/null @@ -1,402 +0,0 @@ -try: - from OpenGL import NullFunctionError -except ImportError: - from OpenGL.error import NullFunctionError -from OpenGL.GL import * -from OpenGL.GL import shaders -import re - -## For centralizing and managing vertex/fragment shader programs. - -def initShaders(): - global Shaders - Shaders = [ - ShaderProgram(None, []), - - ## increases fragment alpha as the normal turns orthogonal to the view - ## this is useful for viewing shells that enclose a volume (such as isosurfaces) - ShaderProgram('balloon', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0); - gl_FragColor = color; - } - """) - ]), - - ## colors fragments based on face normals relative to view - ## This means that the colors will change depending on how the view is rotated - ShaderProgram('viewNormalColor', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.x = (normal.x + 1.0) * 0.5; - color.y = (normal.y + 1.0) * 0.5; - color.z = (normal.z + 1.0) * 0.5; - gl_FragColor = color; - } - """) - ]), - - ## colors fragments based on absolute face normals. - ShaderProgram('normalColor', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.x = (normal.x + 1.0) * 0.5; - color.y = (normal.y + 1.0) * 0.5; - color.z = (normal.z + 1.0) * 0.5; - gl_FragColor = color; - } - """) - ]), - - ## very simple simulation of lighting. - ## The light source position is always relative to the camera. - ShaderProgram('shaded', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0))); - p = p < 0. ? 0. : p * 0.8; - vec4 color = gl_Color; - color.x = color.x * (0.2 + p); - color.y = color.y * (0.2 + p); - color.z = color.z * (0.2 + p); - gl_FragColor = color; - } - """) - ]), - - ## colors get brighter near edges of object - ShaderProgram('edgeHilight', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0); - color.x = color.x + s * (1.0-color.x); - color.y = color.y + s * (1.0-color.y); - color.z = color.z + s * (1.0-color.z); - gl_FragColor = color; - } - """) - ]), - - ## colors fragments by z-value. - ## This is useful for coloring surface plots by height. - ## This shader uses a uniform called "colorMap" to determine how to map the colors: - ## red = pow(colorMap[0]*(z + colorMap[1]), colorMap[2]) - ## green = pow(colorMap[3]*(z + colorMap[4]), colorMap[5]) - ## blue = pow(colorMap[6]*(z + colorMap[7]), colorMap[8]) - ## (set the values like this: shader['uniformMap'] = array([...]) - ShaderProgram('heightColor', [ - VertexShader(""" - varying vec4 pos; - void main() { - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - pos = gl_Vertex; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - uniform float colorMap[9]; - varying vec4 pos; - //out vec4 gl_FragColor; // only needed for later glsl versions - //in vec4 gl_Color; - void main() { - vec4 color = gl_Color; - color.x = colorMap[0] * (pos.z + colorMap[1]); - if (colorMap[2] != 1.0) - color.x = pow(color.x, colorMap[2]); - color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); - - color.y = colorMap[3] * (pos.z + colorMap[4]); - if (colorMap[5] != 1.0) - color.y = pow(color.y, colorMap[5]); - color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); - - color.z = colorMap[6] * (pos.z + colorMap[7]); - if (colorMap[8] != 1.0) - color.z = pow(color.z, colorMap[8]); - color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); - - color.w = 1.0; - gl_FragColor = color; - } - """), - ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}), - ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x - ## See: - ## - ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios - ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 - ## - ## - VertexShader(""" - void main() { - gl_FrontColor=gl_Color; - gl_PointSize = gl_Normal.x; - gl_Position = ftransform(); - } - """), - #FragmentShader(""" - ##version 120 - #uniform sampler2D texture; - #void main ( ) - #{ - #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color; - #} - #""") - ]), - ] - - -CompiledShaderPrograms = {} - -def getShaderProgram(name): - return ShaderProgram.names[name] - -class Shader(object): - def __init__(self, shaderType, code): - self.shaderType = shaderType - self.code = code - self.compiled = None - - def shader(self): - if self.compiled is None: - try: - self.compiled = shaders.compileShader(self.code, self.shaderType) - except NullFunctionError: - raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.") - except RuntimeError as exc: - ## Format compile errors a bit more nicely - if len(exc.args) == 3: - err, code, typ = exc.args - if not err.startswith('Shader compile failure'): - raise - code = code[0].decode('utf_8').split('\n') - err, c, msgs = err.partition(':') - err = err + '\n' - msgs = re.sub('b\'','',msgs) - msgs = re.sub('\'$','',msgs) - msgs = re.sub('\\\\n','\n',msgs) - msgs = msgs.split('\n') - errNums = [()] * len(code) - for i, msg in enumerate(msgs): - msg = msg.strip() - if msg == '': - continue - m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg) - if m is not None: - line = int(m.groups()[1]) - errNums[line-1] = errNums[line-1] + (str(i+1),) - #code[line-1] = '%d\t%s' % (i+1, code[line-1]) - err = err + "%d %s\n" % (i+1, msg) - errNums = [','.join(n) for n in errNums] - maxlen = max(map(len, errNums)) - code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)] - err = err + '\n'.join(code) - raise Exception(err) - else: - raise - return self.compiled - -class VertexShader(Shader): - def __init__(self, code): - Shader.__init__(self, GL_VERTEX_SHADER, code) - -class FragmentShader(Shader): - def __init__(self, code): - Shader.__init__(self, GL_FRAGMENT_SHADER, code) - - - - -class ShaderProgram(object): - names = {} - - def __init__(self, name, shaders, uniforms=None): - self.name = name - ShaderProgram.names[name] = self - self.shaders = shaders - self.prog = None - self.blockData = {} - self.uniformData = {} - - ## parse extra options from the shader definition - if uniforms is not None: - for k,v in uniforms.items(): - self[k] = v - - def setBlockData(self, blockName, data): - if data is None: - del self.blockData[blockName] - else: - self.blockData[blockName] = data - - def setUniformData(self, uniformName, data): - if data is None: - del self.uniformData[uniformName] - else: - self.uniformData[uniformName] = data - - def __setitem__(self, item, val): - self.setUniformData(item, val) - - def __delitem__(self, item): - self.setUniformData(item, None) - - def program(self): - if self.prog is None: - try: - compiled = [s.shader() for s in self.shaders] ## compile all shaders - self.prog = shaders.compileProgram(*compiled) ## compile program - except: - self.prog = -1 - raise - return self.prog - - def __enter__(self): - if len(self.shaders) > 0 and self.program() != -1: - glUseProgram(self.program()) - - try: - ## load uniform values into program - for uniformName, data in self.uniformData.items(): - loc = self.uniform(uniformName) - if loc == -1: - raise Exception('Could not find uniform variable "%s"' % uniformName) - glUniform1fv(loc, len(data), data) - - ### bind buffer data to program blocks - #if len(self.blockData) > 0: - #bindPoint = 1 - #for blockName, data in self.blockData.items(): - ### Program should have a uniform block declared: - ### - ### layout (std140) uniform blockName { - ### vec4 diffuse; - ### }; - - ### pick any-old binding point. (there are a limited number of these per-program - #bindPoint = 1 - - ### get the block index for a uniform variable in the shader - #blockIndex = glGetUniformBlockIndex(self.program(), blockName) - - ### give the shader block a binding point - #glUniformBlockBinding(self.program(), blockIndex, bindPoint) - - ### create a buffer - #buf = glGenBuffers(1) - #glBindBuffer(GL_UNIFORM_BUFFER, buf) - #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) - ### also possible to use glBufferSubData to fill parts of the buffer - - ### bind buffer to the same binding point - #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) - except: - glUseProgram(0) - raise - - - - def __exit__(self, *args): - if len(self.shaders) > 0: - glUseProgram(0) - - def uniform(self, name): - """Return the location integer for a uniform variable in this program""" - return glGetUniformLocation(self.program(), name.encode('utf_8')) - - #def uniformBlockInfo(self, blockName): - #blockIndex = glGetUniformBlockIndex(self.program(), blockName) - #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) - #indices = [] - #for i in range(count): - #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)) - -class HeightColorShader(ShaderProgram): - def __enter__(self): - ## Program should have a uniform block declared: - ## - ## layout (std140) uniform blockName { - ## vec4 diffuse; - ## vec4 ambient; - ## }; - - ## pick any-old binding point. (there are a limited number of these per-program - bindPoint = 1 - - ## get the block index for a uniform variable in the shader - blockIndex = glGetUniformBlockIndex(self.program(), "blockName") - - ## give the shader block a binding point - glUniformBlockBinding(self.program(), blockIndex, bindPoint) - - ## create a buffer - buf = glGenBuffers(1) - glBindBuffer(GL_UNIFORM_BUFFER, buf) - glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) - ## also possible to use glBufferSubData to fill parts of the buffer - - ## bind buffer to the same binding point - glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) - -initShaders() diff --git a/pyqtgraph/ordereddict.py b/pyqtgraph/ordereddict.py deleted file mode 100644 index a562c8b..0000000 --- a/pyqtgraph/ordereddict.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -import warnings -warnings.warn( - "OrderedDict is in the standard library for supported versions of Python. Will be removed in 0.13", - DeprecationWarning, stacklevel=2 -) - -import sys -if sys.version[0] > '2': - from collections import OrderedDict -else: - from UserDict import DictMixin - - class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py deleted file mode 100644 index 2d83027..0000000 --- a/pyqtgraph/parametertree/Parameter.py +++ /dev/null @@ -1,840 +0,0 @@ -# -*- coding: utf-8 -*- -from .. import functions as fn -from ..Qt import QtGui, QtCore -import os, weakref, re -from collections import OrderedDict -from ..python2_3 import asUnicode, basestring -from .ParameterItem import ParameterItem -import warnings - -PARAM_TYPES = {} -PARAM_NAMES = {} - -def registerParameterType(name, cls, override=False): - """Register a parameter type in the parametertree system. - - This enables construction of custom Parameter classes by name in - :meth:`~pyqtgraph.parametertree.Parameter.create`. - """ - global PARAM_TYPES - if name in PARAM_TYPES and not override: - raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) - PARAM_TYPES[name] = cls - PARAM_NAMES[cls] = name - -def __reload__(old): - PARAM_TYPES.update(old.get('PARAM_TYPES', {})) - PARAM_NAMES.update(old.get('PARAM_NAMES', {})) - -class Parameter(QtCore.QObject): - """ - A Parameter is the basic unit of data in a parameter tree. Each parameter has - a name, a type, a value, and several other properties that modify the behavior of the - Parameter. Parameters may have parent / child / sibling relationships to construct - organized hierarchies. Parameters generally do not have any inherent GUI or visual - interpretation; instead they manage ParameterItem instances which take care of - display and user interaction. - - Note: It is fairly uncommon to use the Parameter class directly; mostly you - will use subclasses which provide specialized type and data handling. The static - pethod Parameter.create(...) is an easy way to generate instances of these subclasses. - - For more Parameter types, see ParameterTree.parameterTypes module. - - =================================== ========================================================= - **Signals:** - sigStateChanged(self, change, info) Emitted when anything changes about this parameter at - all. - The second argument is a string indicating what changed - ('value', 'childAdded', etc..) - The third argument can be any extra information about - the change - sigTreeStateChanged(self, changes) Emitted when any child in the tree changes state - (but only if monitorChildren() is called) - the format of *changes* is [(param, change, info), ...] - sigValueChanged(self, value) Emitted when value is finished changing - sigValueChanging(self, value) Emitted immediately for all value changes, - including during editing. - sigChildAdded(self, child, index) Emitted when a child is added - sigChildRemoved(self, child) Emitted when a child is removed - sigRemoved(self) Emitted when this parameter is removed - sigParentChanged(self, parent) Emitted when this parameter's parent has changed - sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed - sigDefaultChanged(self, default) Emitted when this parameter's default value has changed - sigNameChanged(self, name) Emitted when this parameter's name has changed - sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed - sigContextMenu(self, name) Emitted when a context menu was clicked - =================================== ========================================================= - """ - ## name, type, limits, etc. - ## can also carry UI hints (slider vs spinbox, etc.) - - sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited - sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited - - sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index - sigChildRemoved = QtCore.Signal(object, object) ## self, child - sigRemoved = QtCore.Signal(object) ## self - sigParentChanged = QtCore.Signal(object, object) ## self, parent - sigLimitsChanged = QtCore.Signal(object, object) ## self, limits - sigDefaultChanged = QtCore.Signal(object, object) ## self, default - sigNameChanged = QtCore.Signal(object, object) ## self, name - sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} - - ## Emitted when anything changes about this parameter at all. - ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) - ## The third argument can be any extra information about the change - sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info - - ## emitted when any child in the tree changes state - ## (but only if monitorChildren() is called) - sigTreeStateChanged = QtCore.Signal(object, object) # self, changes - # changes = [(param, change, info), ...] - sigContextMenu = QtCore.Signal(object, object) # self, name - - # bad planning. - #def __new__(cls, *args, **opts): - #try: - #cls = PARAM_TYPES[opts['type']] - #except KeyError: - #pass - #return QtCore.QObject.__new__(cls, *args, **opts) - - @staticmethod - def create(**opts): - """ - Static method that creates a new Parameter (or subclass) instance using - opts['type'] to select the appropriate class. - - All options are passed directly to the new Parameter's __init__ method. - Use registerParameterType() to add new class types. - """ - typ = opts.get('type', None) - if typ is None: - cls = Parameter - else: - cls = PARAM_TYPES[opts['type']] - return cls(**opts) - - def __init__(self, **opts): - """ - Initialize a Parameter object. Although it is rare to directly create a - Parameter instance, the options available to this method are also allowed - by most Parameter subclasses. - - ======================= ========================================================= - **Keyword Arguments:** - name The name to give this Parameter. This is the name that - will appear in the left-most column of a ParameterTree - for this Parameter. - value The value to initially assign to this Parameter. - default The default value for this Parameter (most Parameters - provide an option to 'reset to default'). - children A list of children for this Parameter. Children - may be given either as a Parameter instance or as a - dictionary to pass to Parameter.create(). In this way, - it is possible to specify complex hierarchies of - Parameters from a single nested data structure. - readonly If True, the user will not be allowed to edit this - Parameter. (default=False) - enabled If False, any widget(s) for this parameter will appear - disabled. (default=True) - visible If False, the Parameter will not appear when displayed - in a ParameterTree. (default=True) - renamable If True, the user may rename this Parameter. - (default=False) - removable If True, the user may remove this Parameter. - (default=False) - expanded If True, the Parameter will initially be expanded in - ParameterTrees: Its children will be visible. - (default=True) - syncExpanded If True, the `expanded` state of this Parameter is - synchronized with all ParameterTrees it is displayed in. - (default=False) - title (str or None) If specified, then the parameter will be - displayed to the user using this string as its name. - However, the parameter will still be referred to - internally using the *name* specified above. Note that - this option is not compatible with renamable=True. - (default=None; added in version 0.9.9) - ======================= ========================================================= - """ - - - QtCore.QObject.__init__(self) - - self.opts = { - 'type': None, - 'readonly': False, - 'visible': True, - 'enabled': True, - 'renamable': False, - 'removable': False, - 'strictNaming': False, # forces name to be usable as a python variable - 'expanded': True, - 'syncExpanded': False, - 'title': None, - #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. - } - value = opts.get('value', None) - name = opts.get('name', None) - self.opts.update(opts) - self.opts['value'] = None # will be set later. - self.opts['name'] = None - - self.childs = [] - self.names = {} ## map name:child - self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter - self._parent = None - self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit - self.blockTreeChangeEmit = 0 - #self.monitoringChildren = False ## prevent calling monitorChildren more than once - - if not isinstance(name, basestring): - raise Exception("Parameter must have a string name specified in opts.") - self.setName(name) - - self.addChildren(self.opts.pop('children', [])) - - if value is not None: - self.setValue(value) - - if 'default' not in self.opts: - self.opts['default'] = None - - ## Connect all state changed signals to the general sigStateChanged - self.sigValueChanged.connect(self._emitValueChanged) - self.sigChildAdded.connect(self._emitChildAddedChanged) - self.sigChildRemoved.connect(self._emitChildRemovedChanged) - self.sigParentChanged.connect(self._emitParentChanged) - self.sigLimitsChanged.connect(self._emitLimitsChanged) - self.sigDefaultChanged.connect(self._emitDefaultChanged) - self.sigNameChanged.connect(self._emitNameChanged) - self.sigOptionsChanged.connect(self._emitOptionsChanged) - self.sigContextMenu.connect(self._emitContextMenuChanged) - - - #self.watchParam(self) ## emit treechange signals if our own state changes - - def name(self): - """Return the name of this Parameter.""" - return self.opts['name'] - - def title(self): - """Return the title of this Parameter. - - By default, the title is the same as the name unless it has been explicitly specified - otherwise.""" - title = self.opts.get('title', None) - if title is None: - title = self.name() - return title - - def contextMenu(self, name): - """"A context menu entry was clicked""" - self.sigContextMenu.emit(self, name) - - def setName(self, name): - """Attempt to change the name of this parameter; return the actual name. - (The parameter may reject the name change or automatically pick a different name)""" - if self.opts['strictNaming']: - if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): - raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) - parent = self.parent() - if parent is not None: - name = parent._renameChild(self, name) ## first ask parent if it's ok to rename - if self.opts['name'] != name: - self.opts['name'] = name - self.sigNameChanged.emit(self, name) - return name - - def type(self): - """Return the type string for this Parameter.""" - return self.opts['type'] - - def isType(self, typ): - """ - Return True if this parameter type matches the name *typ*. - This can occur either of two ways: - - - If self.type() == *typ* - - If this parameter's class is registered with the name *typ* - """ - if self.type() == typ: - return True - global PARAM_TYPES - cls = PARAM_TYPES.get(typ, None) - if cls is None: - raise Exception("Type name '%s' is not registered." % str(typ)) - return self.__class__ is cls - - def childPath(self, child): - """ - Return the path of parameter names from self to child. - If child is not a (grand)child of self, return None. - """ - path = [] - while child is not self: - path.insert(0, child.name()) - child = child.parent() - if child is None: - return None - return path - - def setValue(self, value, blockSignal=None): - """ - Set the value of this Parameter; return the actual value that was set. - (this may be different from the value that was requested) - """ - try: - if blockSignal is not None: - self.sigValueChanged.disconnect(blockSignal) - value = self._interpretValue(value) - if fn.eq(self.opts['value'], value): - return value - self.opts['value'] = value - self.sigValueChanged.emit(self, value) # value might change after signal is received by tree item - finally: - if blockSignal is not None: - self.sigValueChanged.connect(blockSignal) - - return self.opts['value'] - - def _interpretValue(self, v): - return v - - def value(self): - """ - Return the value of this Parameter. - """ - return self.opts['value'] - - def getValues(self): - """Return a tree of all values that are children of this parameter""" - vals = OrderedDict() - for ch in self: - vals[ch.name()] = (ch.value(), ch.getValues()) - return vals - - def saveState(self, filter=None): - """ - Return a structure representing the entire state of the parameter tree. - The tree state may be restored from this structure using restoreState(). - - If *filter* is set to 'user', then only user-settable data will be included in the - returned state. - """ - if filter is None: - state = self.opts.copy() - if state['type'] is None: - global PARAM_NAMES - state['type'] = PARAM_NAMES.get(type(self), None) - elif filter == 'user': - state = {'value': self.value()} - else: - raise ValueError("Unrecognized filter argument: '%s'" % filter) - - ch = OrderedDict([(ch.name(), ch.saveState(filter=filter)) for ch in self]) - if len(ch) > 0: - state['children'] = ch - return state - - def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): - """ - Restore the state of this parameter and its children from a structure generated using saveState() - If recursive is True, then attempt to restore the state of child parameters as well. - If addChildren is True, then any children which are referenced in the state object will be - created if they do not already exist. - If removeChildren is True, then any children which are not referenced in the state object will - be removed. - If blockSignals is True, no signals will be emitted until the tree has been completely restored. - This prevents signal handlers from responding to a partially-rebuilt network. - """ - state = state.copy() - childState = state.pop('children', []) - - ## list of children may be stored either as list or dict. - if isinstance(childState, dict): - cs = [] - for k,v in childState.items(): - cs.append(v.copy()) - cs[-1].setdefault('name', k) - childState = cs - - if blockSignals: - self.blockTreeChangeSignal() - - try: - self.setOpts(**state) - - if not recursive: - return - - ptr = 0 ## pointer to first child that has not been restored yet - foundChilds = set() - #print "==============", self.name() - - for ch in childState: - name = ch['name'] - #typ = ch.get('type', None) - #print('child: %s, %s' % (self.name()+'.'+name, typ)) - - ## First, see if there is already a child with this name - gotChild = False - for i, ch2 in enumerate(self.childs[ptr:]): - #print " ", ch2.name(), ch2.type() - if ch2.name() != name: # or not ch2.isType(typ): - continue - gotChild = True - #print " found it" - if i != 0: ## move parameter to next position - #self.removeChild(ch2) - self.insertChild(ptr, ch2) - #print " moved to position", ptr - ch2.restoreState(ch, recursive=recursive, addChildren=addChildren, removeChildren=removeChildren) - foundChilds.add(ch2) - - break - - if not gotChild: - if not addChildren: - #print " ignored child" - continue - #print " created new" - ch2 = Parameter.create(**ch) - self.insertChild(ptr, ch2) - foundChilds.add(ch2) - - ptr += 1 - - if removeChildren: - for ch in self.childs[:]: - if ch not in foundChilds: - #print " remove:", ch - self.removeChild(ch) - finally: - if blockSignals: - self.unblockTreeChangeSignal() - - - - def defaultValue(self): - """Return the default value for this parameter.""" - return self.opts['default'] - - def setDefault(self, val): - """Set the default value for this parameter.""" - if self.opts['default'] == val: - return - self.opts['default'] = val - self.sigDefaultChanged.emit(self, val) - - def setToDefault(self): - """Set this parameter's value to the default.""" - if self.hasDefault(): - self.setValue(self.defaultValue()) - - def hasDefault(self): - """Returns True if this parameter has a default value.""" - return self.opts['default'] is not None - - def valueIsDefault(self): - """Returns True if this parameter's value is equal to the default value.""" - return self.value() == self.defaultValue() - - def setLimits(self, limits): - """Set limits on the acceptable values for this parameter. - The format of limits depends on the type of the parameter and - some parameters do not make use of limits at all.""" - if 'limits' in self.opts and self.opts['limits'] == limits: - return - self.opts['limits'] = limits - self.sigLimitsChanged.emit(self, limits) - return limits - - def writable(self): - """ - Returns True if this parameter's value can be changed by the user. - Note that the value of the parameter can *always* be changed by - calling setValue(). - """ - return not self.readonly() - - def setWritable(self, writable=True): - """Set whether this Parameter should be editable by the user. (This is - exactly the opposite of setReadonly).""" - self.setOpts(readonly=not writable) - - def readonly(self): - """ - Return True if this parameter is read-only. (this is the opposite of writable()) - """ - return self.opts.get('readonly', False) - - def setReadonly(self, readonly=True): - """Set whether this Parameter's value may be edited by the user - (this is the opposite of setWritable()).""" - self.setOpts(readonly=readonly) - - def setOpts(self, **opts): - """ - Set any arbitrary options on this parameter. - The exact behavior of this function will depend on the parameter type, but - most parameters will accept a common set of options: value, name, limits, - default, readonly, removable, renamable, visible, enabled, expanded and syncExpanded. - - See :func:`Parameter.__init__ ` - for more information on default options. - """ - changed = OrderedDict() - for k in opts: - if k == 'value': - self.setValue(opts[k]) - elif k == 'name': - self.setName(opts[k]) - elif k == 'limits': - self.setLimits(opts[k]) - elif k == 'default': - self.setDefault(opts[k]) - elif k not in self.opts or self.opts[k] != opts[k]: - self.opts[k] = opts[k] - changed[k] = opts[k] - - if len(changed) > 0: - self.sigOptionsChanged.emit(self, changed) - - def emitStateChanged(self, changeDesc, data): - ## Emits stateChanged signal and - ## requests emission of new treeStateChanged signal - self.sigStateChanged.emit(self, changeDesc, data) - #self.treeStateChanged(self, changeDesc, data) - self.treeStateChanges.append((self, changeDesc, data)) - self.emitTreeChanges() - - def _emitValueChanged(self, param, data): - self.emitStateChanged("value", data) - - def _emitChildAddedChanged(self, param, *data): - self.emitStateChanged("childAdded", data) - - def _emitChildRemovedChanged(self, param, data): - self.emitStateChanged("childRemoved", data) - - def _emitParentChanged(self, param, data): - self.emitStateChanged("parent", data) - - def _emitLimitsChanged(self, param, data): - self.emitStateChanged("limits", data) - - def _emitDefaultChanged(self, param, data): - self.emitStateChanged("default", data) - - def _emitNameChanged(self, param, data): - self.emitStateChanged("name", data) - - def _emitOptionsChanged(self, param, data): - self.emitStateChanged("options", data) - - def _emitContextMenuChanged(self, param, data): - self.emitStateChanged("contextMenu", data) - - def makeTreeItem(self, depth): - """ - Return a TreeWidgetItem suitable for displaying/controlling the content of - this parameter. This is called automatically when a ParameterTree attempts - to display this Parameter. - Most subclasses will want to override this function. - """ - if hasattr(self, 'itemClass'): - #print "Param:", self, "Make item from itemClass:", self.itemClass - return self.itemClass(self, depth) - else: - return ParameterItem(self, depth=depth) - - - def addChild(self, child, autoIncrementName=None): - """ - Add another parameter to the end of this parameter's child list. - - See insertChild() for a description of the *autoIncrementName* - argument. - """ - return self.insertChild(len(self.childs), child, autoIncrementName=autoIncrementName) - - def addChildren(self, children): - """ - Add a list or dict of children to this parameter. This method calls - addChild once for each value in *children*. - """ - ## If children was specified as dict, then assume keys are the names. - if isinstance(children, dict): - ch2 = [] - for name, opts in children.items(): - if isinstance(opts, dict) and 'name' not in opts: - opts = opts.copy() - opts['name'] = name - ch2.append(opts) - children = ch2 - - for chOpts in children: - #print self, "Add child:", type(chOpts), id(chOpts) - self.addChild(chOpts) - - - def insertChild(self, pos, child, autoIncrementName=None): - """ - Insert a new child at pos. - If pos is a Parameter, then insert at the position of that Parameter. - If child is a dict, then a parameter is constructed using - :func:`Parameter.create `. - - By default, the child's 'autoIncrementName' option determines whether - the name will be adjusted to avoid prior name collisions. This - behavior may be overridden by specifying the *autoIncrementName* - argument. This argument was added in version 0.9.9. - """ - if isinstance(child, dict): - child = Parameter.create(**child) - - name = child.name() - if name in self.names and child is not self.names[name]: - if autoIncrementName is True or (autoIncrementName is None and child.opts.get('autoIncrementName', False)): - name = self.incrementName(name) - child.setName(name) - else: - raise Exception("Already have child named %s" % str(name)) - if isinstance(pos, Parameter): - pos = self.childs.index(pos) - - with self.treeChangeBlocker(): - if child.parent() is not None: - child.remove() - - self.names[name] = child - self.childs.insert(pos, child) - - child.parentChanged(self) - child.sigTreeStateChanged.connect(self.treeStateChanged) - self.sigChildAdded.emit(self, child, pos) - return child - - def removeChild(self, child): - """Remove a child parameter.""" - name = child.name() - if name not in self.names or self.names[name] is not child: - raise Exception("Parameter %s is not my child; can't remove." % str(child)) - del self.names[name] - self.childs.pop(self.childs.index(child)) - child.parentChanged(None) - try: - child.sigTreeStateChanged.disconnect(self.treeStateChanged) - except (TypeError, RuntimeError): ## already disconnected - pass - self.sigChildRemoved.emit(self, child) - - def clearChildren(self): - """Remove all child parameters.""" - for ch in self.childs[:]: - self.removeChild(ch) - - def children(self): - """Return a list of this parameter's children. - Warning: this overrides QObject.children - """ - return self.childs[:] - - def hasChildren(self): - """Return True if this Parameter has children.""" - return len(self.childs) > 0 - - def parentChanged(self, parent): - """This method is called when the parameter's parent has changed. - It may be useful to extend this method in subclasses.""" - self._parent = parent - self.sigParentChanged.emit(self, parent) - - def parent(self): - """Return the parent of this parameter.""" - return self._parent - - def remove(self): - """Remove this parameter from its parent's child list""" - parent = self.parent() - if parent is None: - raise Exception("Cannot remove; no parent.") - parent.removeChild(self) - self.sigRemoved.emit(self) - - def incrementName(self, name): - ## return an unused name by adding a number to the name given - base, num = re.match(r'(.*)(\d*)', name).groups() - numLen = len(num) - if numLen == 0: - num = 2 - numLen = 1 - else: - num = int(num) - while True: - newName = base + ("%%0%dd"%numLen) % num - if newName not in self.names: - return newName - num += 1 - - def __iter__(self): - for ch in self.childs: - yield ch - - def __getitem__(self, names): - """Get the value of a child parameter. The name may also be a tuple giving - the path to a sub-parameter:: - - value = param[('child', 'grandchild')] - """ - if not isinstance(names, tuple): - names = (names,) - return self.param(*names).value() - - def __setitem__(self, names, value): - """Set the value of a child parameter. The name may also be a tuple giving - the path to a sub-parameter:: - - param[('child', 'grandchild')] = value - """ - if isinstance(names, basestring): - names = (names,) - return self.param(*names).setValue(value) - - def keys(self): - return self.names - - def child(self, *names): - """Return a child parameter. - Accepts the name of the child or a tuple (path, to, child) - - Added in version 0.9.9. Earlier versions used the 'param' method, which is still - implemented for backward compatibility. - """ - try: - param = self.names[names[0]] - except KeyError: - raise KeyError("Parameter %s has no child named %s" % (self.name(), names[0])) - - if len(names) > 1: - return param.child(*names[1:]) - else: - return param - - def param(self, *names): - # for backward compatibility. - return self.child(*names) - - def __repr__(self): - return asUnicode("<%s '%s' at 0x%x>") % (self.__class__.__name__, self.name(), id(self)) - - def __getattr__(self, attr): - ## Leaving this undocumented because I might like to remove it in the future.. - #print type(self), attr - warnings.warn( - 'Use of Parameter.subParam is deprecated and will be removed in 0.13 ' - 'Use Parameter.param(name) instead.', - DeprecationWarning, stacklevel=2 - ) - if 'names' not in self.__dict__: - raise AttributeError(attr) - if attr in self.names: - import traceback - traceback.print_stack() - print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.") - return self.param(attr) - else: - raise AttributeError(attr) - - def _renameChild(self, child, name): - ## Only to be called from Parameter.rename - if name in self.names: - return child.name() - self.names[name] = child - del self.names[child.name()] - return name - - def registerItem(self, item): - self.items[item] = None - - def hide(self): - """Hide this parameter. It and its children will no longer be visible in any ParameterTree - widgets it is connected to.""" - self.show(False) - - def show(self, s=True): - """Show this parameter. """ - self.opts['visible'] = s - self.sigOptionsChanged.emit(self, {'visible': s}) - - - def treeChangeBlocker(self): - """ - Return an object that can be used to temporarily block and accumulate - sigTreeStateChanged signals. This is meant to be used when numerous changes are - about to be made to the tree and only one change signal should be - emitted at the end. - - Example:: - - with param.treeChangeBlocker(): - param.addChild(...) - param.removeChild(...) - param.setValue(...) - """ - return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) - - def blockTreeChangeSignal(self): - """ - Used to temporarily block and accumulate tree change signals. - *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. - """ - self.blockTreeChangeEmit += 1 - - def unblockTreeChangeSignal(self): - """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" - self.blockTreeChangeEmit -= 1 - self.emitTreeChanges() - - - def treeStateChanged(self, param, changes): - """ - Called when the state of any sub-parameter has changed. - - ============== ================================================================ - **Arguments:** - param The immediate child whose tree state has changed. - note that the change may have originated from a grandchild. - changes List of tuples describing all changes that have been made - in this event: (param, changeDescr, data) - ============== ================================================================ - - This function can be extended to react to tree state changes. - """ - self.treeStateChanges.extend(changes) - self.emitTreeChanges() - - def emitTreeChanges(self): - if self.blockTreeChangeEmit == 0: - changes = self.treeStateChanges - self.treeStateChanges = [] - if len(changes) > 0: - self.sigTreeStateChanged.emit(self, changes) - - -class SignalBlocker(object): - def __init__(self, enterFn, exitFn): - self.enterFn = enterFn - self.exitFn = exitFn - - def __enter__(self): - self.enterFn() - - def __exit__(self, exc_type, exc_value, tb): - self.exitFn() - - - diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py deleted file mode 100644 index 47b91b1..0000000 --- a/pyqtgraph/parametertree/ParameterItem.py +++ /dev/null @@ -1,223 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB -from ..python2_3 import asUnicode -import os, weakref, re - -translate = QtCore.QCoreApplication.translate - -class ParameterItem(QtGui.QTreeWidgetItem): - """ - Abstract ParameterTree item. - Used to represent the state of a Parameter from within a ParameterTree. - - - Sets first column of item to name - - generates context menu if item is renamable or removable - - handles child added / removed events - - provides virtual functions for handling changes from parameter - - For more ParameterItem types, see ParameterTree.parameterTypes module. - """ - - def __init__(self, param, depth=0): - QtGui.QTreeWidgetItem.__init__(self, [param.title(), '']) - - self.param = param - self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) - self.depth = depth - - param.sigValueChanged.connect(self.valueChanged) - param.sigChildAdded.connect(self.childAdded) - param.sigChildRemoved.connect(self.childRemoved) - param.sigNameChanged.connect(self.nameChanged) - param.sigLimitsChanged.connect(self.limitsChanged) - param.sigDefaultChanged.connect(self.defaultChanged) - param.sigOptionsChanged.connect(self.optsChanged) - param.sigParentChanged.connect(self.parentChanged) - - self.updateFlags() - - ## flag used internally during name editing - self.ignoreNameColumnChange = False - - def updateFlags(self): - ## called when Parameter opts changed - opts = self.param.opts - - flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - if opts.get('renamable', False): - if opts.get('title', None) is not None: - raise Exception("Cannot make parameter with both title != None and renamable == True.") - flags |= QtCore.Qt.ItemIsEditable - - ## handle movable / dropEnabled options - if opts.get('movable', False): - flags |= QtCore.Qt.ItemIsDragEnabled - if opts.get('dropEnabled', False): - flags |= QtCore.Qt.ItemIsDropEnabled - self.setFlags(flags) - - - def valueChanged(self, param, val): - ## called when the parameter's value has changed - pass - - def isFocusable(self): - """Return True if this item should be included in the tab-focus order""" - return False - - def setFocus(self): - """Give input focus to this item. - Can be reimplemented to display editor widgets, etc. - """ - pass - - def focusNext(self, forward=True): - """Give focus to the next (or previous) focusable item in the parameter tree""" - self.treeWidget().focusNext(self, forward=forward) - - - def treeWidgetChanged(self): - """Called when this item is added or removed from a tree. - Expansion, visibility, and column widgets must all be configured AFTER - the item is added to a tree, not during __init__. - """ - self.setHidden(not self.param.opts.get('visible', True)) - self.setExpanded(self.param.opts.get('expanded', True)) - - def childAdded(self, param, child, pos): - item = child.makeTreeItem(depth=self.depth+1) - self.insertChild(pos, item) - item.treeWidgetChanged() - - for i, ch in enumerate(child): - item.childAdded(child, ch, i) - - def childRemoved(self, param, child): - for i in range(self.childCount()): - item = self.child(i) - if item.param is child: - self.takeChild(i) - break - - def parentChanged(self, param, parent): - ## called when the parameter's parent has changed. - pass - - def contextMenuEvent(self, ev): - opts = self.param.opts - - if not opts.get('removable', False) and not opts.get('renamable', False)\ - and "context" not in opts: - return - - ## Generate context menu for renaming/removing parameter - self.contextMenu = QtGui.QMenu() # Put in global name space to prevent garbage collection - self.contextMenu.addSeparator() - if opts.get('renamable', False): - self.contextMenu.addAction(translate("ParameterItem", 'Rename')).triggered.connect(self.editName) - if opts.get('removable', False): - self.contextMenu.addAction(translate("ParameterItem", "Remove")).triggered.connect(self.requestRemove) - - # context menu - context = opts.get('context', None) - if isinstance(context, list): - for name in context: - self.contextMenu.addAction(name).triggered.connect( - self.contextMenuTriggered(name)) - elif isinstance(context, dict): - for name, title in context.items(): - self.contextMenu.addAction(title).triggered.connect( - self.contextMenuTriggered(name)) - - self.contextMenu.popup(ev.globalPos()) - - def columnChangedEvent(self, col): - """Called when the text in a column has been edited (or otherwise changed). - By default, we only use changes to column 0 to rename the parameter. - """ - if col == 0 and (self.param.opts.get('title', None) is None): - if self.ignoreNameColumnChange: - return - try: - newName = self.param.setName(asUnicode(self.text(col))) - except Exception: - self.setText(0, self.param.name()) - raise - - try: - self.ignoreNameColumnChange = True - self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. - finally: - self.ignoreNameColumnChange = False - - def expandedChangedEvent(self, expanded): - if self.param.opts['syncExpanded']: - self.param.setOpts(expanded=expanded) - - def nameChanged(self, param, name): - ## called when the parameter's name has changed. - if self.param.opts.get('title', None) is None: - self.titleChanged() - - def titleChanged(self): - # called when the user-visble title has changed (either opts['title'], or name if title is None) - self.setText(0, self.param.title()) - fm = QtGui.QFontMetrics(self.font(0)) - - if QT_LIB == 'PyQt6': - # PyQt6 doesn't allow or-ing of different enum types - # so we need to take its value property - textFlags = QtCore.Qt.TextSingleLine.value - else: - textFlags = QtCore.Qt.TextSingleLine - size = fm.size(textFlags, self.text(0)) - size.setHeight(int(size.height() * 1.35)) - size.setWidth(int(size.width() * 1.15)) - self.setSizeHint(0, size) - - def limitsChanged(self, param, limits): - """Called when the parameter's limits have changed""" - pass - - def defaultChanged(self, param, default): - """Called when the parameter's default value has changed""" - pass - - def optsChanged(self, param, opts): - """Called when any options are changed that are not - name, value, default, or limits""" - if 'visible' in opts: - self.setHidden(not opts['visible']) - - if 'expanded' in opts: - if self.isExpanded() != opts['expanded']: - self.setExpanded(opts['expanded']) - - if 'title' in opts: - self.titleChanged() - - self.updateFlags() - - def contextMenuTriggered(self, name): - def trigger(): - self.param.contextMenu(name) - return trigger - - def editName(self): - self.treeWidget().editItem(self, 0) - - def selected(self, sel): - """Called when this item has been selected (sel=True) OR deselected (sel=False)""" - pass - - def requestRemove(self): - ## called when remove is selected from the context menu. - ## we need to delay removal until the action is complete - ## since destroying the menu in mid-action will cause a crash. - QtCore.QTimer.singleShot(0, self.param.remove) - - ## for python 3 support, we need to redefine hash and eq methods. - def __hash__(self): - return id(self) - - def __eq__(self, x): - return x is self diff --git a/pyqtgraph/parametertree/ParameterSystem.py b/pyqtgraph/parametertree/ParameterSystem.py deleted file mode 100644 index 33bb2de..0000000 --- a/pyqtgraph/parametertree/ParameterSystem.py +++ /dev/null @@ -1,127 +0,0 @@ -from .parameterTypes import GroupParameter -from .. import functions as fn -from .SystemSolver import SystemSolver - - -class ParameterSystem(GroupParameter): - """ - ParameterSystem is a subclass of GroupParameter that manages a tree of - sub-parameters with a set of interdependencies--changing any one parameter - may affect other parameters in the system. - - See parametertree/SystemSolver for more information. - - NOTE: This API is experimental and may change substantially across minor - version numbers. - """ - def __init__(self, *args, **kwds): - GroupParameter.__init__(self, *args, **kwds) - self._system = None - self._fixParams = [] # all auto-generated 'fixed' params - sys = kwds.pop('system', None) - if sys is not None: - self.setSystem(sys) - self._ignoreChange = [] # params whose changes should be ignored temporarily - self.sigTreeStateChanged.connect(self.updateSystem) - - def setSystem(self, sys): - self._system = sys - - # auto-generate defaults to match child parameters - defaults = {} - vals = {} - for param in self: - name = param.name() - constraints = '' - if hasattr(sys, '_' + name): - constraints += 'n' - - if not param.readonly(): - constraints += 'f' - if 'n' in constraints: - ch = param.addChild(dict(name='fixed', type='bool', value=False)) - self._fixParams.append(ch) - param.setReadonly(True) - param.setOpts(expanded=False) - else: - vals[name] = param.value() - ch = param.addChild(dict(name='fixed', type='bool', value=True, readonly=True)) - #self._fixParams.append(ch) - - defaults[name] = [None, param.type(), None, constraints] - - sys.defaultState.update(defaults) - sys.reset() - for name, value in vals.items(): - setattr(sys, name, value) - - self.updateAllParams() - - def updateSystem(self, param, changes): - changes = [ch for ch in changes if ch[0] not in self._ignoreChange] - - #resets = [ch[0] for ch in changes if ch[1] == 'setToDefault'] - sets = [ch[0] for ch in changes if ch[1] == 'value'] - #for param in resets: - #setattr(self._system, param.name(), None) - - for param in sets: - #if param in resets: - #continue - - #if param in self._fixParams: - #param.parent().setWritable(param.value()) - #else: - if param in self._fixParams: - parent = param.parent() - if param.value(): - setattr(self._system, parent.name(), parent.value()) - else: - setattr(self._system, parent.name(), None) - else: - setattr(self._system, param.name(), param.value()) - - self.updateAllParams() - - def updateAllParams(self): - try: - self.sigTreeStateChanged.disconnect(self.updateSystem) - for name, state in self._system._vars.items(): - param = self.child(name) - try: - v = getattr(self._system, name) - if self._system._vars[name][2] is None: - self.updateParamState(self.child(name), 'autoSet') - param.setValue(v) - else: - self.updateParamState(self.child(name), 'fixed') - except RuntimeError: - self.updateParamState(param, 'autoUnset') - finally: - self.sigTreeStateChanged.connect(self.updateSystem) - - def updateParamState(self, param, state): - if state == 'autoSet': - bg = fn.mkBrush((200, 255, 200, 255)) - bold = False - readonly = True - elif state == 'autoUnset': - bg = fn.mkBrush(None) - bold = False - readonly = False - elif state == 'fixed': - bg = fn.mkBrush('y') - bold = True - readonly = False - - param.setReadonly(readonly) - - #for item in param.items: - #item.setBackground(0, bg) - #f = item.font(0) - #f.setWeight(f.Bold if bold else f.Normal) - #item.setFont(0, f) - - - - diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py deleted file mode 100644 index d1804fd..0000000 --- a/pyqtgraph/parametertree/ParameterTree.py +++ /dev/null @@ -1,194 +0,0 @@ -from ..Qt import QtCore, QtWidgets -from ..widgets.TreeWidget import TreeWidget -import os, weakref, re -from .ParameterItem import ParameterItem -#import functions as fn - - - -class ParameterTree(TreeWidget): - """Widget used to display or control data from a hierarchy of Parameters""" - - def __init__(self, parent=None, showHeader=True): - """ - ============== ======================================================== - **Arguments:** - parent (QWidget) An optional parent widget - showHeader (bool) If True, then the QTreeView header is displayed. - ============== ======================================================== - """ - TreeWidget.__init__(self, parent) - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setHorizontalScrollMode(self.ScrollPerPixel) - self.setAnimated(False) - self.setColumnCount(2) - self.setHeaderLabels(["Parameter", "Value"]) - self.setAlternatingRowColors(True) - self.paramSet = None - self.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents) - self.setHeaderHidden(not showHeader) - self.itemChanged.connect(self.itemChangedEvent) - self.itemExpanded.connect(self.itemExpandedEvent) - self.itemCollapsed.connect(self.itemCollapsedEvent) - self.lastSel = None - self.setRootIsDecorated(False) - - def setParameters(self, param, showTop=True): - """ - Set the top-level :class:`Parameter ` - to be displayed in this ParameterTree. - - If *showTop* is False, then the top-level parameter is hidden and only - its children will be visible. This is a convenience method equivalent - to:: - - tree.clear() - tree.addParameters(param, showTop) - """ - self.clear() - self.addParameters(param, showTop=showTop) - - def addParameters(self, param, root=None, depth=0, showTop=True): - """ - Adds one top-level :class:`Parameter ` - to the view. - - ============== ========================================================== - **Arguments:** - param The :class:`Parameter ` - to add. - root The item within the tree to which *param* should be added. - By default, *param* is added as a top-level item. - showTop If False, then *param* will be hidden, and only its - children will be visible in the tree. - ============== ========================================================== - """ - item = param.makeTreeItem(depth=depth) - if root is None: - root = self.invisibleRootItem() - ## Hide top-level item - if not showTop: - item.setText(0, '') - item.setSizeHint(0, QtCore.QSize(1,1)) - item.setSizeHint(1, QtCore.QSize(1,1)) - depth -= 1 - root.addChild(item) - item.treeWidgetChanged() - - for ch in param: - self.addParameters(ch, root=item, depth=depth+1) - - def clear(self): - """ - Remove all parameters from the tree. - """ - self.invisibleRootItem().takeChildren() - - def focusNext(self, item, forward=True): - """Give input focus to the next (or previous) item after *item* - """ - while True: - parent = item.parent() - if parent is None: - return - nextItem = self.nextFocusableChild(parent, item, forward=forward) - if nextItem is not None: - nextItem.setFocus() - self.setCurrentItem(nextItem) - return - item = parent - - def focusPrevious(self, item): - self.focusNext(item, forward=False) - - def nextFocusableChild(self, root, startItem=None, forward=True): - if startItem is None: - if forward: - index = 0 - else: - index = root.childCount()-1 - else: - if forward: - index = root.indexOfChild(startItem) + 1 - else: - index = root.indexOfChild(startItem) - 1 - - if forward: - inds = list(range(index, root.childCount())) - else: - inds = list(range(index, -1, -1)) - - for i in inds: - item = root.child(i) - if hasattr(item, 'isFocusable') and item.isFocusable(): - return item - else: - item = self.nextFocusableChild(item, forward=forward) - if item is not None: - return item - return None - - def contextMenuEvent(self, ev): - item = self.currentItem() - if hasattr(item, 'contextMenuEvent'): - item.contextMenuEvent(ev) - - def itemChangedEvent(self, item, col): - if hasattr(item, 'columnChangedEvent'): - item.columnChangedEvent(col) - - def itemExpandedEvent(self, item): - if hasattr(item, 'expandedChangedEvent'): - item.expandedChangedEvent(True) - - def itemCollapsedEvent(self, item): - if hasattr(item, 'expandedChangedEvent'): - item.expandedChangedEvent(False) - - def selectionChanged(self, *args): - sel = self.selectedItems() - if len(sel) != 1: - sel = None - if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): - self.lastSel.selected(False) - if sel is None: - self.lastSel = None - return - self.lastSel = sel[0] - if hasattr(sel[0], 'selected'): - sel[0].selected(True) - return super().selectionChanged(*args) - - # commented out due to being unreliable - # def wheelEvent(self, ev): - # self.clearSelection() - # return super().wheelEvent(ev) - - def sizeHint(self): - w, h = 0, 0 - ind = self.indentation() - for x in self.listAllItems(): - if x.isHidden(): - continue - try: - depth = x.depth - except AttributeError: - depth = 0 - - s0 = x.sizeHint(0) - s1 = x.sizeHint(1) - w = max(w, depth * ind + max(0, s0.width()) + max(0, s1.width())) - h += max(0, s0.height(), s1.height()) - # typ = x.param.opts['type'] if isinstance(x, ParameterItem) else x - # print(typ, depth * ind, (s0.width(), s0.height()), (s1.width(), s1.height()), (w, h)) - - # todo: find out if this alternative can be made to work (currently fails when color or colormap are present) - # print('custom', (w, h)) - # w = self.sizeHintForColumn(0) + self.sizeHintForColumn(1) - # h = self.viewportSizeHint().height() - # print('alternative', (w, h)) - - if not self.header().isHidden(): - h += self.header().height() - - return QtCore.QSize(w, h) diff --git a/pyqtgraph/parametertree/SystemSolver.py b/pyqtgraph/parametertree/SystemSolver.py deleted file mode 100644 index cac4248..0000000 --- a/pyqtgraph/parametertree/SystemSolver.py +++ /dev/null @@ -1,424 +0,0 @@ -from collections import OrderedDict -import numpy as np -import copy - - -class SystemSolver(object): - """ - This abstract class is used to formalize and manage user interaction with a - complex system of equations (related to "constraint satisfaction problems"). - It is often the case that devices must be controlled - through a large number of free variables, and interactions between these - variables make the system difficult to manage and conceptualize as a user - interface. This class does _not_ attempt to numerically solve the system - of equations. Rather, it provides a framework for subdividing the system - into manageable pieces and specifying closed-form solutions to these small - pieces. - - For an example, see the simple Camera class below. - - Theory of operation: Conceptualize the system as 1) a set of variables - whose values may be either user-specified or automatically generated, and - 2) a set of functions that define *how* each variable should be generated. - When a variable is accessed (as an instance attribute), the solver first - checks to see if it already has a value (either user-supplied, or cached - from a previous calculation). If it does not, then the solver calls a - method on itself (the method must be named `_variableName`) that will - either return the calculated value (which usually involves acccessing - other variables in the system), or raise RuntimeError if it is unable to - calculate the value (usually because the user has not provided sufficient - input to fully constrain the system). - - Each method that calculates a variable value may include multiple - try/except blocks, so that if one method generates a RuntimeError, it may - fall back on others. - In this way, the system may be solved by recursively searching the tree of - possible relationships between variables. This allows the user flexibility - in deciding which variables are the most important to specify, while - avoiding the apparent combinatorial explosion of calculation pathways - that must be considered by the developer. - - Solved values are cached for efficiency, and automatically cleared when - a state change invalidates the cache. The rules for this are simple: any - time a value is set, it invalidates the cache *unless* the previous value - was None (which indicates that no other variable has yet requested that - value). More complex cache management may be defined in subclasses. - - - Subclasses must define: - - 1) The *defaultState* class attribute: This is a dict containing a - description of the variables in the system--their default values, - data types, and the ways they can be constrained. The format is:: - - { name: [value, type, constraint, allowed_constraints], ...} - - * *value* is the default value. May be None if it has not been specified - yet. - * *type* may be float, int, bool, np.ndarray, ... - * *constraint* may be None, single value, or (min, max) - * None indicates that the value is not constrained--it may be - automatically generated if the value is requested. - * *allowed_constraints* is a string composed of (n)one, (f)ixed, and (r)ange. - - Note: do not put mutable objects inside defaultState! - - 2) For each variable that may be automatically determined, a method must - be defined with the name `_variableName`. This method may either return - the - """ - - defaultState = OrderedDict() - - def __init__(self): - self.__dict__['_vars'] = OrderedDict() - self.__dict__['_currentGets'] = set() - self.reset() - - def copy(self): - sys = type(self)() - sys.__dict__['_vars'] = copy.deepcopy(self.__dict__['_vars']) - sys.__dict__['_currentGets'] = copy.deepcopy(self.__dict__['_currentGets']) - return sys - - def reset(self): - """ - Reset all variables in the solver to their default state. - """ - self._currentGets.clear() - for k in self.defaultState: - self._vars[k] = self.defaultState[k][:] - - def __getattr__(self, name): - if name in self._vars: - return self.get(name) - raise AttributeError(name) - - def __setattr__(self, name, value): - """ - Set the value of a state variable. - If None is given for the value, then the constraint will also be set to None. - If a tuple is given for a scalar variable, then the tuple is used as a range constraint instead of a value. - Otherwise, the constraint is set to 'fixed'. - - """ - # First check this is a valid attribute - if name in self._vars: - if value is None: - self.set(name, value, None) - elif isinstance(value, tuple) and self._vars[name][1] is not np.ndarray: - self.set(name, None, value) - else: - self.set(name, value, 'fixed') - else: - # also allow setting any other pre-existing attribute - if hasattr(self, name): - object.__setattr__(self, name, value) - else: - raise AttributeError(name) - - def get(self, name): - """ - Return the value for parameter *name*. - - If the value has not been specified, then attempt to compute it from - other interacting parameters. - - If no value can be determined, then raise RuntimeError. - """ - if name in self._currentGets: - raise RuntimeError("Cyclic dependency while calculating '%s'." % name) - self._currentGets.add(name) - try: - v = self._vars[name][0] - if v is None: - cfunc = getattr(self, '_' + name, None) - if cfunc is None: - v = None - else: - v = cfunc() - if v is None: - raise RuntimeError("Parameter '%s' is not specified." % name) - v = self.set(name, v) - finally: - self._currentGets.remove(name) - - return v - - def set(self, name, value=None, constraint=True): - """ - Set a variable *name* to *value*. The actual set value is returned (in - some cases, the value may be cast into another type). - - If *value* is None, then the value is left to be determined in the - future. At any time, the value may be re-assigned arbitrarily unless - a constraint is given. - - If *constraint* is True (the default), then supplying a value that - violates a previously specified constraint will raise an exception. - - If *constraint* is 'fixed', then the value is set (if provided) and - the variable will not be updated automatically in the future. - - If *constraint* is a tuple, then the value is constrained to be within the - given (min, max). Either constraint may be None to disable - it. In some cases, a constraint cannot be satisfied automatically, - and the user will be forced to resolve the constraint manually. - - If *constraint* is None, then any constraints are removed for the variable. - """ - var = self._vars[name] - if constraint is None: - if 'n' not in var[3]: - raise TypeError("Empty constraints not allowed for '%s'" % name) - var[2] = constraint - elif constraint == 'fixed': - if 'f' not in var[3]: - raise TypeError("Fixed constraints not allowed for '%s'" % name) - # This is nice, but not reliable because sometimes there is 1 DOF but we set 2 - # values simultaneously. - # if var[2] is None: - # try: - # self.get(name) - # # has already been computed by the system; adding a fixed constraint - # # would overspecify the system. - # raise ValueError("Cannot fix parameter '%s'; system would become overconstrained." % name) - # except RuntimeError: - # pass - var[2] = constraint - elif isinstance(constraint, tuple): - if 'r' not in var[3]: - raise TypeError("Range constraints not allowed for '%s'" % name) - assert len(constraint) == 2 - var[2] = constraint - elif constraint is not True: - raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint) - - # type checking / massaging - if var[1] is np.ndarray and value is not None: - value = np.array(value, dtype=float) - elif var[1] in (int, float, tuple) and value is not None: - value = var[1](value) - - # constraint checks - if constraint is True and not self.check_constraint(name, value): - raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2])) - - # invalidate other dependent values - if var[0] is not None or value is None: - # todo: we can make this more clever..(and might need to) - # we just know that a value of None cannot have dependencies - # (because if anyone else had asked for this value, it wouldn't be - # None anymore) - self.resetUnfixed() - - var[0] = value - return value - - def check_constraint(self, name, value): - c = self._vars[name][2] - if c is None or value is None: - return True - if isinstance(c, tuple): - return ((c[0] is None or c[0] <= value) and - (c[1] is None or c[1] >= value)) - else: - return value == c - - def saveState(self): - """ - Return a serializable description of the solver's current state. - """ - state = OrderedDict() - for name, var in self._vars.items(): - state[name] = (var[0], var[2]) - return state - - def restoreState(self, state): - """ - Restore the state of all values and constraints in the solver. - """ - self.reset() - for name, var in state.items(): - self.set(name, var[0], var[1]) - - def resetUnfixed(self): - """ - For any variable that does not have a fixed value, reset - its value to None. - """ - for var in self._vars.values(): - if var[2] != 'fixed': - var[0] = None - - def solve(self): - for k in self._vars: - getattr(self, k) - - def checkOverconstraint(self): - """Check whether the system is overconstrained. If so, return the name of - the first overconstrained parameter. - - Overconstraints occur when any fixed parameter can be successfully computed by the system. - (Ideally, all parameters are either fixed by the user or constrained by the - system, but never both). - """ - for k,v in self._vars.items(): - if v[2] == 'fixed' and 'n' in v[3]: - oldval = v[:] - self.set(k, None, None) - try: - self.get(k) - return k - except RuntimeError: - pass - finally: - self._vars[k] = oldval - - return False - - - - - def __repr__(self): - state = OrderedDict() - for name, var in self._vars.items(): - if var[2] == 'fixed': - state[name] = var[0] - state = ', '.join(["%s=%s" % (n, v) for n,v in state.items()]) - return "<%s %s>" % (self.__class__.__name__, state) - - - - - -if __name__ == '__main__': - - class Camera(SystemSolver): - """ - Consider a simple SLR camera. The variables we will consider that - affect the camera's behavior while acquiring a photo are aperture, shutter speed, - ISO, and flash (of course there are many more, but let's keep the example simple). - - In rare cases, the user wants to manually specify each of these variables and - no more work needs to be done to take the photo. More often, the user wants to - specify more interesting constraints like depth of field, overall exposure, - or maximum allowed ISO value. - - If we add a simple light meter measurement into this system and an 'exposure' - variable that indicates the desired exposure (0 is "perfect", -1 is one stop - darker, etc), then the system of equations governing the camera behavior would - have the following variables: - - aperture, shutter, iso, flash, exposure, light meter - - The first four variables are the "outputs" of the system (they directly drive - the camera), the last is a constant (the camera itself cannot affect the - reading on the light meter), and 'exposure' specifies a desired relationship - between other variables in the system. - - So the question is: how can I formalize a system like this as a user interface? - Typical cameras have a fairly limited approach: provide the user with a list - of modes, each of which defines a particular set of constraints. For example: - - manual: user provides aperture, shutter, iso, and flash - aperture priority: user provides aperture and exposure, camera selects - iso, shutter, and flash automatically - shutter priority: user provides shutter and exposure, camera selects - iso, aperture, and flash - program: user specifies exposure, camera selects all other variables - automatically - action: camera selects all variables while attempting to maximize - shutter speed - portrait: camera selects all variables while attempting to minimize - aperture - - A more general approach might allow the user to provide more explicit - constraints on each variable (for example: I want a shutter speed of 1/30 or - slower, an ISO no greater than 400, an exposure between -1 and 1, and the - smallest aperture possible given all other constraints) and have the camera - solve the system of equations, with a warning if no solution is found. This - is exactly what we will implement in this example class. - """ - - defaultState = OrderedDict([ - # Field stop aperture - ('aperture', [None, float, None, 'nf']), - # Duration that shutter is held open. - ('shutter', [None, float, None, 'nf']), - # ISO (sensitivity) value. 100, 200, 400, 800, 1600.. - ('iso', [None, int, None, 'nf']), - - # Flash is a value indicating the brightness of the flash. A table - # is used to decide on "balanced" settings for each flash level: - # 0: no flash - # 1: s=1/60, a=2.0, iso=100 - # 2: s=1/60, a=4.0, iso=100 ..and so on.. - ('flash', [None, float, None, 'nf']), - - # exposure is a value indicating how many stops brighter (+1) or - # darker (-1) the photographer would like the photo to appear from - # the 'balanced' settings indicated by the light meter (see below). - ('exposure', [None, float, None, 'f']), - - # Let's define this as an external light meter (not affected by - # aperture) with logarithmic output. We arbitrarily choose the - # following settings as "well balanced" for each light meter value: - # -1: s=1/60, a=2.0, iso=100 - # 0: s=1/60, a=4.0, iso=100 - # 1: s=1/120, a=4.0, iso=100 ..and so on.. - # Note that the only allowed constraint mode is (f)ixed, since the - # camera never _computes_ the light meter value, it only reads it. - ('lightMeter', [None, float, None, 'f']), - - # Indicates the camera's final decision on how it thinks the photo will - # look, given the chosen settings. This value is _only_ determined - # automatically. - ('balance', [None, float, None, 'n']), - ]) - - def _aperture(self): - """ - Determine aperture automatically under a variety of conditions. - """ - iso = self.iso - exp = self.exposure - light = self.lightMeter - - try: - # shutter-priority mode - sh = self.shutter # this raises RuntimeError if shutter has not - # been specified - ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light) - ap = np.clip(ap, 2.0, 16.0) - except RuntimeError: - # program mode; we can select a suitable shutter - # value at the same time. - sh = (1./60.) - raise - - - - return ap - - def _balance(self): - iso = self.iso - light = self.lightMeter - sh = self.shutter - ap = self.aperture - fl = self.flash - - bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light) - return np.log2(bal) - - camera = Camera() - - camera.iso = 100 - camera.exposure = 0 - camera.lightMeter = 2 - camera.shutter = 1./60. - camera.flash = 0 - - camera.solve() - print(camera.saveState()) - diff --git a/pyqtgraph/parametertree/__init__.py b/pyqtgraph/parametertree/__init__.py deleted file mode 100644 index 722410d..0000000 --- a/pyqtgraph/parametertree/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .Parameter import Parameter, registerParameterType -from .ParameterTree import ParameterTree -from .ParameterItem import ParameterItem -from .ParameterSystem import ParameterSystem, SystemSolver -from . import parameterTypes as types \ No newline at end of file diff --git a/pyqtgraph/parametertree/__pycache__/Parameter.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/Parameter.cpython-36.pyc deleted file mode 100644 index 759d438..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/Parameter.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/Parameter.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/Parameter.cpython-37.pyc deleted file mode 100644 index 6343f90..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/Parameter.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-36.pyc deleted file mode 100644 index 56e6986..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-37.pyc deleted file mode 100644 index 583b897..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterItem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-36.pyc deleted file mode 100644 index 97ab6fb..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-37.pyc deleted file mode 100644 index c93f39d..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterSystem.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-36.pyc deleted file mode 100644 index 8782486..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-37.pyc deleted file mode 100644 index 430c223..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/ParameterTree.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-36.pyc deleted file mode 100644 index 2c78319..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-37.pyc deleted file mode 100644 index c6fb580..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/SystemSolver.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 4b497bf..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index a2b4177..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-36.pyc b/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-36.pyc deleted file mode 100644 index 832a20d..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-37.pyc b/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-37.pyc deleted file mode 100644 index a4a8d77..0000000 Binary files a/pyqtgraph/parametertree/__pycache__/parameterTypes.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py deleted file mode 100644 index ee33982..0000000 --- a/pyqtgraph/parametertree/parameterTypes.py +++ /dev/null @@ -1,743 +0,0 @@ -import os -from ..Qt import QtCore, QtGui -from ..python2_3 import asUnicode -from .Parameter import Parameter, registerParameterType -from .ParameterItem import ParameterItem -from ..widgets.SpinBox import SpinBox -from ..widgets.ColorButton import ColorButton -from ..colormap import ColorMap -from .. import icons as icons -from .. import functions as fn -from collections import OrderedDict - - -class WidgetParameterItem(ParameterItem): - """ - ParameterTree item with: - - * label in second column for displaying value - * simple widget for editing value (displayed instead of label when item is selected) - * button that resets value to default - - ========================== ============================================================= - **Registered Types:** - int Displays a :class:`SpinBox ` in integer - mode. - float Displays a :class:`SpinBox `. - bool Displays a QCheckBox - str Displays a QLineEdit - color Displays a :class:`ColorButton ` - colormap Displays a :class:`GradientWidget ` - ========================== ============================================================= - - This class can be subclassed by overriding makeWidget() to provide a custom widget. - """ - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - - self.asSubItem = False # place in a child item's column 0 instead of column 1 - self.hideWidget = True ## hide edit widget, replace with label when not selected - ## set this to False to keep the editor widget always visible - - # build widget with a display label and default button - w = self.makeWidget() - self.widget = w - self.eventProxy = EventProxy(w, self.widgetEventFilter) - - if self.asSubItem: - self.subItem = QtGui.QTreeWidgetItem() - self.subItem.depth = self.depth + 1 - self.subItem.setFlags(QtCore.Qt.NoItemFlags) - self.addChild(self.subItem) - - self.defaultBtn = QtGui.QPushButton() - self.defaultBtn.setAutoDefault(False) - self.defaultBtn.setFixedWidth(20) - self.defaultBtn.setFixedHeight(20) - modDir = os.path.dirname(__file__) - self.defaultBtn.setIcon(icons.getGraphIcon('default')) - self.defaultBtn.clicked.connect(self.defaultClicked) - - self.displayLabel = QtGui.QLabel() - - layout = QtGui.QHBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(2) - if not self.asSubItem: - layout.addWidget(w, 1) - layout.addWidget(self.displayLabel, 1) - layout.addStretch(0) - layout.addWidget(self.defaultBtn) - self.layoutWidget = QtGui.QWidget() - self.layoutWidget.setLayout(layout) - - if w.sigChanged is not None: - w.sigChanged.connect(self.widgetValueChanged) - - if hasattr(w, 'sigChanging'): - w.sigChanging.connect(self.widgetValueChanging) - - ## update value shown in widget. - opts = self.param.opts - if opts.get('value', None) is not None: - self.valueChanged(self, opts['value'], force=True) - else: - ## no starting value was given; use whatever the widget has - self.widgetValueChanged() - - self.updateDefaultBtn() - - self.optsChanged(self.param, self.param.opts) - - # set size hints - sw = self.widget.sizeHint() - sb = self.defaultBtn.sizeHint() - # shrink row heights a bit for more compact look - sw.setHeight(int(sw.height() * 0.9)) - sb.setHeight(int(sb.height() * 0.9)) - if self.asSubItem: - self.setSizeHint(1, sb) - self.subItem.setSizeHint(0, sw) - else: - w = sw.width() + sb.width() - h = max(sw.height(), sb.height()) - self.setSizeHint(1, QtCore.QSize(w, h)) - - def makeWidget(self): - """ - Return a single widget whose position in the tree is determined by the - value of self.asSubItem. If True, it will be placed in the second tree - column, and if False, the first tree column of a child item. - - The widget must be given three attributes: - - ========== ============================================================ - sigChanged a signal that is emitted when the widget's value is changed - value a function that returns the value - setValue a function that sets the value - ========== ============================================================ - - This is a good function to override in subclasses. - """ - opts = self.param.opts - t = opts['type'] - if t in ('int', 'float'): - defs = { - 'value': 0, 'min': None, 'max': None, - 'step': 1.0, 'dec': False, - 'siPrefix': False, 'suffix': '', 'decimals': 3, - } - if t == 'int': - defs['int'] = True - defs['minStep'] = 1.0 - for k in defs: - if k in opts: - defs[k] = opts[k] - if 'limits' in opts: - defs['min'], defs['max'] = opts['limits'] - w = SpinBox() - w.setOpts(**defs) - w.sigChanged = w.sigValueChanged - w.sigChanging = w.sigValueChanging - elif t == 'bool': - w = QtGui.QCheckBox() - w.sigChanged = w.toggled - w.value = w.isChecked - w.setValue = w.setChecked - self.hideWidget = False - elif t == 'str': - w = QtGui.QLineEdit() - w.setStyleSheet('border: 0px') - w.sigChanged = w.editingFinished - w.value = lambda: asUnicode(w.text()) - w.setValue = lambda v: w.setText(asUnicode(v)) - w.sigChanging = w.textChanged - elif t == 'color': - w = ColorButton() - w.sigChanged = w.sigColorChanged - w.sigChanging = w.sigColorChanging - w.value = w.color - w.setValue = w.setColor - self.hideWidget = False - w.setFlat(True) - elif t == 'colormap': - from ..widgets.GradientWidget import GradientWidget ## need this here to avoid import loop - w = GradientWidget(orientation='bottom') - w.sizeHint = lambda: QtCore.QSize(300, 35) - w.sigChanged = w.sigGradientChangeFinished - w.sigChanging = w.sigGradientChanged - w.value = w.colorMap - w.setValue = w.setColorMap - self.hideWidget = False - self.asSubItem = True - else: - raise Exception("Unknown type '%s'" % asUnicode(t)) - return w - - def widgetEventFilter(self, obj, ev): - ## filter widget's events - ## catch TAB to change focus - ## catch focusOut to hide editor - if ev.type() == ev.KeyPress: - if ev.key() == QtCore.Qt.Key_Tab: - self.focusNext(forward=True) - return True ## don't let anyone else see this event - elif ev.key() == QtCore.Qt.Key_Backtab: - self.focusNext(forward=False) - return True ## don't let anyone else see this event - - return False - - def setFocus(self): - self.showEditor() - - def isFocusable(self): - return self.param.opts['visible'] and self.param.opts['enabled'] and self.param.writable() - - def valueChanged(self, param, val, force=False): - ## called when the parameter's value has changed - ParameterItem.valueChanged(self, param, val) - if force or not fn.eq(val, self.widget.value()): - try: - self.widget.sigChanged.disconnect(self.widgetValueChanged) - self.param.sigValueChanged.disconnect(self.valueChanged) - self.widget.setValue(val) - self.param.setValue(self.widget.value()) - finally: - self.widget.sigChanged.connect(self.widgetValueChanged) - self.param.sigValueChanged.connect(self.valueChanged) - self.updateDisplayLabel() ## always make sure label is updated, even if values match! - self.updateDefaultBtn() - - def updateDefaultBtn(self): - ## enable/disable default btn - self.defaultBtn.setEnabled( - not self.param.valueIsDefault() and self.param.opts['enabled'] and self.param.writable()) - - # hide / show - self.defaultBtn.setVisible(self.param.hasDefault() and not self.param.readonly()) - - def updateDisplayLabel(self, value=None): - """Update the display label to reflect the value of the parameter.""" - if value is None: - value = self.param.value() - opts = self.param.opts - if isinstance(self.widget, QtGui.QAbstractSpinBox): - text = asUnicode(self.widget.lineEdit().text()) - elif isinstance(self.widget, QtGui.QComboBox): - text = self.widget.currentText() - else: - text = asUnicode(value) - self.displayLabel.setText(text) - - def widgetValueChanged(self): - ## called when the widget's value has been changed by the user - val = self.widget.value() - newVal = self.param.setValue(val) - - def widgetValueChanging(self, *args): - """ - Called when the widget's value is changing, but not finalized. - For example: editing text before pressing enter or changing focus. - """ - self.param.sigValueChanging.emit(self.param, self.widget.value()) - - def selected(self, sel): - """Called when this item has been selected (sel=True) OR deselected (sel=False)""" - ParameterItem.selected(self, sel) - - if self.widget is None: - return - if sel and self.param.writable(): - self.showEditor() - elif self.hideWidget: - self.hideEditor() - - def showEditor(self): - self.widget.show() - self.displayLabel.hide() - self.widget.setFocus(QtCore.Qt.OtherFocusReason) - if isinstance(self.widget, SpinBox): - self.widget.selectNumber() # select the numerical portion of the text for quick editing - - def hideEditor(self): - self.widget.hide() - self.displayLabel.show() - - def limitsChanged(self, param, limits): - """Called when the parameter's limits have changed""" - ParameterItem.limitsChanged(self, param, limits) - - t = self.param.opts['type'] - if t == 'int' or t == 'float': - self.widget.setOpts(bounds=limits) - else: - return ## don't know what to do with any other types.. - - def defaultChanged(self, param, value): - self.updateDefaultBtn() - - def treeWidgetChanged(self): - """Called when this item is added or removed from a tree.""" - ParameterItem.treeWidgetChanged(self) - - ## add all widgets for this item into the tree - if self.widget is not None: - tree = self.treeWidget() - if tree is None: - return - if self.asSubItem: - self.subItem.setFirstColumnSpanned(True) - tree.setItemWidget(self.subItem, 0, self.widget) - tree.setItemWidget(self, 1, self.layoutWidget) - self.displayLabel.hide() - self.selected(False) - - def defaultClicked(self): - self.param.setToDefault() - - def optsChanged(self, param, opts): - """Called when any options are changed that are not - name, value, default, or limits""" - ParameterItem.optsChanged(self, param, opts) - - if 'enabled' in opts: - self.updateDefaultBtn() - self.widget.setEnabled(opts['enabled']) - - if 'readonly' in opts: - self.updateDefaultBtn() - if hasattr(self.widget, 'setReadOnly'): - self.widget.setReadOnly(opts['readonly']) - else: - self.widget.setEnabled(self.param.opts['enabled'] and not opts['readonly']) - - if 'tip' in opts: - self.widget.setToolTip(opts['tip']) - - ## If widget is a SpinBox, pass options straight through - if isinstance(self.widget, SpinBox): - # send only options supported by spinbox - sbOpts = {} - if 'units' in opts and 'suffix' not in opts: - sbOpts['suffix'] = opts['units'] - for k,v in opts.items(): - if k in self.widget.opts: - sbOpts[k] = v - self.widget.setOpts(**sbOpts) - self.updateDisplayLabel() - - -class EventProxy(QtCore.QObject): - def __init__(self, qobj, callback): - QtCore.QObject.__init__(self) - self.callback = callback - qobj.installEventFilter(self) - - def eventFilter(self, obj, ev): - return self.callback(obj, ev) - - -class SimpleParameter(Parameter): - """Parameter representing a single value. - - This parameter is backed by :class:`WidgetParameterItem` to represent the - following parameter names: - - - 'int' - - 'float' - - 'bool' - - 'str' - - 'color' - - 'colormap' - """ - itemClass = WidgetParameterItem - - def __init__(self, *args, **kargs): - """Initialize the parameter. - - This is normally called implicitly through :meth:`Parameter.create`. - The keyword arguments avaialble to :meth:`Parameter.__init__` are - applicable. - """ - Parameter.__init__(self, *args, **kargs) - - ## override a few methods for color parameters - if self.opts['type'] == 'color': - self.value = self.colorValue - self.saveState = self.saveColorState - - def colorValue(self): - return fn.mkColor(Parameter.value(self)) - - def saveColorState(self, *args, **kwds): - state = Parameter.saveState(self, *args, **kwds) - state['value'] = fn.colorTuple(self.value()) - return state - - def _interpretValue(self, v): - fn = { - 'int': int, - 'float': float, - 'bool': bool, - 'str': asUnicode, - 'color': self._interpColor, - 'colormap': self._interpColormap, - }[self.opts['type']] - return fn(v) - - def _interpColor(self, v): - return fn.mkColor(v) - - def _interpColormap(self, v): - if not isinstance(v, ColorMap): - raise TypeError("Cannot set colormap parameter from object %r" % v) - return v - - -registerParameterType('int', SimpleParameter, override=True) -registerParameterType('float', SimpleParameter, override=True) -registerParameterType('bool', SimpleParameter, override=True) -registerParameterType('str', SimpleParameter, override=True) -registerParameterType('color', SimpleParameter, override=True) -registerParameterType('colormap', SimpleParameter, override=True) - - -class GroupParameterItem(ParameterItem): - """ - Group parameters are used mainly as a generic parent item that holds (and groups!) a set - of child parameters. It also provides a simple mechanism for displaying a button or combo - that can be used to add new parameters to the group. - """ - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - self.updateDepth(depth) - - self.addItem = None - if 'addText' in param.opts: - addText = param.opts['addText'] - if 'addList' in param.opts: - self.addWidget = QtGui.QComboBox() - self.addWidget.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.updateAddList() - self.addWidget.currentIndexChanged.connect(self.addChanged) - else: - self.addWidget = QtGui.QPushButton(addText) - self.addWidget.clicked.connect(self.addClicked) - w = QtGui.QWidget() - l = QtGui.QHBoxLayout() - l.setContentsMargins(0,0,0,0) - w.setLayout(l) - l.addWidget(self.addWidget) - l.addStretch() - self.addWidgetBox = w - self.addItem = QtGui.QTreeWidgetItem([]) - self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) - self.addItem.depth = self.depth + 1 - ParameterItem.addChild(self, self.addItem) - self.addItem.setSizeHint(0, self.addWidgetBox.sizeHint()) - - self.optsChanged(self.param, self.param.opts) - - def updateDepth(self, depth): - ## Change item's appearance based on its depth in the tree - ## This allows highest-level groups to be displayed more prominently. - if depth == 0: - for c in [0,1]: - self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100))) - self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255))) - font = self.font(c) - font.setBold(True) - font.setPointSize(font.pointSize()+1) - self.setFont(c, font) - else: - for c in [0,1]: - self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) - self.setForeground(c, QtGui.QBrush(QtGui.QColor(50,50,50))) - font = self.font(c) - font.setBold(True) - #font.setPointSize(font.pointSize()+1) - self.setFont(c, font) - self.titleChanged() # sets the size hint for column 0 which is based on the new font - - def addClicked(self): - """Called when "add new" button is clicked - The parameter MUST have an 'addNew' method defined. - """ - self.param.addNew() - - def addChanged(self): - """Called when "add new" combo is changed - The parameter MUST have an 'addNew' method defined. - """ - if self.addWidget.currentIndex() == 0: - return - typ = asUnicode(self.addWidget.currentText()) - self.param.addNew(typ) - self.addWidget.setCurrentIndex(0) - - def treeWidgetChanged(self): - ParameterItem.treeWidgetChanged(self) - tw = self.treeWidget() - if tw is None: - return - self.setFirstColumnSpanned(True) - if self.addItem is not None: - tw.setItemWidget(self.addItem, 0, self.addWidgetBox) - self.addItem.setFirstColumnSpanned(True) - - def addChild(self, child): ## make sure added childs are actually inserted before add btn - if self.addItem is not None: - ParameterItem.insertChild(self, self.childCount()-1, child) - else: - ParameterItem.addChild(self, child) - - def optsChanged(self, param, opts): - ParameterItem.optsChanged(self, param, opts) - - if 'addList' in opts: - self.updateAddList() - - if hasattr(self, 'addWidget'): - if 'enabled' in opts: - self.addWidget.setEnabled(opts['enabled']) - - if 'tip' in opts: - self.addWidget.setToolTip(opts['tip']) - - def updateAddList(self): - self.addWidget.blockSignals(True) - try: - self.addWidget.clear() - self.addWidget.addItem(self.param.opts['addText']) - for t in self.param.opts['addList']: - self.addWidget.addItem(t) - finally: - self.addWidget.blockSignals(False) - - -class GroupParameter(Parameter): - """ - Group parameters are used mainly as a generic parent item that holds (and groups!) a set - of child parameters. - - It also provides a simple mechanism for displaying a button or combo - that can be used to add new parameters to the group. To enable this, the group - must be initialized with the 'addText' option (the text will be displayed on - a button which, when clicked, will cause addNew() to be called). If the 'addList' - option is specified as well, then a dropdown-list of addable items will be displayed - instead of a button. - """ - itemClass = GroupParameterItem - - sigAddNew = QtCore.Signal(object, object) # self, type - - def addNew(self, typ=None): - """ - This method is called when the user has requested to add a new item to the group. - By default, it emits ``sigAddNew(self, typ)``. - """ - self.sigAddNew.emit(self, typ) - - def setAddList(self, vals): - """Change the list of options available for the user to add to the group.""" - self.setOpts(addList=vals) - - -registerParameterType('group', GroupParameter, override=True) - - -class ListParameterItem(WidgetParameterItem): - """ - WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. - - """ - def __init__(self, param, depth): - self.targetValue = None - WidgetParameterItem.__init__(self, param, depth) - - def makeWidget(self): - opts = self.param.opts - t = opts['type'] - w = QtGui.QComboBox() - w.setMaximumHeight(20) ## set to match height of spin box and line edit - w.sigChanged = w.currentIndexChanged - w.value = self.value - w.setValue = self.setValue - self.widget = w ## needs to be set before limits are changed - self.limitsChanged(self.param, self.param.opts['limits']) - if len(self.forward) > 0: - self.setValue(self.param.value()) - return w - - def value(self): - key = asUnicode(self.widget.currentText()) - - return self.forward.get(key, None) - - def setValue(self, val): - self.targetValue = val - if val not in self.reverse[0]: - self.widget.setCurrentIndex(0) - else: - key = self.reverse[1][self.reverse[0].index(val)] - ind = self.widget.findText(key) - self.widget.setCurrentIndex(ind) - - def limitsChanged(self, param, limits): - # set up forward / reverse mappings for name:value - - if len(limits) == 0: - limits = [''] ## Can never have an empty list--there is always at least a singhe blank item. - - self.forward, self.reverse = ListParameter.mapping(limits) - try: - self.widget.blockSignals(True) - val = self.targetValue #asUnicode(self.widget.currentText()) - - self.widget.clear() - for k in self.forward: - self.widget.addItem(k) - if k == val: - self.widget.setCurrentIndex(self.widget.count()-1) - self.updateDisplayLabel() - finally: - self.widget.blockSignals(False) - - -class ListParameter(Parameter): - """Parameter with a list of acceptable values. - - By default, this parameter is represtented by a :class:`ListParameterItem`, - displaying a combo box to select a value from the list. - - In addition to the generic :class:`~pyqtgraph.parametertree.Parameter` - options, this parameter type accepts a ``limits`` argument specifying the - list of allowed values. ``values`` is an alias and may be used instead. - - The values may generally be of any data type, as long as they can be - represented as a string. If the string representation provided is - undesirable, the values may be given as a dictionary mapping the desired - string representation to the value. - """ - - itemClass = ListParameterItem - - def __init__(self, **opts): - self.forward = OrderedDict() ## {name: value, ...} - self.reverse = ([], []) ## ([value, ...], [name, ...]) - - # Parameter uses 'limits' option to define the set of allowed values - if 'values' in opts: - opts['limits'] = opts['values'] - if opts.get('limits', None) is None: - opts['limits'] = [] - Parameter.__init__(self, **opts) - self.setLimits(opts['limits']) - - def setLimits(self, limits): - """Change the list of allowed values.""" - self.forward, self.reverse = self.mapping(limits) - - Parameter.setLimits(self, limits) - if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]: - self.setValue(self.reverse[0][0]) - - @staticmethod - def mapping(limits): - # Return forward and reverse mapping objects given a limit specification - forward = OrderedDict() ## {name: value, ...} - reverse = ([], []) ## ([value, ...], [name, ...]) - if isinstance(limits, dict): - for k, v in limits.items(): - forward[k] = v - reverse[0].append(v) - reverse[1].append(k) - else: - for v in limits: - n = asUnicode(v) - forward[n] = v - reverse[0].append(v) - reverse[1].append(n) - return forward, reverse - -registerParameterType('list', ListParameter, override=True) - - - -class ActionParameterItem(ParameterItem): - """ParameterItem displaying a clickable button.""" - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - self.layoutWidget = QtGui.QWidget() - self.layout = QtGui.QHBoxLayout() - self.layout.setContentsMargins(0, 0, 0, 0) - self.layoutWidget.setLayout(self.layout) - self.button = QtGui.QPushButton() - #self.layout.addSpacing(100) - self.layout.addWidget(self.button) - self.layout.addStretch() - self.button.clicked.connect(self.buttonClicked) - self.titleChanged() - self.optsChanged(self.param, self.param.opts) - - def treeWidgetChanged(self): - ParameterItem.treeWidgetChanged(self) - tree = self.treeWidget() - if tree is None: - return - - self.setFirstColumnSpanned(True) - tree.setItemWidget(self, 0, self.layoutWidget) - - def titleChanged(self): - self.button.setText(self.param.title()) - self.setSizeHint(0, self.button.sizeHint()) - - def optsChanged(self, param, opts): - ParameterItem.optsChanged(self, param, opts) - - if 'enabled' in opts: - self.button.setEnabled(opts['enabled']) - - if 'tip' in opts: - self.button.setToolTip(opts['tip']) - - def buttonClicked(self): - self.param.activate() - -class ActionParameter(Parameter): - """Used for displaying a button within the tree. - - ``sigActivated(self)`` is emitted when the button is clicked. - """ - itemClass = ActionParameterItem - sigActivated = QtCore.Signal(object) - - def activate(self): - self.sigActivated.emit(self) - self.emitStateChanged('activated', None) - -registerParameterType('action', ActionParameter, override=True) - - -class TextParameterItem(WidgetParameterItem): - """ParameterItem displaying a QTextEdit widget.""" - - def makeWidget(self): - self.hideWidget = False - self.asSubItem = True - self.textBox = w = QtGui.QTextEdit() - w.sizeHint = lambda: QtCore.QSize(300, 100) - w.value = lambda: str(w.toPlainText()) - w.setValue = w.setPlainText - w.sigChanged = w.textChanged - return w - - -class TextParameter(Parameter): - """Editable string, displayed as large text box in the tree.""" - itemClass = TextParameterItem - - -registerParameterType('text', TextParameter, override=True) diff --git a/pyqtgraph/pgcollections.py b/pyqtgraph/pgcollections.py deleted file mode 100644 index faadc06..0000000 --- a/pyqtgraph/pgcollections.py +++ /dev/null @@ -1,486 +0,0 @@ -# -*- coding: utf-8 -*- -""" -advancedTypes.py - Basic data structures not included with python -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. - -Includes: - - OrderedDict - Dictionary which preserves the order of its elements - - BiDict, ReverseDict - Bi-directional dictionaries - - ThreadsafeDict, ThreadsafeList - Self-mutexed data structures -""" - -import warnings -warnings.warn( - "None of these are used in pyqtgraph. Will be removed in 0.13", - DeprecationWarning, stacklevel=2 -) - -import threading -import sys -import copy - -from collections import OrderedDict - -try: - from collections.abc import Sequence -except ImportError: - # fallback for python < 3.3 - from collections import Sequence - - -class ReverseDict(dict): - """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: - d = BiDict({'x': 1, 'y': 2}) - d['x'] - 1 - d[[2]] - 'y' - """ - def __init__(self, data=None): - if data is None: - data = {} - self.reverse = {} - for k in data: - self.reverse[data[k]] = k - dict.__init__(self, data) - - def __getitem__(self, item): - if type(item) is list: - return self.reverse[item[0]] - else: - return dict.__getitem__(self, item) - - def __setitem__(self, item, value): - self.reverse[value] = item - dict.__setitem__(self, item, value) - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - -class BiDict(dict): - """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. - This only works if all values and keys are unique.""" - def __init__(self, data=None): - if data is None: - data = {} - dict.__init__(self) - for k in data: - self[data[k]] = k - - def __setitem__(self, item, value): - dict.__setitem__(self, item, value) - dict.__setitem__(self, value, item) - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - -class ThreadsafeDict(dict): - """Extends dict so that getitem, setitem, and contains are all thread-safe. - Also adds lock/unlock functions for extended exclusive operations - Converts all sub-dicts and lists to threadsafe as well. - """ - - def __init__(self, *args, **kwargs): - self.mutex = threading.RLock() - dict.__init__(self, *args, **kwargs) - for k in self: - if type(self[k]) is dict: - self[k] = ThreadsafeDict(self[k]) - - def __getitem__(self, attr): - self.lock() - try: - val = dict.__getitem__(self, attr) - finally: - self.unlock() - return val - - def __setitem__(self, attr, val): - if type(val) is dict: - val = ThreadsafeDict(val) - self.lock() - try: - dict.__setitem__(self, attr, val) - finally: - self.unlock() - - def __contains__(self, attr): - self.lock() - try: - val = dict.__contains__(self, attr) - finally: - self.unlock() - return val - - def __len__(self): - self.lock() - try: - val = dict.__len__(self) - finally: - self.unlock() - return val - - def clear(self): - self.lock() - try: - dict.clear(self) - finally: - self.unlock() - - def lock(self): - self.mutex.acquire() - - def unlock(self): - self.mutex.release() - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - -class ThreadsafeList(list): - """Extends list so that getitem, setitem, and contains are all thread-safe. - Also adds lock/unlock functions for extended exclusive operations - Converts all sub-lists and dicts to threadsafe as well. - """ - - def __init__(self, *args, **kwargs): - self.mutex = threading.RLock() - list.__init__(self, *args, **kwargs) - for k in self: - self[k] = mkThreadsafe(self[k]) - - def __getitem__(self, attr): - self.lock() - try: - val = list.__getitem__(self, attr) - finally: - self.unlock() - return val - - def __setitem__(self, attr, val): - val = makeThreadsafe(val) - self.lock() - try: - list.__setitem__(self, attr, val) - finally: - self.unlock() - - def __contains__(self, attr): - self.lock() - try: - val = list.__contains__(self, attr) - finally: - self.unlock() - return val - - def __len__(self): - self.lock() - try: - val = list.__len__(self) - finally: - self.unlock() - return val - - def lock(self): - self.mutex.acquire() - - def unlock(self): - self.mutex.release() - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - -def makeThreadsafe(obj): - if type(obj) is dict: - return ThreadsafeDict(obj) - elif type(obj) is list: - return ThreadsafeList(obj) - elif type(obj) in [str, int, float, bool, tuple]: - return obj - else: - raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) - - -class Locker(object): - def __init__(self, lock): - self.lock = lock - self.lock.acquire() - def __del__(self): - try: - self.lock.release() - except: - pass - -class CaselessDict(OrderedDict): - """Case-insensitive dict. Values can be set and retrieved using keys of any case. - Note that when iterating, the original case is returned for each key.""" - def __init__(self, *args): - OrderedDict.__init__(self, {}) ## requirement for the empty {} here seems to be a python bug? - self.keyMap = OrderedDict([(k.lower(), k) for k in OrderedDict.keys(self)]) - if len(args) == 0: - return - elif len(args) == 1 and isinstance(args[0], dict): - for k in args[0]: - self[k] = args[0][k] - else: - raise Exception("CaselessDict may only be instantiated with a single dict.") - - #def keys(self): - #return self.keyMap.values() - - def __setitem__(self, key, val): - kl = key.lower() - if kl in self.keyMap: - OrderedDict.__setitem__(self, self.keyMap[kl], val) - else: - OrderedDict.__setitem__(self, key, val) - self.keyMap[kl] = key - - def __getitem__(self, key): - kl = key.lower() - if kl not in self.keyMap: - raise KeyError(key) - return OrderedDict.__getitem__(self, self.keyMap[kl]) - - def __contains__(self, key): - return key.lower() in self.keyMap - - def update(self, d): - for k, v in d.items(): - self[k] = v - - def copy(self): - return CaselessDict(OrderedDict.copy(self)) - - def __delitem__(self, key): - kl = key.lower() - if kl not in self.keyMap: - raise KeyError(key) - OrderedDict.__delitem__(self, self.keyMap[kl]) - del self.keyMap[kl] - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - def clear(self): - OrderedDict.clear(self) - self.keyMap.clear() - - - -class ProtectedDict(dict): - """ - A class allowing read-only 'view' of a dict. - The object can be treated like a normal dict, but will never modify the original dict it points to. - Any values accessed from the dict will also be read-only. - """ - def __init__(self, data): - self._data_ = data - - ## List of methods to directly wrap from _data_ - wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] - - ## List of methods to disable - disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - def error(self, *args, **kargs): - raise Exception("Can not modify read-only list.") - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - ## Disable any methods that could change data in the list - for methodName in disableMethods: - locals()[methodName] = error - - - ## Add a few extra methods. - def copy(self): - raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") - - def itervalues(self): - for v in self._data_.values(): - yield protect(v) - - def iteritems(self): - for k, v in self._data_.items(): - yield (k, protect(v)) - - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - - -class ProtectedList(Sequence): - """ - A class allowing read-only 'view' of a list or dict. - The object can be treated like a normal list, but will never modify the original list it points to. - Any values accessed from the list will also be read-only. - - Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. - However, doing this causes tuple(obj) to return unprotected results (importantly, this means - unpacking into function arguments will also fail) - """ - def __init__(self, data): - self._data_ = data - #self.__mro__ = (ProtectedList, object) - - ## List of methods to directly wrap from _data_ - wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] - - ## List of methods to disable - disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - def error(self, *args, **kargs): - raise Exception("Can not modify read-only list.") - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - ## Disable any methods that could change data in the list - for methodName in disableMethods: - locals()[methodName] = error - - - ## Add a few extra methods. - def __iter__(self): - for item in self._data_: - yield protect(item) - - - def __add__(self, op): - if isinstance(op, ProtectedList): - return protect(self._data_.__add__(op._data_)) - elif isinstance(op, list): - return protect(self._data_.__add__(op)) - else: - raise TypeError("Argument must be a list.") - - def __radd__(self, op): - if isinstance(op, ProtectedList): - return protect(op._data_.__add__(self._data_)) - elif isinstance(op, list): - return protect(op.__add__(self._data_)) - else: - raise TypeError("Argument must be a list.") - - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - def poop(self): - raise Exception("This is a list. It does not poop.") - - -class ProtectedTuple(Sequence): - """ - A class allowing read-only 'view' of a tuple. - The object can be treated like a normal tuple, but its contents will be returned as protected objects. - - Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. - However, doing this causes tuple(obj) to return unprotected results (importantly, this means - unpacking into function arguments will also fail) - """ - def __init__(self, data): - self._data_ = data - - ## List of methods to directly wrap from _data_ - wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - - ## Add a few extra methods. - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - - -def protect(obj): - if isinstance(obj, dict): - return ProtectedDict(obj) - elif isinstance(obj, list): - return ProtectedList(obj) - elif isinstance(obj, tuple): - return ProtectedTuple(obj) - else: - return obj - - -if __name__ == '__main__': - d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} - dp = protect(d) - - l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] - lp = protect(l) - - t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) - tp = protect(t) diff --git a/pyqtgraph/ptime.py b/pyqtgraph/ptime.py deleted file mode 100644 index efd2be3..0000000 --- a/pyqtgraph/ptime.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ptime.py - Precision time function made os-independent (should have been taken care of by python) -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - - -import sys - -if sys.version_info[0] < 3: - from time import clock - from time import time as system_time -else: - from time import perf_counter as clock - from time import time as system_time - -START_TIME = None -time = None - -def winTime(): - """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent).""" - return clock() + START_TIME - -def unixTime(): - """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent).""" - return system_time() - -if sys.platform.startswith('win'): - cstart = clock() ### Required to start the clock in windows - START_TIME = system_time() - cstart - - time = winTime -else: - time = unixTime - diff --git a/pyqtgraph/python2_3.py b/pyqtgraph/python2_3.py deleted file mode 100644 index 952b49b..0000000 --- a/pyqtgraph/python2_3.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Helper functions that smooth out the differences between python 2 and 3. -""" -import sys - -def asUnicode(x): - if sys.version_info[0] == 2: - if isinstance(x, unicode): - return x - elif isinstance(x, str): - return x.decode('UTF-8') - else: - return unicode(x) - else: - return str(x) - - -if sys.version_info[0] == 3: - basestring = str - xrange = range -else: - import __builtin__ - basestring = __builtin__.basestring - xrange = __builtin__.xrange diff --git a/pyqtgraph/reload.py b/pyqtgraph/reload.py deleted file mode 100644 index 05ef8f0..0000000 --- a/pyqtgraph/reload.py +++ /dev/null @@ -1,604 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Magic Reload Library -Luke Campagnola 2010 - -Python reload function that actually works (the way you expect it to) - - No re-importing necessary - - Modules can be reloaded in any order - - Replaces functions and methods with their updated code - - Changes instances to use updated classes - - Automatically decides which modules to update by comparing file modification times - -Does NOT: - - re-initialize exting instances, even if __init__ changes - - update references to any module-level objects - ie, this does not reload correctly: - from module import someObject - print someObject - ..but you can use this instead: (this works even for the builtin reload) - import module - print module.someObject -""" - -from __future__ import print_function -import inspect, os, sys, gc, traceback, types -from .debug import printExc -try: - from importlib import reload as orig_reload -except ImportError: - orig_reload = reload - - -py3 = sys.version_info >= (3,) - - -def reloadAll(prefix=None, debug=False): - """Automatically reload all modules whose __file__ begins with *prefix*. - - Skips reload if the file has not been updated (if .pyc is newer than .py) - If *prefix* is None, then all loaded modules are checked. - - Returns a dictionary {moduleName: (reloaded, reason)} describing actions taken - for each module. - """ - failed = [] - changed = [] - ret = {} - for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload - if not inspect.ismodule(mod): - ret[modName] = (False, 'not a module') - continue - if modName == '__main__': - ret[modName] = (False, 'ignored __main__') - continue - - # Ignore modules without a __file__ that is .py or .pyc - if getattr(mod, '__file__', None) is None: - ret[modName] = (False, 'module has no __file__') - continue - - if os.path.splitext(mod.__file__)[1] not in ['.py', '.pyc']: - ret[modName] = (False, '%s not a .py/pyc file' % str(mod.__file__)) - continue - - # Ignore if the file name does not start with prefix - if prefix is not None and mod.__file__[:len(prefix)] != prefix: - ret[modName] = (False, 'file %s not in prefix %s' % (mod.__file__, prefix)) - continue - - py = os.path.splitext(mod.__file__)[0] + '.py' - if py in changed: - # already processed this module - continue - if not os.path.isfile(py): - # skip modules that lie about their __file__ - ret[modName] = (False, '.py does not exist: %s' % py) - continue - - # if source file is newer than cache file, then it needs to be reloaded. - pyc = getattr(mod, '__cached__', py + 'c') - if not os.path.isfile(pyc): - ret[modName] = (False, 'code has no pyc file to compare') - continue - - if os.stat(pyc).st_mtime > os.stat(py).st_mtime: - ret[modName] = (False, 'code has not changed since compile') - continue - - # keep track of which modules have changed to ensure that duplicate-import modules get reloaded. - changed.append(py) - - try: - reload(mod, debug=debug) - ret[modName] = (True, None) - except Exception as exc: - printExc("Error while reloading module %s, skipping\n" % mod) - failed.append(mod.__name__) - ret[modName] = (False, 'reload failed: %s' % traceback.format_exception_only(type(exc), exc)) - - if len(failed) > 0: - raise Exception("Some modules failed to reload: %s" % ', '.join(failed)) - - return ret - - -def reload(module, debug=False, lists=False, dicts=False): - """Replacement for the builtin reload function: - - Reloads the module as usual - - Updates all old functions and class methods to use the new code - - Updates all instances of each modified class to use the new class - - Can update lists and dicts, but this is disabled by default - - Requires that class and function names have not changed - """ - if debug: - print("Reloading %s" % str(module)) - - ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison - oldDict = module.__dict__.copy() - orig_reload(module) - newDict = module.__dict__ - - ## Allow modules access to the old dictionary after they reload - if hasattr(module, '__reload__'): - module.__reload__(oldDict) - - ## compare old and new elements from each dict; update where appropriate - for k in oldDict: - old = oldDict[k] - new = newDict.get(k, None) - if old is new or new is None: - continue - - if inspect.isclass(old): - if debug: - print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) - updateClass(old, new, debug) - # don't put this inside updateClass because it is reentrant. - new.__previous_reload_version__ = old - - elif inspect.isfunction(old): - depth = updateFunction(old, new, debug) - if debug: - extra = "" - if depth > 0: - extra = " (and %d previous versions)" % depth - print(" Updating function %s.%s%s" % (module.__name__, k, extra)) - elif lists and isinstance(old, list): - l = old.len() - old.extend(new) - for i in range(l): - old.pop(0) - elif dicts and isinstance(old, dict): - old.update(new) - for k in old: - if k not in new: - del old[k] - - - -## For functions: -## 1) update the code and defaults to new versions. -## 2) keep a reference to the previous version so ALL versions get updated for every reload -def updateFunction(old, new, debug, depth=0, visited=None): - #if debug and depth > 0: - #print " -> also updating previous version", old, " -> ", new - - old.__code__ = new.__code__ - old.__defaults__ = new.__defaults__ - if hasattr(old, '__kwdefaults'): - old.__kwdefaults__ = new.__kwdefaults__ - old.__doc__ = new.__doc__ - - if visited is None: - visited = [] - if old in visited: - return - visited.append(old) - - ## finally, update any previous versions still hanging around.. - if hasattr(old, '__previous_reload_version__'): - maxDepth = updateFunction(old.__previous_reload_version__, new, debug, depth=depth+1, visited=visited) - else: - maxDepth = depth - - ## We need to keep a pointer to the previous version so we remember to update BOTH - ## when the next reload comes around. - if depth == 0: - new.__previous_reload_version__ = old - return maxDepth - - - -## For classes: -## 1) find all instances of the old class and set instance.__class__ to the new class -## 2) update all old class methods to use code from the new class methods - - -def updateClass(old, new, debug): - ## Track town all instances and subclasses of old - refs = gc.get_referrers(old) - for ref in refs: - try: - if isinstance(ref, old) and ref.__class__ is old: - ref.__class__ = new - if debug: - print(" Changed class for %s" % safeStr(ref)) - elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: - ind = ref.__bases__.index(old) - - ## Does not work: - #ref.__bases__ = ref.__bases__[:ind] + (new,) + ref.__bases__[ind+1:] - ## reason: Even though we change the code on methods, they remain bound - ## to their old classes (changing im_class is not allowed). Instead, - ## we have to update the __bases__ such that this class will be allowed - ## as an argument to older methods. - - ## This seems to work. Is there any reason not to? - ## Note that every time we reload, the class hierarchy becomes more complex. - ## (and I presume this may slow things down?) - newBases = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] - try: - ref.__bases__ = newBases - except TypeError: - print(" Error setting bases for class %s" % ref) - print(" old bases: %s" % repr(ref.__bases__)) - print(" new bases: %s" % repr(newBases)) - raise - if debug: - print(" Changed superclass for %s" % safeStr(ref)) - #else: - #if debug: - #print " Ignoring reference", type(ref) - except Exception: - print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) - raise - - ## update all class methods to use new code. - ## Generally this is not needed since instances already know about the new class, - ## but it fixes a few specific cases (pyqt signals, for one) - for attr in dir(old): - oa = getattr(old, attr) - if (py3 and inspect.isfunction(oa)) or inspect.ismethod(oa): - # note python2 has unbound methods, whereas python3 just uses plain functions - try: - na = getattr(new, attr) - except AttributeError: - if debug: - print(" Skipping method update for %s; new class does not have this attribute" % attr) - continue - - ofunc = getattr(oa, '__func__', oa) # in py2 we have to get the __func__ from unbound method, - nfunc = getattr(na, '__func__', na) # in py3 the attribute IS the function - - if ofunc is not nfunc: - depth = updateFunction(ofunc, nfunc, debug) - if not hasattr(nfunc, '__previous_reload_method__'): - nfunc.__previous_reload_method__ = oa # important for managing signal connection - #oa.__class__ = new ## bind old method to new class ## not allowed - if debug: - extra = "" - if depth > 0: - extra = " (and %d previous versions)" % depth - print(" Updating method %s%s" % (attr, extra)) - - ## And copy in new functions that didn't exist previously - for attr in dir(new): - if attr == '__previous_reload_version__': - continue - if not hasattr(old, attr): - if debug: - print(" Adding missing attribute %s" % attr) - setattr(old, attr, getattr(new, attr)) - - ## finally, update any previous versions still hanging around.. - if hasattr(old, '__previous_reload_version__'): - updateClass(old.__previous_reload_version__, new, debug) - - -## It is possible to build classes for which str(obj) just causes an exception. -## Avoid thusly: -def safeStr(obj): - try: - s = str(obj) - except Exception: - try: - s = repr(obj) - except Exception: - s = "" % (safeStr(type(obj)), id(obj)) - return s - - -def getPreviousVersion(obj): - """Return the previous version of *obj*, or None if this object has not - been reloaded. - """ - if isinstance(obj, type) or inspect.isfunction(obj): - return getattr(obj, '__previous_reload_version__', None) - elif inspect.ismethod(obj): - if obj.__self__ is None: - # unbound method - return getattr(obj.__func__, '__previous_reload_method__', None) - else: - oldmethod = getattr(obj.__func__, '__previous_reload_method__', None) - if oldmethod is None: - return None - self = obj.__self__ - oldfunc = getattr(oldmethod, '__func__', oldmethod) - if hasattr(oldmethod, 'im_class'): - # python 2 - cls = oldmethod.im_class - return types.MethodType(oldfunc, self, cls) - else: - # python 3 - return types.MethodType(oldfunc, self) - - - -## Tests: -# write modules to disk, import, then re-write and run again -if __name__ == '__main__': - doQtTest = True - try: - from PyQt4 import QtCore - if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal - #app = QtGui.QApplication([]) - class Btn(QtCore.QObject): - sig = QtCore.Signal() - def emit(self): - self.sig.emit() - btn = Btn() - except: - raise - print("Error; skipping Qt tests") - doQtTest = False - - - - import os - if not os.path.isdir('test1'): - os.mkdir('test1') - with open('test1/__init__.py', 'w'): - pass - modFile1 = "test1/test1.py" - modCode1 = """ -import sys -class A(object): - def __init__(self, msg): - object.__init__(self) - self.msg = msg - def fn(self, pfx = ""): - print(pfx+"A class: %%s %%s" %% (str(self.__class__), str(id(self.__class__)))) - print(pfx+" %%s: %d" %% self.msg) - -class B(A): - def fn(self, pfx=""): - print(pfx+"B class:", self.__class__, id(self.__class__)) - print(pfx+" %%s: %d" %% self.msg) - print(pfx+" calling superclass.. (%%s)" %% id(A) ) - A.fn(self, " ") -""" - - modFile2 = "test2.py" - modCode2 = """ -from test1.test1 import A -from test1.test1 import B - -a1 = A("ax1") -b1 = B("bx1") -class C(A): - def __init__(self, msg): - #print "| C init:" - #print "| C.__bases__ = ", map(id, C.__bases__) - #print "| A:", id(A) - #print "| A.__init__ = ", id(A.__init__.im_func), id(A.__init__.im_func.__code__), id(A.__init__.im_class) - A.__init__(self, msg + "(init from C)") - -def fn(): - print("fn: %s") -""" - - with open(modFile1, 'w') as f: - f.write(modCode1 % (1, 1)) - with open(modFile2, 'w') as f: - f.write(modCode2 % ("message 1", )) - import test1.test1 as test1 - import test2 - print("Test 1 originals:") - A1 = test1.A - B1 = test1.B - a1 = test1.A("a1") - b1 = test1.B("b1") - a1.fn() - b1.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - - from test2 import fn, C - - if doQtTest: - print("Button test before:") - btn.sig.connect(fn) - btn.sig.connect(a1.fn) - btn.emit() - #btn.sig.emit() - print("") - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - - print("Test2 before reload:") - - fn() - oldfn = fn - test2.a1.fn() - test2.b1.fn() - c1 = test2.C('c1') - c1.fn() - - os.remove(modFile1+'c') - with open(modFile1, 'w') as f: - f.write(modCode1 %(2, 2)) - print("\n----RELOAD test1-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - - print("Subclass test:") - c2 = test2.C('c2') - c2.fn() - - - os.remove(modFile2+'c') - with open(modFile2, 'w') as f: - f.write(modCode2 % ("message 2", )) - print("\n----RELOAD test2-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - if doQtTest: - print("Button test after:") - btn.emit() - #btn.sig.emit() - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - print("Test2 after reload:") - fn() - test2.a1.fn() - test2.b1.fn() - - print("\n==> Test 1 Old instances:") - a1.fn() - b1.fn() - c1.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - print("\n==> Test 1 New instances:") - a2 = test1.A("a2") - b2 = test1.B("b2") - a2.fn() - b2.fn() - c2 = test2.C('c2') - c2.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - - - - os.remove(modFile1+'c') - os.remove(modFile2+'c') - with open(modFile1, 'w') as f: - f.write(modCode1 % (3, 3)) - with open(modFile2, 'w') as f: - f.write(modCode2 % ("message 3", )) - - print("\n----RELOAD-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - if doQtTest: - print("Button test after:") - btn.emit() - #btn.sig.emit() - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - print("Test2 after reload:") - fn() - test2.a1.fn() - test2.b1.fn() - - print("\n==> Test 1 Old instances:") - a1.fn() - b1.fn() - print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) - - print("\n==> Test 1 New instances:") - a2 = test1.A("a2") - b2 = test1.B("b2") - a2.fn() - b2.fn() - print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) - - - os.remove(modFile1) - os.remove(modFile2) - os.remove(modFile1+'c') - os.remove(modFile2+'c') - os.system('rm -r test1') - - - - - - - - -# -# Failure graveyard ahead: -# - - -"""Reload Importer: -Hooks into import system to -1) keep a record of module dependencies as they are imported -2) make sure modules are always reloaded in correct order -3) update old classes and functions to use reloaded code""" - -#import imp, sys - -## python's import hook mechanism doesn't work since we need to be -## informed every time there is an import statement, not just for new imports -#class ReloadImporter: - #def __init__(self): - #self.depth = 0 - - #def find_module(self, name, path): - #print " "*self.depth + "find: ", name, path - ##if name == 'PyQt4' and path is None: - ##print "PyQt4 -> PySide" - ##self.modData = imp.find_module('PySide') - ##return self - ##return None ## return none to allow the import to proceed normally; return self to intercept with load_module - #self.modData = imp.find_module(name, path) - #self.depth += 1 - ##sys.path_importer_cache = {} - #return self - - #def load_module(self, name): - #mod = imp.load_module(name, *self.modData) - #self.depth -= 1 - #print " "*self.depth + "load: ", name - #return mod - -#def pathHook(path): - #print "path hook:", path - #raise ImportError -#sys.path_hooks.append(pathHook) - -#sys.meta_path.append(ReloadImporter()) - - -### replace __import__ with a wrapper that tracks module dependencies -#modDeps = {} -#reloadModule = None -#origImport = __builtins__.__import__ -#def _import(name, globals=None, locals=None, fromlist=None, level=-1, stack=[]): - ### Note that stack behaves as a static variable. - ##print " "*len(importStack) + "import %s" % args[0] - #stack.append(set()) - #mod = origImport(name, globals, locals, fromlist, level) - #deps = stack.pop() - #if len(stack) > 0: - #stack[-1].add(mod) - #elif reloadModule is not None: ## If this is the top level import AND we're inside a module reload - #modDeps[reloadModule].add(mod) - - #if mod in modDeps: - #modDeps[mod] |= deps - #else: - #modDeps[mod] = deps - - - #return mod - -#__builtins__.__import__ = _import - -### replace -#origReload = __builtins__.reload -#def _reload(mod): - #reloadModule = mod - #ret = origReload(mod) - #reloadModule = None - #return ret -#__builtins__.reload = _reload - - -#def reload(mod, visited=None): - #if visited is None: - #visited = set() - #if mod in visited: - #return - #visited.add(mod) - #for dep in modDeps.get(mod, []): - #reload(dep, visited) - #__builtins__.reload(mod) diff --git a/pyqtgraph/tests/__init__.py b/pyqtgraph/tests/__init__.py deleted file mode 100644 index 393bd3c..0000000 --- a/pyqtgraph/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .image_testing import assertImageApproved, TransposedImageItem -from .ui_testing import resizeWindow, mousePress, mouseMove, mouseRelease, mouseDrag, mouseClick diff --git a/pyqtgraph/tests/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/tests/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 1d62f2b..0000000 Binary files a/pyqtgraph/tests/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/image_testing.cpython-36.pyc b/pyqtgraph/tests/__pycache__/image_testing.cpython-36.pyc deleted file mode 100644 index a359249..0000000 Binary files a/pyqtgraph/tests/__pycache__/image_testing.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_Vector.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_Vector.cpython-36.pyc deleted file mode 100644 index 4611c99..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_Vector.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_configparser.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_configparser.cpython-36.pyc deleted file mode 100644 index 3ebc0db..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_configparser.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_exit_crash.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_exit_crash.cpython-36.pyc deleted file mode 100644 index 085d48e..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_exit_crash.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_functions.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_functions.cpython-36.pyc deleted file mode 100644 index 5e9da79..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_functions.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_qt.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_qt.cpython-36.pyc deleted file mode 100644 index 34ed52f..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_qt.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_ref_cycles.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_ref_cycles.cpython-36.pyc deleted file mode 100644 index 674ac3b..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_ref_cycles.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_reload.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_reload.cpython-36.pyc deleted file mode 100644 index 4920170..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_reload.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_signalproxy.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_signalproxy.cpython-36.pyc deleted file mode 100644 index 12d86a1..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_signalproxy.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_srttransform3d.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_srttransform3d.cpython-36.pyc deleted file mode 100644 index 42df9f3..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_srttransform3d.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/test_stability.cpython-36.pyc b/pyqtgraph/tests/__pycache__/test_stability.cpython-36.pyc deleted file mode 100644 index 24c774b..0000000 Binary files a/pyqtgraph/tests/__pycache__/test_stability.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/__pycache__/ui_testing.cpython-36.pyc b/pyqtgraph/tests/__pycache__/ui_testing.cpython-36.pyc deleted file mode 100644 index 82cf4cb..0000000 Binary files a/pyqtgraph/tests/__pycache__/ui_testing.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/tests/image_testing.py b/pyqtgraph/tests/image_testing.py deleted file mode 100644 index bfb8dc0..0000000 --- a/pyqtgraph/tests/image_testing.py +++ /dev/null @@ -1,657 +0,0 @@ -# Image-based testing borrowed from vispy - -""" -Procedure for unit-testing with images: - -1. Run unit tests at least once; this initializes a git clone of - pyqtgraph/test-data in ~/.pyqtgraph. - -2. Run individual test scripts with the PYQTGRAPH_AUDIT environment variable set: - - $ PYQTGRAPH_AUDIT=1 python pyqtgraph/graphicsItems/tests/test_PlotCurveItem.py - - Any failing tests will display the test results, standard image, and the - differences between the two. If the test result is bad, then press (f)ail. - If the test result is good, then press (p)ass and the new image will be - saved to the test-data directory. - - To check all test results regardless of whether the test failed, set the - environment variable PYQTGRAPH_AUDIT_ALL=1. - -3. After adding or changing test images, create a new commit: - - $ cd ~/.pyqtgraph/test-data - $ git add ... - $ git commit -a - -4. Look up the most recent tag name from the `testDataTag` global variable - below. Increment the tag name by 1 and create a new tag in the test-data - repository: - - $ git tag test-data-NNN - $ git push --tags origin master - - This tag is used to ensure that each pyqtgraph commit is linked to a specific - commit in the test-data repository. This makes it possible to push new - commits to the test-data repository without interfering with existing - tests, and also allows unit tests to continue working on older pyqtgraph - versions. - -""" - - -# This is the name of a tag in the test-data repository that this version of -# pyqtgraph should be tested against. When adding or changing test images, -# create and push a new tag and update this variable. To test locally, begin -# by creating the tag in your ~/.pyqtgraph/test-data repository. -testDataTag = 'test-data-8' - - -import time -import os -import sys -import inspect -import base64 -import subprocess as sp -import numpy as np - -if sys.version[0] >= '3': - import http.client as httplib - import urllib.parse as urllib -else: - import httplib - import urllib -from ..Qt import QtGui, QtCore, QtTest, QT_LIB -from .. import functions as fn -from .. import GraphicsLayoutWidget -from .. import ImageItem, TextItem - - -tester = None - -# Convenient stamp used for ensuring image orientation is correct -axisImg = [ - " 1 1 1 ", - " 1 1 1 1 1 1 ", - " 1 1 1 1 1 1 1 1 1 1", - " 1 1 1 1 1 ", - " 1 1 1 1 1 1 ", - " 1 1 ", - " 1 1 ", - " 1 ", - " ", - " 1 ", - " 1 ", - " 1 ", - "1 1 1 1 1 ", - "1 1 1 1 1 ", - " 1 1 1 ", - " 1 1 1 ", - " 1 ", - " 1 ", -] -axisImg = np.array([map(int, row[::2].replace(' ', '0')) for row in axisImg]) - - - -def getTester(): - global tester - if tester is None: - tester = ImageTester() - return tester - - -def assertImageApproved(image, standardFile, message=None, **kwargs): - """Check that an image test result matches a pre-approved standard. - - If the result does not match, then the user can optionally invoke a GUI - to compare the images and decide whether to fail the test or save the new - image as the standard. - - This function will automatically clone the test-data repository into - ~/.pyqtgraph/test-data. However, it is up to the user to ensure this repository - is kept up to date and to commit/push new images after they are saved. - - Run the test with the environment variable PYQTGRAPH_AUDIT=1 to bring up - the auditing GUI. - - Parameters - ---------- - image : (h, w, 4) ndarray - standardFile : str - The name of the approved test image to check against. This file name - is relative to the root of the pyqtgraph test-data repository and will - be automatically fetched. - message : str - A string description of the image. It is recommended to describe - specific features that an auditor should look for when deciding whether - to fail a test. - - Extra keyword arguments are used to set the thresholds for automatic image - comparison (see ``assertImageMatch()``). - """ - if isinstance(image, QtGui.QWidget): - w = image - - # just to be sure the widget size is correct (new window may be resized): - QtGui.QApplication.processEvents() - - graphstate = scenegraphState(w, standardFile) - qimg = QtGui.QImage(w.size(), QtGui.QImage.Format.Format_ARGB32) - qimg.fill(QtCore.Qt.GlobalColor.transparent) - painter = QtGui.QPainter(qimg) - w.render(painter) - painter.end() - - image = fn.imageToArray(qimg, copy=False, transpose=False) - - # transpose BGRA to RGBA - image = image[..., [2, 1, 0, 3]] - - if message is None: - code = inspect.currentframe().f_back.f_code - message = "%s::%s" % (code.co_filename, code.co_name) - - # Make sure we have a test data repo available, possibly invoking git - dataPath = getTestDataRepo() - - # Read the standard image if it exists - stdFileName = os.path.join(dataPath, standardFile + '.png') - if not os.path.isfile(stdFileName): - stdImage = None - else: - pxm = QtGui.QPixmap() - pxm.load(stdFileName) - stdImage = fn.imageToArray(pxm.toImage(), copy=True, transpose=False) - - # If the test image does not match, then we go to audit if requested. - try: - if stdImage is None: - raise Exception("No reference image saved for this test.") - if image.shape[2] != stdImage.shape[2]: - raise Exception("Test result has different channel count than standard image" - "(%d vs %d)" % (image.shape[2], stdImage.shape[2])) - if image.shape != stdImage.shape: - # Allow im1 to be an integer multiple larger than im2 to account - # for high-resolution displays - ims1 = np.array(image.shape).astype(float) - ims2 = np.array(stdImage.shape).astype(float) - sr = ims1 / ims2 if ims1[0] > ims2[0] else ims2 / ims1 - if (sr[0] != sr[1] or not np.allclose(sr, np.round(sr)) or - sr[0] < 1): - raise TypeError("Test result shape %s is not an integer factor" - " different than standard image shape %s." % - (ims1, ims2)) - sr = np.round(sr).astype(int) - image = fn.downsample(image, sr[0], axis=(0, 1)).astype(image.dtype) - - assertImageMatch(image, stdImage, **kwargs) - - if bool(os.getenv('PYQTGRAPH_PRINT_TEST_STATE', False)): - print(graphstate) - - if os.getenv('PYQTGRAPH_AUDIT_ALL') == '1': - raise Exception("Image test passed, but auditing due to PYQTGRAPH_AUDIT_ALL evnironment variable.") - except Exception: - - if stdFileName in gitStatus(dataPath): - print("\n\nWARNING: unit test failed against modified standard " - "image %s.\nTo revert this file, run `cd %s; git checkout " - "%s`\n" % (stdFileName, dataPath, standardFile)) - if os.getenv('PYQTGRAPH_AUDIT') == '1' or os.getenv('PYQTGRAPH_AUDIT_ALL') == '1': - sys.excepthook(*sys.exc_info()) - getTester().test(image, stdImage, message) - stdPath = os.path.dirname(stdFileName) - print('Saving new standard image to "%s"' % stdFileName) - if not os.path.isdir(stdPath): - os.makedirs(stdPath) - img = fn.makeQImage(image, alpha=True, transpose=False) - img.save(stdFileName) - else: - if stdImage is None: - raise Exception("Test standard %s does not exist. Set " - "PYQTGRAPH_AUDIT=1 to add this image." % stdFileName) - else: - if os.getenv('TRAVIS') is not None: - saveFailedTest(image, stdImage, standardFile, upload=True) - elif os.getenv('CI') is not None: - standardFile = os.path.join(os.getenv("SCREENSHOT_DIR", "screenshots"), standardFile) - saveFailedTest(image, stdImage, standardFile) - print(graphstate) - raise - - -def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., - pxCount=-1, maxPxDiff=None, avgPxDiff=None, - imgDiff=None): - """Check that two images match. - - Images that differ in shape or dtype will fail unconditionally. - Further tests for similarity depend on the arguments supplied. - - By default, images may have no pixels that gave a value difference greater - than 50. - - Parameters - ---------- - im1 : (h, w, 4) ndarray - Test output image - im2 : (h, w, 4) ndarray - Test standard image - minCorr : float or None - Minimum allowed correlation coefficient between corresponding image - values (see numpy.corrcoef) - pxThreshold : float - Minimum value difference at which two pixels are considered different - pxCount : int or None - Maximum number of pixels that may differ. Default is 0 for Qt4 and - 1% of image size for Qt5. - maxPxDiff : float or None - Maximum allowed difference between pixels - avgPxDiff : float or None - Average allowed difference between pixels - imgDiff : float or None - Maximum allowed summed difference between images - - """ - assert im1.ndim == 3 - assert im1.shape[2] == 4 - assert im1.dtype == im2.dtype - - if pxCount == -1: - if QT_LIB in {'PyQt5', 'PySide2', 'PySide6', 'PyQt6'}: - # Qt5 generates slightly different results; relax the tolerance - # until test images are updated. - pxCount = int(im1.shape[0] * im1.shape[1] * 0.01) - else: - pxCount = 0 - - diff = im1.astype(float) - im2.astype(float) - if imgDiff is not None: - assert np.abs(diff).sum() <= imgDiff - - pxdiff = diff.max(axis=2) # largest value difference per pixel - mask = np.abs(pxdiff) >= pxThreshold - if pxCount is not None: - assert mask.sum() <= pxCount - - maskedDiff = diff[mask] - if maxPxDiff is not None and maskedDiff.size > 0: - assert maskedDiff.max() <= maxPxDiff - if avgPxDiff is not None and maskedDiff.size > 0: - assert maskedDiff.mean() <= avgPxDiff - - if minCorr is not None: - with np.errstate(invalid='ignore'): - corr = np.corrcoef(im1.ravel(), im2.ravel())[0, 1] - assert corr >= minCorr - - -def saveFailedTest(data, expect, filename, upload=False): - """Upload failed test images to web server to allow CI test debugging. - """ - # concatenate data, expect, and diff into a single image - ds = data.shape - es = expect.shape - - shape = (max(ds[0], es[0]) + 4, ds[1] + es[1] + 8 + max(ds[1], es[1]), 4) - img = np.empty(shape, dtype=np.ubyte) - img[..., :3] = 100 - img[..., 3] = 255 - - img[2:2+ds[0], 2:2+ds[1], :ds[2]] = data - img[2:2+es[0], ds[1]+4:ds[1]+4+es[1], :es[2]] = expect - - diff = makeDiffImage(data, expect) - img[2:2+diff.shape[0], -diff.shape[1]-2:-2] = diff - - png = makePng(img) - directory = os.path.dirname(filename) - if not os.path.isdir(directory): - os.makedirs(directory) - with open(filename + ".png", "wb") as png_file: - png_file.write(png) - print("\nImage comparison failed. Test result: %s %s Expected result: " - "%s %s" % (data.shape, data.dtype, expect.shape, expect.dtype)) - if upload: - uploadFailedTest(filename, png) - - -def uploadFailedTest(filename, png): - commit = runSubprocess(['git', 'rev-parse', 'HEAD']) - name = filename.split(os.path.sep) - name.insert(-1, commit.strip()) - filename = os.path.sep.join(name) - - host = 'data.pyqtgraph.org' - conn = httplib.HTTPConnection(host) - req = urllib.urlencode({'name': filename, - 'data': base64.b64encode(png)}) - conn.request('POST', '/upload.py', req) - response = conn.getresponse().read() - conn.close() - - print("Uploaded to: \nhttp://%s/data/%s" % (host, filename)) - if not response.startswith(b'OK'): - print("WARNING: Error uploading data to %s" % host) - print(response) - - -def makePng(img): - """Given an array like (H, W, 4), return a PNG-encoded byte string. - """ - io = QtCore.QBuffer() - qim = fn.makeQImage(img.transpose(1, 0, 2), alpha=False) - qim.save(io, 'PNG') - png = bytes(io.data().data()) - return png - - -def makeDiffImage(im1, im2): - """Return image array showing the differences between im1 and im2. - - Handles images of different shape. Alpha channels are not compared. - """ - ds = im1.shape - es = im2.shape - - diff = np.empty((max(ds[0], es[0]), max(ds[1], es[1]), 4), dtype=int) - diff[..., :3] = 128 - diff[..., 3] = 255 - diff[:ds[0], :ds[1], :min(ds[2], 3)] += im1[..., :3] - diff[:es[0], :es[1], :min(es[2], 3)] -= im2[..., :3] - diff = np.clip(diff, 0, 255).astype(np.ubyte) - return diff - - -class ImageTester(QtGui.QWidget): - """Graphical interface for auditing image comparison tests. - """ - def __init__(self): - self.lastKey = None - - QtGui.QWidget.__init__(self) - self.resize(1200, 800) - #self.showFullScreen() - - self.layout = QtGui.QGridLayout() - self.setLayout(self.layout) - - self.view = GraphicsLayoutWidget() - self.layout.addWidget(self.view, 0, 0, 1, 2) - - self.label = QtGui.QLabel() - self.layout.addWidget(self.label, 1, 0, 1, 2) - self.label.setWordWrap(True) - font = QtGui.QFont("monospace", 14, QtGui.QFont.Bold) - self.label.setFont(font) - - self.passBtn = QtGui.QPushButton('Pass') - self.failBtn = QtGui.QPushButton('Fail') - self.layout.addWidget(self.passBtn, 2, 0) - self.layout.addWidget(self.failBtn, 2, 1) - self.passBtn.clicked.connect(self.passTest) - self.failBtn.clicked.connect(self.failTest) - - self.views = (self.view.addViewBox(row=0, col=0), - self.view.addViewBox(row=0, col=1), - self.view.addViewBox(row=0, col=2)) - labelText = ['test output', 'standard', 'diff'] - for i, v in enumerate(self.views): - v.setAspectLocked(1) - v.invertY() - v.image = ImageItem(axisOrder='row-major') - v.image.setAutoDownsample(True) - v.addItem(v.image) - v.label = TextItem(labelText[i]) - v.setBackgroundColor(0.5) - - self.views[1].setXLink(self.views[0]) - self.views[1].setYLink(self.views[0]) - self.views[2].setXLink(self.views[0]) - self.views[2].setYLink(self.views[0]) - - def test(self, im1, im2, message): - """Ask the user to decide whether an image test passes or fails. - - This method displays the test image, reference image, and the difference - between the two. It then blocks until the user selects the test output - by clicking a pass/fail button or typing p/f. If the user fails the test, - then an exception is raised. - """ - self.show() - if im2 is None: - message += '\nImage1: %s %s Image2: [no standard]' % (im1.shape, im1.dtype) - im2 = np.zeros((1, 1, 3), dtype=np.ubyte) - else: - message += '\nImage1: %s %s Image2: %s %s' % (im1.shape, im1.dtype, im2.shape, im2.dtype) - self.label.setText(message) - - self.views[0].image.setImage(im1) - self.views[1].image.setImage(im2) - diff = makeDiffImage(im1, im2) - - self.views[2].image.setImage(diff) - self.views[0].autoRange() - - while True: - QtGui.QApplication.processEvents() - lastKey = self.lastKey - - self.lastKey = None - if lastKey in ('f', 'esc') or not self.isVisible(): - raise Exception("User rejected test result.") - elif lastKey == 'p': - break - time.sleep(0.03) - - for v in self.views: - v.image.setImage(np.zeros((1, 1, 3), dtype=np.ubyte)) - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape: - self.lastKey = 'esc' - else: - self.lastKey = str(event.text()).lower() - - def passTest(self): - self.lastKey = 'p' - - def failTest(self): - self.lastKey = 'f' - - -def getTestDataRepo(): - """Return the path to a git repository with the required commit checked - out. - - If the repository does not exist, then it is cloned from - https://github.com/pyqtgraph/test-data. If the repository already exists - then the required commit is checked out. - """ - global testDataTag - - if os.getenv("CI"): - dataPath = os.path.join(os.environ["GITHUB_WORKSPACE"], '.pyqtgraph', 'test-data') - else: - dataPath = os.path.join(os.path.expanduser('~'), '.pyqtgraph', 'test-data') - gitPath = 'https://github.com/pyqtgraph/test-data' - gitbase = gitCmdBase(dataPath) - - if os.path.isdir(dataPath): - # Already have a test-data repository to work with. - - # Get the commit ID of testDataTag. Do a fetch if necessary. - try: - tagCommit = gitCommitId(dataPath, testDataTag) - except NameError: - cmd = gitbase + ['fetch', '--tags', 'origin'] - print(' '.join(cmd)) - sp.check_call(cmd) - try: - tagCommit = gitCommitId(dataPath, testDataTag) - except NameError: - raise Exception("Could not find tag '%s' in test-data repo at" - " %s" % (testDataTag, dataPath)) - except Exception: - if not os.path.exists(os.path.join(dataPath, '.git')): - raise Exception("Directory '%s' does not appear to be a git " - "repository. Please remove this directory." % - dataPath) - else: - raise - - # If HEAD is not the correct commit, then do a checkout - if gitCommitId(dataPath, 'HEAD') != tagCommit: - print("Checking out test-data tag '%s'" % testDataTag) - sp.check_call(gitbase + ['checkout', testDataTag]) - - else: - print("Attempting to create git clone of test data repo in %s.." % - dataPath) - - parentPath = os.path.split(dataPath)[0] - if not os.path.isdir(parentPath): - os.makedirs(parentPath) - - if os.getenv('TRAVIS') is not None or os.getenv('CI') is not None: - # Create a shallow clone of the test-data repository (to avoid - # downloading more data than is necessary) - os.makedirs(dataPath) - cmds = [ - gitbase + ['init'], - gitbase + ['remote', 'add', 'origin', gitPath], - gitbase + ['fetch', '--tags', 'origin', testDataTag, - '--depth=1'], - gitbase + ['checkout', '-b', 'master', 'FETCH_HEAD'], - ] - else: - # Create a full clone - cmds = [['git', 'clone', gitPath, dataPath]] - - for cmd in cmds: - print(' '.join(cmd)) - rval = sp.check_call(cmd) - if rval == 0: - continue - raise RuntimeError("Test data path '%s' does not exist and could " - "not be created with git. Please create a git " - "clone of %s at this path." % - (dataPath, gitPath)) - - return dataPath - - -def gitCmdBase(path): - return ['git', '--git-dir=%s/.git' % path, '--work-tree=%s' % path] - - -def gitStatus(path): - """Return a string listing all changes to the working tree in a git - repository. - """ - cmd = gitCmdBase(path) + ['status', '--porcelain'] - return runSubprocess(cmd, stderr=None, universal_newlines=True) - - -def gitCommitId(path, ref): - """Return the commit id of *ref* in the git repository at *path*. - """ - cmd = gitCmdBase(path) + ['show', ref] - try: - output = runSubprocess(cmd, stderr=None, universal_newlines=True) - except sp.CalledProcessError: - print(cmd) - raise NameError("Unknown git reference '%s'" % ref) - commit = output.split('\n')[0] - assert commit[:7] == 'commit ' - return commit[7:] - - -def runSubprocess(command, return_code=False, **kwargs): - """Run command using subprocess.Popen - - Similar to subprocess.check_output(), which is not available in 2.6. - - Run command and wait for command to complete. If the return code was zero - then return, otherwise raise CalledProcessError. - By default, this will also add stdout= and stderr=subproces.PIPE - to the call to Popen to suppress printing to the terminal. - - Parameters - ---------- - command : list of str - Command to run as subprocess (see subprocess.Popen documentation). - **kwargs : dict - Additional kwargs to pass to ``subprocess.Popen``. - - Returns - ------- - stdout : str - Stdout returned by the process. - """ - # code adapted with permission from mne-python - use_kwargs = dict(stderr=None, stdout=sp.PIPE) - use_kwargs.update(kwargs) - - p = sp.Popen(command, **use_kwargs) - output = p.communicate()[0] - - # communicate() may return bytes, str, or None depending on the kwargs - # passed to Popen(). Convert all to unicode str: - output = '' if output is None else output - output = output.decode('utf-8') if isinstance(output, bytes) else output - - if p.returncode != 0: - print(output) - err_fun = sp.CalledProcessError.__init__ - if 'output' in inspect.getfullargspec(err_fun).args: - raise sp.CalledProcessError(p.returncode, command, output) - else: - raise sp.CalledProcessError(p.returncode, command) - - return output - - -def scenegraphState(view, name): - """Return information about the scenegraph for debugging test failures. - """ - state = "====== Scenegraph state for %s ======\n" % name - state += "view size: %dx%d\n" % (view.width(), view.height()) - state += "view transform:\n" + indent(transformStr(view.transform()), " ") - for item in view.scene().items(): - if item.parentItem() is None: - state += itemState(item) + '\n' - return state - - -def itemState(root): - state = str(root) + '\n' - from .. import ViewBox - state += 'bounding rect: ' + str(root.boundingRect()) + '\n' - if isinstance(root, ViewBox): - state += "view range: " + str(root.viewRange()) + '\n' - state += "transform:\n" + indent(transformStr(root.transform()).strip(), " ") + '\n' - for item in root.childItems(): - state += indent(itemState(item).strip(), " ") + '\n' - return state - - -def transformStr(t): - return ("[%0.2f %0.2f %0.2f]\n"*3) % (t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), t.m31(), t.m32(), t.m33()) - - -def indent(s, pfx): - return '\n'.join([pfx+line for line in s.split('\n')]) - - -class TransposedImageItem(ImageItem): - # used for testing image axis order; we can test row-major and col-major using - # the same test images - def __init__(self, *args, **kwds): - self.__transpose = kwds.pop('transpose', False) - ImageItem.__init__(self, *args, **kwds) - def setImage(self, image=None, **kwds): - if image is not None and self.__transpose is True: - image = np.swapaxes(image, 0, 1) - return ImageItem.setImage(self, image, **kwds) diff --git a/pyqtgraph/tests/test_Vector.py b/pyqtgraph/tests/test_Vector.py deleted file mode 100644 index dcae6f9..0000000 --- a/pyqtgraph/tests/test_Vector.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - - -def test_Vector_init(): - """Test construction of Vector objects from a variety of source types.""" - # separate values without z - v = pg.Vector(0, 1) - assert v.z() == 0 - - v = pg.Vector(0.0, 1.0) - assert v.z() == 0 - - # separate values with 3 args - v = pg.Vector(0, 1, 2) - v = pg.Vector(0.0, 1.0, 2.0) - - # all in a list - v = pg.Vector([0, 1]) - assert v.z() == 0 - v = pg.Vector([0, 1, 2]) - - # QSizeF - v = pg.Vector(QtCore.QSizeF(1, 2)) - assert v.x() == 1 - assert v.z() == 0 - - # QPoint - v = pg.Vector(QtCore.QPoint(0, 1)) - assert v.z() == 0 - v = pg.Vector(QtCore.QPointF(0, 1)) - assert v.z() == 0 - - # QVector3D - qv = QtGui.QVector3D(1, 2, 3) - v = pg.Vector(qv) - assert v == qv - - with pytest.raises(Exception): - v = pg.Vector(1, 2, 3, 4) - - -def test_Vector_interface(): - """Test various aspects of the Vector API.""" - v = pg.Vector(-1, 2) - - # len - assert len(v) == 3 - - # indexing - assert v[0] == -1 - assert v[2] == 0 - with pytest.raises(IndexError): - x = v[4] - - assert v[1] == 2 - v[1] = 5 - assert v[1] == 5 - - # iteration - v2 = pg.Vector(*v) - assert v2 == v - - assert abs(v).x() == 1 - - # angle - v1 = pg.Vector(1, 0) - v2 = pg.Vector(1, 1) - assert abs(v1.angle(v2) - 45) < 0.001 diff --git a/pyqtgraph/tests/test_configparser.py b/pyqtgraph/tests/test_configparser.py deleted file mode 100644 index 27af9ec..0000000 --- a/pyqtgraph/tests/test_configparser.py +++ /dev/null @@ -1,36 +0,0 @@ -from pyqtgraph import configfile -import numpy as np -import tempfile, os - -def test_longArrays(): - """ - Test config saving and loading of long arrays. - """ - tmp = tempfile.mktemp(".cfg") - - arr = np.arange(20) - configfile.writeConfigFile({'arr':arr}, tmp) - config = configfile.readConfigFile(tmp) - - assert all(config['arr'] == arr) - - os.remove(tmp) - -def test_multipleParameters(): - """ - Test config saving and loading of multiple parameters. - """ - tmp = tempfile.mktemp(".cfg") - - par1 = [1,2,3] - par2 = "Test" - par3 = {'a':3,'b':'c'} - - configfile.writeConfigFile({'par1':par1, 'par2':par2, 'par3':par3}, tmp) - config = configfile.readConfigFile(tmp) - - assert config['par1'] == par1 - assert config['par2'] == par2 - assert config['par3'] == par3 - - os.remove(tmp) diff --git a/pyqtgraph/tests/test_exit_crash.py b/pyqtgraph/tests/test_exit_crash.py deleted file mode 100644 index e566138..0000000 --- a/pyqtgraph/tests/test_exit_crash.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -import subprocess -import tempfile -import pyqtgraph as pg -import pytest -import textwrap -import time - -code = """ -import sys -sys.path.insert(0, '{path}') -import pyqtgraph as pg -app = pg.mkQApp() -w = pg.{classname}({args}) -""" - -skipmessage = ('unclear why this test is failing. skipping until someone has' - ' time to fix it') - - -def call_with_timeout(*args, **kwargs): - """Mimic subprocess.call with timeout for python < 3.3""" - wait_per_poll = 0.1 - try: - timeout = kwargs.pop('timeout') - except KeyError: - timeout = 10 - - rc = None - p = subprocess.Popen(*args, **kwargs) - for i in range(int(timeout/wait_per_poll)): - rc = p.poll() - if rc is not None: - break - time.sleep(wait_per_poll) - return rc - - -@pytest.mark.skipif(True, reason=skipmessage) -def test_exit_crash(): - # For each Widget subclass, run a simple python script that creates an - # instance and then shuts down. The intent is to check for segmentation - # faults when each script exits. - tmp = tempfile.mktemp(".py") - path = os.path.dirname(pg.__file__) - - initArgs = { - 'CheckTable': "[]", - 'ProgressDialog': '"msg"', - 'VerticalLabel': '"msg"', - } - - for name in dir(pg): - obj = getattr(pg, name) - if not isinstance(obj, type) or not issubclass(obj, pg.QtGui.QWidget): - continue - - print(name) - argstr = initArgs.get(name, "") - with open(tmp, 'w') as f: - f.write(code.format(path=path, classname=name, args=argstr)) - proc = subprocess.Popen([sys.executable, tmp]) - assert proc.wait() == 0 - - os.remove(tmp) - -@pytest.mark.skipif(pg.Qt.QtVersion.startswith("5.9"), reason="Functionality not well supported, failing only on this config") -def test_pg_exit(): - # test the pg.exit() function - code = textwrap.dedent(""" - import pyqtgraph as pg - app = pg.mkQApp() - pg.plot() - pg.exit() - """) - rc = call_with_timeout([sys.executable, '-c', code], timeout=5, shell=False) - assert rc == 0 diff --git a/pyqtgraph/tests/test_functions.py b/pyqtgraph/tests/test_functions.py deleted file mode 100644 index ea35cc2..0000000 --- a/pyqtgraph/tests/test_functions.py +++ /dev/null @@ -1,450 +0,0 @@ -# -*- coding: utf-8 -*- -import pyqtgraph as pg -import numpy as np -import sys -from copy import deepcopy -from collections import OrderedDict -from numpy.testing import assert_array_almost_equal, assert_almost_equal -import pytest - - -np.random.seed(12345) - - -def testSolve3D(): - p1 = np.array([[0,0,0,1], - [1,0,0,1], - [0,1,0,1], - [0,0,1,1]], dtype=float) - - # transform points through random matrix - tr = np.random.normal(size=(4, 4)) - tr[3] = (0,0,0,1) - p2 = np.dot(tr, p1.T).T[:,:3] - - # solve to see if we can recover the transformation matrix. - tr2 = pg.solve3DTransform(p1, p2) - - assert_array_almost_equal(tr[:3], tr2[:3]) - - -def test_interpolateArray_order0(): - check_interpolateArray(order=0) - - -def test_interpolateArray_order1(): - check_interpolateArray(order=1) - - -def check_interpolateArray(order): - def interpolateArray(data, x): - result = pg.interpolateArray(data, x, order=order) - assert result.shape == x.shape[:-1] + data.shape[x.shape[-1]:] - return result - - data = np.array([[ 1., 2., 4. ], - [ 10., 20., 40. ], - [ 100., 200., 400.]]) - - # test various x shapes - interpolateArray(data, np.ones((1,))) - interpolateArray(data, np.ones((2,))) - interpolateArray(data, np.ones((1, 1))) - interpolateArray(data, np.ones((1, 2))) - interpolateArray(data, np.ones((5, 1))) - interpolateArray(data, np.ones((5, 2))) - interpolateArray(data, np.ones((5, 5, 1))) - interpolateArray(data, np.ones((5, 5, 2))) - with pytest.raises(TypeError): - interpolateArray(data, np.ones((3,))) - with pytest.raises(TypeError): - interpolateArray(data, np.ones((1, 3,))) - with pytest.raises(TypeError): - interpolateArray(data, np.ones((5, 5, 3,))) - - x = np.array([[ 0.3, 0.6], - [ 1. , 1. ], - [ 0.501, 1. ], # NOTE: testing at exactly 0.5 can yield different results from map_coordinates - [ 0.501, 2.501], # due to differences in rounding - [ 10. , 10. ]]) - - result = interpolateArray(data, x) - # make sure results match ndimage.map_coordinates - import scipy.ndimage - spresult = scipy.ndimage.map_coordinates(data, x.T, order=order) - #spresult = np.array([ 5.92, 20. , 11. , 0. , 0. ]) # generated with the above line - - assert_array_almost_equal(result, spresult) - - # test mapping when x.shape[-1] < data.ndim - x = np.array([[ 0.3, 0], - [ 0.3, 1], - [ 0.3, 2]]) - r1 = interpolateArray(data, x) - x = np.array([0.3]) # should broadcast across axis 1 - r2 = interpolateArray(data, x) - - assert_array_almost_equal(r1, r2) - - - # test mapping 2D array of locations - x = np.array([[[0.501, 0.501], [0.501, 1.0], [0.501, 1.501]], - [[1.501, 0.501], [1.501, 1.0], [1.501, 1.501]]]) - - r1 = interpolateArray(data, x) - r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=order) - #r2 = np.array([[ 8.25, 11. , 16.5 ], # generated with the above line - #[ 82.5 , 110. , 165. ]]) - - assert_array_almost_equal(r1, r2) - -def test_subArray(): - a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) - b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) - c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]]) - assert np.all(b == c) - - # operate over first axis; broadcast over the rest - aa = np.vstack([a, a/100.]).T - cc = np.empty(c.shape + (2,)) - cc[..., 0] = c - cc[..., 1] = c / 100. - bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1)) - assert np.all(bb == cc) - -def test_subArray(): - a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) - b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) - c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]]) - assert np.all(b == c) - - # operate over first axis; broadcast over the rest - aa = np.vstack([a, a/100.]).T - cc = np.empty(c.shape + (2,)) - cc[..., 0] = c - cc[..., 1] = c / 100. - bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1)) - assert np.all(bb == cc) - - -def test_rescaleData(): - dtypes = map(np.dtype, ('ubyte', 'uint16', 'byte', 'int16', 'int', 'float')) - for dtype1 in dtypes: - for dtype2 in dtypes: - data = (np.random.random(size=10) * 2**32 - 2**31).astype(dtype1) - for scale, offset in [(10, 0), (10., 0.), (1, -50), (0.2, 0.5), (0.001, 0)]: - if dtype2.kind in 'iu': - lim = np.iinfo(dtype2) - lim = lim.min, lim.max - else: - lim = (-np.inf, np.inf) - s1 = np.clip(float(scale) * (data-float(offset)), *lim).astype(dtype2) - s2 = pg.rescaleData(data, scale, offset, dtype2) - assert s1.dtype == s2.dtype - if dtype2.kind in 'iu': - assert np.all(s1 == s2) - else: - assert np.allclose(s1, s2) - - -def test_makeARGB(): - # Many parameters to test here: - # * data dtype (ubyte, uint16, float, others) - # * data ndim (2 or 3) - # * levels (None, 1D, or 2D) - # * lut dtype - # * lut size - # * lut ndim (1 or 2) - # * useRGBA argument - # Need to check that all input values map to the correct output values, especially - # at and beyond the edges of the level range. - - def checkArrays(a, b): - # because py.test output is difficult to read for arrays - if not np.all(a == b): - comp = [] - for i in range(a.shape[0]): - if a.shape[1] > 1: - comp.append('[') - for j in range(a.shape[1]): - m = a[i,j] == b[i,j] - comp.append('%d,%d %s %s %s%s' % - (i, j, str(a[i,j]).ljust(15), str(b[i,j]).ljust(15), - m, ' ********' if not np.all(m) else '')) - if a.shape[1] > 1: - comp.append(']') - raise Exception("arrays do not match:\n%s" % '\n'.join(comp)) - - def checkImage(img, check, alpha, alphaCheck): - assert img.dtype == np.ubyte - assert alpha is alphaCheck - if alpha is False: - checkArrays(img[..., 3], 255) - - if np.isscalar(check) or check.ndim == 3: - checkArrays(img[..., :3], check) - elif check.ndim == 2: - checkArrays(img[..., :3], check[..., np.newaxis]) - elif check.ndim == 1: - checkArrays(img[..., :3], check[..., np.newaxis, np.newaxis]) - else: - raise Exception('invalid check array ndim') - - # uint8 data tests - - im1 = np.arange(256).astype('ubyte').reshape(256, 1) - im2, alpha = pg.makeARGB(im1, levels=(0, 255)) - checkImage(im2, im1, alpha, False) - - im3, alpha = pg.makeARGB(im1, levels=(0.0, 255.0)) - checkImage(im3, im1, alpha, False) - - im4, alpha = pg.makeARGB(im1, levels=(255, 0)) - checkImage(im4, 255-im1, alpha, False) - - im5, alpha = pg.makeARGB(np.concatenate([im1]*3, axis=1), levels=[(0, 255), (0.0, 255.0), (255, 0)]) - checkImage(im5, np.concatenate([im1, im1, 255-im1], axis=1), alpha, False) - - - im2, alpha = pg.makeARGB(im1, levels=(128,383)) - checkImage(im2[:128], 0, alpha, False) - checkImage(im2[128:], im1[:128], alpha, False) - - - # uint8 data + uint8 LUT - lut = np.arange(256)[::-1].astype(np.uint8) - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, lut, alpha, False) - - # lut larger than maxint - lut = np.arange(511).astype(np.uint8) - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, lut[::2], alpha, False) - - # lut smaller than maxint - lut = np.arange(128).astype(np.uint8) - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, np.linspace(0, 127.5, 256, dtype='ubyte'), alpha, False) - - # lut + levels - lut = np.arange(256)[::-1].astype(np.uint8) - im2, alpha = pg.makeARGB(im1, lut=lut, levels=[-128, 384]) - checkImage(im2, np.linspace(191.5, 64.5, 256, dtype='ubyte'), alpha, False) - - im2, alpha = pg.makeARGB(im1, lut=lut, levels=[64, 192]) - checkImage(im2, np.clip(np.linspace(384.5, -127.5, 256), 0, 255).astype('ubyte'), alpha, False) - - # uint8 data + uint16 LUT - lut = np.arange(4096)[::-1].astype(np.uint16) // 16 - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, np.arange(256)[::-1].astype('ubyte'), alpha, False) - - # uint8 data + float LUT - lut = np.linspace(10., 137., 256) - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, lut.astype('ubyte'), alpha, False) - - # uint8 data + 2D LUT - lut = np.zeros((256, 3), dtype='ubyte') - lut[:,0] = np.arange(256) - lut[:,1] = np.arange(256)[::-1] - lut[:,2] = 7 - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, lut[:,None,::-1], alpha, False) - - # check useRGBA - im2, alpha = pg.makeARGB(im1, lut=lut, useRGBA=True) - checkImage(im2, lut[:,None,:], alpha, False) - - - # uint16 data tests - im1 = np.arange(0, 2**16, 256).astype('uint16')[:, None] - im2, alpha = pg.makeARGB(im1, levels=(512, 2**16)) - checkImage(im2, np.clip(np.linspace(-2, 253, 256), 0, 255).astype('ubyte'), alpha, False) - - lut = (np.arange(512, 2**16)[::-1] // 256).astype('ubyte') - im2, alpha = pg.makeARGB(im1, lut=lut, levels=(512, 2**16-256)) - checkImage(im2, np.clip(np.linspace(257, 2, 256), 0, 255).astype('ubyte'), alpha, False) - - lut = np.zeros(2**16, dtype='ubyte') - lut[1000:1256] = np.arange(256) - lut[1256:] = 255 - im1 = np.arange(1000, 1256).astype('uint16')[:, None] - im2, alpha = pg.makeARGB(im1, lut=lut) - checkImage(im2, np.arange(256).astype('ubyte'), alpha, False) - - - - # float data tests - im1 = np.linspace(1.0, 17.0, 256)[:, None] - im2, alpha = pg.makeARGB(im1, levels=(5.0, 13.0)) - checkImage(im2, np.clip(np.linspace(-128, 383, 256), 0, 255).astype('ubyte'), alpha, False) - - lut = (np.arange(1280)[::-1] // 10).astype('ubyte') - im2, alpha = pg.makeARGB(im1, lut=lut, levels=(1, 17)) - checkImage(im2, np.linspace(127.5, 0, 256).astype('ubyte'), alpha, False) - - # nans in image - - # 2d input image, one pixel is nan - im1 = np.ones((10, 12)) - im1[3, 5] = np.nan - im2, alpha = pg.makeARGB(im1, levels=(0, 1)) - assert alpha - assert im2[3, 5, 3] == 0 # nan pixel is transparent - assert im2[0, 0, 3] == 255 # doesn't affect other pixels - - # 3d RGB input image, any color channel of a pixel is nan - im1 = np.ones((10, 12, 3)) - im1[3, 5, 1] = np.nan - im2, alpha = pg.makeARGB(im1, levels=(0, 1)) - assert alpha - assert im2[3, 5, 3] == 0 # nan pixel is transparent - assert im2[0, 0, 3] == 255 # doesn't affect other pixels - - # 3d RGBA input image, any color channel of a pixel is nan - im1 = np.ones((10, 12, 4)) - im1[3, 5, 1] = np.nan - im2, alpha = pg.makeARGB(im1, levels=(0, 1), useRGBA=True) - assert alpha - assert im2[3, 5, 3] == 0 # nan pixel is transparent - - # test sanity checks - class AssertExc(object): - def __init__(self, exc=Exception): - self.exc = exc - def __enter__(self): - return self - def __exit__(self, *args): - assert args[0] is self.exc, "Should have raised %s (got %s)" % (self.exc, args[0]) - return True - - with AssertExc(TypeError): # invalid image shape - pg.makeARGB(np.zeros((2,), dtype='float')) - with AssertExc(TypeError): # invalid image shape - pg.makeARGB(np.zeros((2,2,7), dtype='float')) - with AssertExc(): # float images require levels arg - pg.makeARGB(np.zeros((2,2), dtype='float')) - with AssertExc(): # bad levels arg - pg.makeARGB(np.zeros((2,2), dtype='float'), levels=[1]) - with AssertExc(): # bad levels arg - pg.makeARGB(np.zeros((2,2), dtype='float'), levels=[1,2,3]) - with AssertExc(): # can't mix 3-channel levels and LUT - pg.makeARGB(np.zeros((2,2)), lut=np.zeros((10,3), dtype='ubyte'), levels=[(0,1)]*3) - with AssertExc(): # multichannel levels must have same number of channels as image - pg.makeARGB(np.zeros((2,2,3), dtype='float'), levels=[(1,2)]*4) - with AssertExc(): # 3d levels not allowed - pg.makeARGB(np.zeros((2,2,3), dtype='float'), levels=np.zeros([3, 2, 2])) - - -def test_eq(): - eq = pg.functions.eq - - zeros = [0, 0.0, np.float64(0), np.float32(0), np.int32(0), np.int64(0)] - for i,x in enumerate(zeros): - for y in zeros[i:]: - assert eq(x, y) - assert eq(y, x) - - assert eq(np.nan, np.nan) - - # test - class NotEq(object): - def __eq__(self, x): - return False - - noteq = NotEq() - assert eq(noteq, noteq) # passes because they are the same object - assert not eq(noteq, NotEq()) - - - # Should be able to test for equivalence even if the test raises certain - # exceptions - class NoEq(object): - def __init__(self, err): - self.err = err - def __eq__(self, x): - raise self.err - - noeq1 = NoEq(AttributeError()) - noeq2 = NoEq(ValueError()) - noeq3 = NoEq(Exception()) - - assert eq(noeq1, noeq1) - assert not eq(noeq1, noeq2) - assert not eq(noeq2, noeq1) - with pytest.raises(Exception): - eq(noeq3, noeq2) - - # test array equivalence - # note that numpy has a weird behavior here--np.all() always returns True - # if one of the arrays has size=0; eq() will only return True if both arrays - # have the same shape. - a1 = np.zeros((10, 20)).astype('float') - a2 = a1 + 1 - a3 = a2.astype('int') - a4 = np.empty((0, 20)) - assert not eq(a1, a2) # same shape/dtype, different values - assert not eq(a1, a3) # same shape, different dtype and values - assert not eq(a1, a4) # different shape (note: np.all gives True if one array has size 0) - - assert not eq(a2, a3) # same values, but different dtype - assert not eq(a2, a4) # different shape - - assert not eq(a3, a4) # different shape and dtype - - assert eq(a4, a4.copy()) - assert not eq(a4, a4.T) - - # test containers - - assert not eq({'a': 1}, {'a': 1, 'b': 2}) - assert not eq({'a': 1}, {'a': 2}) - d1 = {'x': 1, 'y': np.nan, 3: ['a', np.nan, a3, 7, 2.3], 4: a4} - d2 = deepcopy(d1) - assert eq(d1, d2) - d1_ordered = OrderedDict(d1) - d2_ordered = deepcopy(d1_ordered) - assert eq(d1_ordered, d2_ordered) - assert not eq(d1_ordered, d2) - items = list(d1.items()) - assert not eq(OrderedDict(items), OrderedDict(reversed(items))) - - assert not eq([1,2,3], [1,2,3,4]) - l1 = [d1, np.inf, -np.inf, np.nan] - l2 = deepcopy(l1) - t1 = tuple(l1) - t2 = tuple(l2) - assert eq(l1, l2) - assert eq(t1, t2) - - assert eq(set(range(10)), set(range(10))) - assert not eq(set(range(10)), set(range(9))) - - -@pytest.mark.parametrize("s,suffix,expected", [ - # usual cases - ("100 uV", "V", ("100", "u", "V")), - ("100 µV", "V", ("100", "µ", "V")), - ("4.2 nV", None, ("4.2", "n", "V")), - ("1.2 m", "m", ("1.2", "", "m")), - ("1.2 m", None, ("1.2", "", "m")), - ("5.0e9", None, ("5.0e9", "", "")), - ("2 units", "units", ("2", "", "units")), - # siPrefix with explicit empty suffix - ("1.2 m", "", ("1.2", "m", "")), - ("5.0e-9 M", "", ("5.0e-9", "M", "")), - # weirder cases that should return the reasonable thing - ("4.2 nV", "nV", ("4.2", "", "nV")), - ("4.2 nV", "", ("4.2", "n", "")), - ("1.2 j", "", ("1.2", "", "")), - ("1.2 j", None, ("1.2", "", "j")), - # expected error cases - ("100 uV", "v", ValueError), -]) -def test_siParse(s, suffix, expected): - if isinstance(expected, tuple): - assert pg.siParse(s, suffix=suffix) == expected - else: - with pytest.raises(expected): - pg.siParse(s, suffix=suffix) diff --git a/pyqtgraph/tests/test_qt.py b/pyqtgraph/tests/test_qt.py deleted file mode 100644 index 3ecf9db..0000000 --- a/pyqtgraph/tests/test_qt.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -import pyqtgraph as pg -import gc, os -import pytest - - -app = pg.mkQApp() - -def test_isQObjectAlive(): - o1 = pg.QtCore.QObject() - o2 = pg.QtCore.QObject() - o2.setParent(o1) - del o1 - assert not pg.Qt.isQObjectAlive(o2) - -@pytest.mark.skipif(pg.Qt.QT_LIB == 'PySide', reason='pysideuic does not appear to be ' - 'packaged with conda') -@pytest.mark.skipif( - pg.Qt.QT_LIB == "PySide2" - and tuple(map(int, pg.Qt.PySide2.__version__.split("."))) >= (5, 14) - and tuple(map(int, pg.Qt.PySide2.__version__.split("."))) < (5, 14, 2, 2), - reason="new PySide2 doesn't have loadUi functionality" -) -def test_loadUiType(): - path = os.path.dirname(__file__) - formClass, baseClass = pg.Qt.loadUiType(os.path.join(path, 'uictest.ui')) - w = baseClass() - ui = formClass() - ui.setupUi(w) - w.show() - app.processEvents() diff --git a/pyqtgraph/tests/test_ref_cycles.py b/pyqtgraph/tests/test_ref_cycles.py deleted file mode 100644 index f1b1acc..0000000 --- a/pyqtgraph/tests/test_ref_cycles.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Test for unwanted reference cycles - -""" -import pyqtgraph as pg -import numpy as np -import weakref -import warnings -app = pg.mkQApp() - -def assert_alldead(refs): - for ref in refs: - assert ref() is None - -def qObjectTree(root): - """Return root and its entire tree of qobject children""" - childs = [root] - for ch in pg.QtCore.QObject.children(root): - childs += qObjectTree(ch) - return childs - -def mkrefs(*objs): - """Return a list of weakrefs to each object in *objs. - QObject instances are expanded to include all child objects. - """ - allObjs = {} - for obj in objs: - if isinstance(obj, pg.QtCore.QObject): - obj = qObjectTree(obj) - else: - obj = [obj] - for o in obj: - allObjs[id(o)] = o - return [weakref.ref(obj) for obj in allObjs.values()] - - -def test_PlotWidget(): - def mkobjs(*args, **kwds): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - w = pg.PlotWidget(*args, **kwds) - data = pg.np.array([1,5,2,4,3]) - c = w.plot(data, name='stuff') - w.addLegend() - - # test that connections do not keep objects alive - w.plotItem.vb.sigRangeChanged.connect(mkrefs) - app.focusChanged.connect(w.plotItem.vb.invertY) - - # return weakrefs to a bunch of objects that should die when the scope exits. - return mkrefs(w, c, data, w.plotItem, w.plotItem.vb, w.plotItem.getMenu(), w.plotItem.getAxis('left')) - - for i in range(5): - assert_alldead(mkobjs()) - -def test_GraphicsWindow(): - def mkobjs(): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - w = pg.GraphicsWindow() - p1 = w.addPlot() - v1 = w.addViewBox() - return mkrefs(w, p1, v1) - - for i in range(5): - assert_alldead(mkobjs()) - -def test_ImageView(): - def mkobjs(): - iv = pg.ImageView() - data = np.zeros((10,10,5)) - iv.setImage(data) - - return mkrefs(iv, iv.imageItem, iv.view, iv.ui.histogram, data) - - for i in range(5): - assert_alldead(mkobjs()) - - - - - - -if __name__ == '__main__': - ot = test_PlotItem() diff --git a/pyqtgraph/tests/test_reload.py b/pyqtgraph/tests/test_reload.py deleted file mode 100644 index 193f17a..0000000 --- a/pyqtgraph/tests/test_reload.py +++ /dev/null @@ -1,126 +0,0 @@ -import tempfile, os, sys, shutil, time -import pyqtgraph as pg -import pyqtgraph.reload -import pytest - - -pgpath = os.path.join(os.path.dirname(pg.__file__), '..') -pgpath_repr = repr(pgpath) - -# make temporary directory to write module code -path = None - -def setup_module(): - # make temporary directory to write module code - global path - path = tempfile.mkdtemp() - sys.path.insert(0, path) - -def teardown_module(): - global path - shutil.rmtree(path) - sys.path.remove(path) - - -code = """ -import sys -sys.path.append({path_repr}) - -import pyqtgraph as pg - -class C(pg.QtCore.QObject): - sig = pg.QtCore.Signal() - def fn(self): - print("{msg}") - -""" - -def remove_cache(mod): - if os.path.isfile(mod+'c'): - os.remove(mod+'c') - cachedir = os.path.join(os.path.dirname(mod), '__pycache__') - if os.path.isdir(cachedir): - shutil.rmtree(cachedir) - -@pytest.mark.skipif( - ( - (pg.Qt.QT_LIB == "PySide2" and pg.Qt.QtVersion.startswith("5.15")) - or (pg.Qt.QT_LIB == "PySide6") - ) and (sys.version_info > (3, 9)) - or (sys.version_info >= (3, 10)), - reason="Unknown Issue" -) -def test_reload(): - py3 = sys.version_info >= (3,) - - # write a module - mod = os.path.join(path, 'reload_test_mod.py') - print("\nRELOAD FILE:", mod) - open(mod, 'w').write(code.format(path_repr=pgpath_repr, msg="C.fn() Version1")) - - # import the new module - import reload_test_mod - print("RELOAD MOD:", reload_test_mod.__file__) - - c = reload_test_mod.C() - c.sig.connect(c.fn) - if py3: - v1 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) - else: - v1 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__) - - - # write again and reload - open(mod, 'w').write(code.format(path_repr=pgpath_repr, msg="C.fn() Version2")) - time.sleep(1.1) - #remove_cache(mod) - result1 = pg.reload.reloadAll(path, debug=True) - if py3: - v2 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) - else: - v2 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__) - - if not py3: - assert c.fn.im_class is v2[0] - oldcfn = pg.reload.getPreviousVersion(c.fn) - if oldcfn is None: - # Function did not reload; are we using pytest's assertion rewriting? - raise Exception("Function did not reload. (This can happen when using py.test" - " with assertion rewriting; use --assert=plain for this test.)") - if py3: - assert oldcfn.__func__ is v1[2] - else: - assert oldcfn.im_class is v1[0] - assert oldcfn.__func__ is v1[2].__func__ - assert oldcfn.__self__ is c - - - # write again and reload - open(mod, 'w').write(code.format(path_repr=pgpath_repr, msg="C.fn() Version2")) - time.sleep(1.1) -# remove_cache(mod) - result2 = pg.reload.reloadAll(path, debug=True) - if py3: - v3 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, c.sig, c.fn, c.fn.__func__) - else: - v3 = (reload_test_mod.C, reload_test_mod.C.sig, reload_test_mod.C.fn, reload_test_mod.C.fn.__func__, c.sig, c.fn, c.fn.__func__) - - #for i in range(len(old)): - #print id(old[i]), id(new1[i]), id(new2[i]), old[i], new1[i] - - cfn1 = pg.reload.getPreviousVersion(c.fn) - cfn2 = pg.reload.getPreviousVersion(cfn1) - - if py3: - assert cfn1.__func__ is v2[2] - assert cfn2.__func__ is v1[2] - else: - assert cfn1.__func__ is v2[2].__func__ - assert cfn2.__func__ is v1[2].__func__ - assert cfn1.im_class is v2[0] - assert cfn2.im_class is v1[0] - assert cfn1.__self__ is c - assert cfn2.__self__ is c - - pg.functions.disconnect(c.sig, c.fn) - diff --git a/pyqtgraph/tests/test_signalproxy.py b/pyqtgraph/tests/test_signalproxy.py deleted file mode 100644 index 615fc32..0000000 --- a/pyqtgraph/tests/test_signalproxy.py +++ /dev/null @@ -1,153 +0,0 @@ -import sys -import pytest - -from ..Qt import QtCore -from ..Qt import QtGui -from ..Qt import QT_LIB, PYSIDE - -from ..SignalProxy import SignalProxy - - -class Sender(QtCore.QObject): - signalSend = QtCore.Signal() - - def __init__(self, parent=None): - super(Sender, self).__init__(parent) - - -class Receiver(QtCore.QObject): - - def __init__(self, parent=None): - super(Receiver, self).__init__(parent) - self.counter = 0 - - def slotReceive(self): - self.counter += 1 - - -@pytest.fixture -def qapp(): - app = QtGui.QApplication.instance() - if app is None: - app = QtGui.QApplication(sys.argv) - yield app - - app.processEvents(QtCore.QEventLoop.AllEvents, 100) - app.deleteLater() - - -def test_signal_proxy_slot(qapp): - """Test the normal work mode of SignalProxy with `signal` and `slot`""" - sender = Sender(parent=qapp) - receiver = Receiver(parent=qapp) - proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, - slot=receiver.slotReceive) - - assert proxy.blockSignal is False - assert proxy is not None - assert sender is not None - assert receiver is not None - - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - - assert receiver.counter > 0 - del proxy - del sender - del receiver - - -@pytest.mark.skipif(QT_LIB == PYSIDE and sys.version_info < (2, 8), - reason="Crashing on PySide and Python 2.7") -def test_signal_proxy_disconnect_slot(qapp): - """Test the disconnect of SignalProxy with `signal` and `slot`""" - sender = Sender(parent=qapp) - receiver = Receiver(parent=qapp) - proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, - slot=receiver.slotReceive) - - assert proxy.blockSignal is False - assert proxy is not None - assert sender is not None - assert receiver is not None - assert proxy.slot is not None - - proxy.disconnect() - assert proxy.slot is None - assert proxy.blockSignal is True - - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - - assert receiver.counter == 0 - - del proxy - del sender - del receiver - - -def test_signal_proxy_no_slot_start(qapp): - """Test the connect mode of SignalProxy without slot at start`""" - sender = Sender(parent=qapp) - receiver = Receiver(parent=qapp) - proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6) - - assert proxy.blockSignal is True - assert proxy is not None - assert sender is not None - assert receiver is not None - - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - assert receiver.counter == 0 - - # Start a connect - proxy.connectSlot(receiver.slotReceive) - assert proxy.blockSignal is False - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - assert receiver.counter > 0 - - # An additional connect should raise an AssertionError - with pytest.raises(AssertionError): - proxy.connectSlot(receiver.slotReceive) - - del proxy - del sender - del receiver - - -def test_signal_proxy_slot_block(qapp): - """Test the block mode of SignalProxy with `signal` and `slot`""" - sender = Sender(parent=qapp) - receiver = Receiver(parent=qapp) - proxy = SignalProxy(sender.signalSend, delay=0.0, rateLimit=0.6, - slot=receiver.slotReceive) - - assert proxy.blockSignal is False - assert proxy is not None - assert sender is not None - assert receiver is not None - - with proxy.block(): - sender.signalSend.emit() - sender.signalSend.emit() - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - - assert receiver.counter == 0 - - sender.signalSend.emit() - proxy.flush() - qapp.processEvents(QtCore.QEventLoop.AllEvents, 10) - - assert receiver.counter > 0 - - del proxy - del sender - del receiver diff --git a/pyqtgraph/tests/test_srttransform3d.py b/pyqtgraph/tests/test_srttransform3d.py deleted file mode 100644 index 88aa158..0000000 --- a/pyqtgraph/tests/test_srttransform3d.py +++ /dev/null @@ -1,39 +0,0 @@ -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui -import numpy as np -from numpy.testing import assert_array_almost_equal, assert_almost_equal - -testPoints = np.array([ - [0, 0, 0], - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - [-1, -1, 0], - [0, -1, -1]]) - - -def testMatrix(): - """ - SRTTransform3D => Transform3D => SRTTransform3D - """ - tr = pg.SRTTransform3D() - tr.setRotate(45, (0, 0, 1)) - tr.setScale(0.2, 0.4, 1) - tr.setTranslate(10, 20, 40) - assert tr.getRotation() == (45, QtGui.QVector3D(0, 0, 1)) - assert tr.getScale() == QtGui.QVector3D(0.2, 0.4, 1) - assert tr.getTranslation() == QtGui.QVector3D(10, 20, 40) - - tr2 = pg.Transform3D(tr) - assert np.all(tr.matrix() == tr2.matrix()) - - # This is the most important test: - # The transition from Transform3D to SRTTransform3D is a tricky one. - tr3 = pg.SRTTransform3D(tr2) - assert_array_almost_equal(tr.matrix(), tr3.matrix()) - assert_almost_equal(tr3.getRotation()[0], tr.getRotation()[0]) - assert_array_almost_equal(tr3.getRotation()[1], tr.getRotation()[1]) - assert_array_almost_equal(tr3.getScale(), tr.getScale()) - assert_array_almost_equal(tr3.getTranslation(), tr.getTranslation()) - - diff --git a/pyqtgraph/tests/test_stability.py b/pyqtgraph/tests/test_stability.py deleted file mode 100644 index 810b53b..0000000 --- a/pyqtgraph/tests/test_stability.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -PyQt/PySide stress test: - -Create lots of random widgets and graphics items, connect them together randomly, -the tear them down repeatedly. - -The purpose of this is to attempt to generate segmentation faults. -""" -from pyqtgraph.Qt import QtTest -import pyqtgraph as pg -from random import seed, randint -import sys, gc, weakref - -app = pg.mkQApp() - -seed(12345) - -widgetTypes = [ - pg.PlotWidget, - pg.ImageView, - pg.GraphicsView, - pg.QtGui.QWidget, - pg.QtGui.QTreeWidget, - pg.QtGui.QPushButton, - ] - -itemTypes = [ - pg.PlotCurveItem, - pg.ImageItem, - pg.PlotDataItem, - pg.ViewBox, - pg.QtGui.QGraphicsRectItem - ] - -widgets = [] -items = [] -allWidgets = weakref.WeakKeyDictionary() - - -def crashtest(): - global allWidgets - try: - gc.disable() - actions = [ - createWidget, - #setParent, - forgetWidget, - showWidget, - processEvents, - #raiseException, - #addReference, - ] - - thread = WorkThread() - thread.start() - - while True: - try: - action = randItem(actions) - action() - print('[%d widgets alive, %d zombie]' % (len(allWidgets), len(allWidgets) - len(widgets))) - except KeyboardInterrupt: - print("Caught interrupt; send another to exit.") - try: - for i in range(100): - QtTest.QTest.qWait(100) - except KeyboardInterrupt: - thread.terminate() - break - except: - sys.excepthook(*sys.exc_info()) - finally: - gc.enable() - - - -class WorkThread(pg.QtCore.QThread): - '''Intended to give the gc an opportunity to run from a non-gui thread.''' - def run(self): - i = 0 - while True: - i += 1 - if (i % 1000000) == 0: - print('--worker--') - - -def randItem(items): - return items[randint(0, len(items)-1)] - -def p(msg): - print(msg) - sys.stdout.flush() - -def createWidget(): - p('create widget') - global widgets, allWidgets - if len(widgets) > 50: - return - widget = randItem(widgetTypes)() - widget.setWindowTitle(widget.__class__.__name__) - widgets.append(widget) - allWidgets[widget] = 1 - p(" %s" % widget) - return widget - -def setParent(): - p('set parent') - global widgets - if len(widgets) < 2: - return - child = parent = None - while child is parent: - child = randItem(widgets) - parent = randItem(widgets) - p(" %s parent of %s" % (parent, child)) - child.setParent(parent) - -def forgetWidget(): - p('forget widget') - global widgets - if len(widgets) < 1: - return - widget = randItem(widgets) - p(' %s' % widget) - widgets.remove(widget) - -def showWidget(): - p('show widget') - global widgets - if len(widgets) < 1: - return - widget = randItem(widgets) - p(' %s' % widget) - widget.show() - -def processEvents(): - p('process events') - QtTest.QTest.qWait(25) - -class TstException(Exception): - pass - -def raiseException(): - p('raise exception') - raise TstException("A test exception") - -def addReference(): - p('add reference') - global widgets - if len(widgets) < 1: - return - obj1 = randItem(widgets) - obj2 = randItem(widgets) - p(' %s -> %s' % (obj1, obj2)) - obj1._testref = obj2 - - - -if __name__ == '__main__': - test_stability() diff --git a/pyqtgraph/tests/ui_testing.py b/pyqtgraph/tests/ui_testing.py deleted file mode 100644 index 05e74f6..0000000 --- a/pyqtgraph/tests/ui_testing.py +++ /dev/null @@ -1,69 +0,0 @@ -import time -from ..Qt import QtCore, QtGui, QtTest - - -def resizeWindow(win, w, h, timeout=2.0): - """Resize a window and wait until it has the correct size. - - This is required for unit testing on some platforms that do not guarantee - immediate response from the windowing system. - """ - QtGui.QApplication.processEvents() - # Sometimes the window size will switch multiple times before settling - # on its final size. Adding qWaitForWindowExposed seems to help with this. - QtTest.QTest.qWaitForWindowExposed(win) - win.resize(w, h) - start = time.time() - while True: - w1, h1 = win.width(), win.height() - if (w,h) == (w1,h1): - return - QtTest.QTest.qWait(10) - if time.time()-start > timeout: - raise TimeoutError("Window resize failed (requested %dx%d, got %dx%d)" % (w, h, w1, h1)) - - -# Functions for generating user input events. -# We would like to use QTest for this purpose, but it seems to be broken. -# See: http://stackoverflow.com/questions/16299779/qt-qgraphicsview-unit-testing-how-to-keep-the-mouse-in-a-pressed-state - -def mousePress(widget, pos, button, modifier=None): - if isinstance(widget, QtGui.QGraphicsView): - widget = widget.viewport() - if modifier is None: - modifier = QtCore.Qt.NoModifier - event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, pos, button, QtCore.Qt.NoButton, modifier) - QtGui.QApplication.sendEvent(widget, event) - - -def mouseRelease(widget, pos, button, modifier=None): - if isinstance(widget, QtGui.QGraphicsView): - widget = widget.viewport() - if modifier is None: - modifier = QtCore.Qt.NoModifier - event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, pos, button, QtCore.Qt.NoButton, modifier) - QtGui.QApplication.sendEvent(widget, event) - - -def mouseMove(widget, pos, buttons=None, modifier=None): - if isinstance(widget, QtGui.QGraphicsView): - widget = widget.viewport() - if modifier is None: - modifier = QtCore.Qt.NoModifier - if buttons is None: - buttons = QtCore.Qt.NoButton - event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, pos, QtCore.Qt.NoButton, buttons, modifier) - QtGui.QApplication.sendEvent(widget, event) - - -def mouseDrag(widget, pos1, pos2, button, modifier=None): - mouseMove(widget, pos1) - mousePress(widget, pos1, button, modifier) - mouseMove(widget, pos2, button, modifier) - mouseRelease(widget, pos2, button, modifier) - - -def mouseClick(widget, pos, button, modifier=None): - mouseMove(widget, pos) - mousePress(widget, pos, button, modifier) - mouseRelease(widget, pos, button, modifier) diff --git a/pyqtgraph/units.py b/pyqtgraph/units.py deleted file mode 100644 index d0f8987..0000000 --- a/pyqtgraph/units.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# Very simple unit support: -# - creates variable names like 'mV' and 'kHz' -# - the value assigned to the variable corresponds to the scale prefix -# (mV = 0.001) -# - the actual units are purely cosmetic for making code clearer: -# -# x = 20*pA is identical to x = 20*1e-12 -# -# No unicode variable names (μ,Ω) allowed until python 3, but just assigning -# them to the globals dict doesn't error in python 2. -import unicodedata - -# All unicode identifiers get normalized automatically -SI_PREFIXES = unicodedata.normalize("NFKC", u"yzafpnµm kMGTPEZY") -UNITS = unicodedata.normalize("NFKC", u"m,s,g,W,J,V,A,F,T,Hz,Ohm,Ω,S,N,C,px,b,B,Pa").split(",") -allUnits = {} - - -def addUnit(prefix, val): - g = globals() - for u in UNITS: - g[prefix + u] = val - allUnits[prefix + u] = val - - -for pre in SI_PREFIXES: - v = SI_PREFIXES.index(pre) - 8 - if pre == " ": - pre = "" - addUnit(pre, 1000 ** v) - -addUnit("c", 0.01) -addUnit("d", 0.1) -addUnit("da", 10) -addUnit("h", 100) -# py2 compatibility -addUnit("u", 1e-6) - - -def evalUnits(unitStr): - """ - Evaluate a unit string into ([numerators,...], [denominators,...]) - Examples: - N m/s^2 => ([N, m], [s, s]) - A*s / V => ([A, s], [V,]) - """ - pass - - -def formatUnits(units): - """ - Format a unit specification ([numerators,...], [denominators,...]) - into a string (this is the inverse of evalUnits) - """ - pass - - -def simplify(units): - """ - Cancel units that appear in both numerator and denominator, then attempt to replace - groups of units with single units where possible (ie, J/s => W) - """ - pass diff --git a/pyqtgraph/util/__init__.py b/pyqtgraph/util/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/util/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/util/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 0c6b177..0000000 Binary files a/pyqtgraph/util/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/util/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 3f447ff..0000000 Binary files a/pyqtgraph/util/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/cprint.cpython-36.pyc b/pyqtgraph/util/__pycache__/cprint.cpython-36.pyc deleted file mode 100644 index 05d57a3..0000000 Binary files a/pyqtgraph/util/__pycache__/cprint.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/cprint.cpython-37.pyc b/pyqtgraph/util/__pycache__/cprint.cpython-37.pyc deleted file mode 100644 index b507a7a..0000000 Binary files a/pyqtgraph/util/__pycache__/cprint.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/cupy_helper.cpython-36.pyc b/pyqtgraph/util/__pycache__/cupy_helper.cpython-36.pyc deleted file mode 100644 index fc0031c..0000000 Binary files a/pyqtgraph/util/__pycache__/cupy_helper.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/cupy_helper.cpython-37.pyc b/pyqtgraph/util/__pycache__/cupy_helper.cpython-37.pyc deleted file mode 100644 index 04d65c9..0000000 Binary files a/pyqtgraph/util/__pycache__/cupy_helper.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/garbage_collector.cpython-36.pyc b/pyqtgraph/util/__pycache__/garbage_collector.cpython-36.pyc deleted file mode 100644 index 0cbceb6..0000000 Binary files a/pyqtgraph/util/__pycache__/garbage_collector.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/get_resolution.cpython-36.pyc b/pyqtgraph/util/__pycache__/get_resolution.cpython-36.pyc deleted file mode 100644 index f61aea2..0000000 Binary files a/pyqtgraph/util/__pycache__/get_resolution.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/lru_cache.cpython-36.pyc b/pyqtgraph/util/__pycache__/lru_cache.cpython-36.pyc deleted file mode 100644 index 89a6347..0000000 Binary files a/pyqtgraph/util/__pycache__/lru_cache.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/mutex.cpython-36.pyc b/pyqtgraph/util/__pycache__/mutex.cpython-36.pyc deleted file mode 100644 index 9f5afbd..0000000 Binary files a/pyqtgraph/util/__pycache__/mutex.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/mutex.cpython-37.pyc b/pyqtgraph/util/__pycache__/mutex.cpython-37.pyc deleted file mode 100644 index 293ecf2..0000000 Binary files a/pyqtgraph/util/__pycache__/mutex.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/__pycache__/pil_fix.cpython-36.pyc b/pyqtgraph/util/__pycache__/pil_fix.cpython-36.pyc deleted file mode 100644 index 0440192..0000000 Binary files a/pyqtgraph/util/__pycache__/pil_fix.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__init__.py b/pyqtgraph/util/colorama/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/util/colorama/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/util/colorama/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index e610464..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/util/colorama/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 661798b..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__pycache__/win32.cpython-36.pyc b/pyqtgraph/util/colorama/__pycache__/win32.cpython-36.pyc deleted file mode 100644 index 928f129..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/win32.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__pycache__/win32.cpython-37.pyc b/pyqtgraph/util/colorama/__pycache__/win32.cpython-37.pyc deleted file mode 100644 index e9ef6e2..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/win32.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__pycache__/winterm.cpython-36.pyc b/pyqtgraph/util/colorama/__pycache__/winterm.cpython-36.pyc deleted file mode 100644 index eebce01..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/winterm.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/__pycache__/winterm.cpython-37.pyc b/pyqtgraph/util/colorama/__pycache__/winterm.cpython-37.pyc deleted file mode 100644 index cb42fe0..0000000 Binary files a/pyqtgraph/util/colorama/__pycache__/winterm.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/util/colorama/win32.py b/pyqtgraph/util/colorama/win32.py deleted file mode 100644 index c86ce18..0000000 --- a/pyqtgraph/util/colorama/win32.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. - -# from winbase.h -STDOUT = -11 -STDERR = -12 - -try: - from ctypes import windll - from ctypes import wintypes -except ImportError: - windll = None - SetConsoleTextAttribute = lambda *_: None -else: - from ctypes import ( - byref, Structure, c_char, c_short, c_int, c_uint32, c_ushort, c_void_p, POINTER - ) - - class CONSOLE_SCREEN_BUFFER_INFO(Structure): - """struct in wincon.h.""" - _fields_ = [ - ("dwSize", wintypes._COORD), - ("dwCursorPosition", wintypes._COORD), - ("wAttributes", wintypes.WORD), - ("srWindow", wintypes.SMALL_RECT), - ("dwMaximumWindowSize", wintypes._COORD), - ] - def __str__(self): - return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( - self.dwSize.Y, self.dwSize.X - , self.dwCursorPosition.Y, self.dwCursorPosition.X - , self.wAttributes - , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right - , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X - ) - - _GetStdHandle = windll.kernel32.GetStdHandle - _GetStdHandle.argtypes = [ - wintypes.DWORD, - ] - _GetStdHandle.restype = wintypes.HANDLE - - _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo - _GetConsoleScreenBufferInfo.argtypes = [ - wintypes.HANDLE, - c_void_p, - #POINTER(CONSOLE_SCREEN_BUFFER_INFO), - ] - _GetConsoleScreenBufferInfo.restype = wintypes.BOOL - - _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute - _SetConsoleTextAttribute.argtypes = [ - wintypes.HANDLE, - wintypes.WORD, - ] - _SetConsoleTextAttribute.restype = wintypes.BOOL - - _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition - _SetConsoleCursorPosition.argtypes = [ - wintypes.HANDLE, - c_int, - #wintypes._COORD, - ] - _SetConsoleCursorPosition.restype = wintypes.BOOL - - _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA - _FillConsoleOutputCharacterA.argtypes = [ - wintypes.HANDLE, - c_char, - wintypes.DWORD, - wintypes._COORD, - POINTER(wintypes.DWORD), - ] - _FillConsoleOutputCharacterA.restype = wintypes.BOOL - - _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute - _FillConsoleOutputAttribute.argtypes = [ - wintypes.HANDLE, - wintypes.WORD, - wintypes.DWORD, - c_int, - #wintypes._COORD, - POINTER(wintypes.DWORD), - ] - _FillConsoleOutputAttribute.restype = wintypes.BOOL - - handles = { - STDOUT: _GetStdHandle(STDOUT), - STDERR: _GetStdHandle(STDERR), - } - - def GetConsoleScreenBufferInfo(stream_id=STDOUT): - handle = handles[stream_id] - csbi = CONSOLE_SCREEN_BUFFER_INFO() - success = _GetConsoleScreenBufferInfo( - handle, byref(csbi)) - return csbi - - def SetConsoleTextAttribute(stream_id, attrs): - handle = handles[stream_id] - return _SetConsoleTextAttribute(handle, attrs) - - def SetConsoleCursorPosition(stream_id, position): - position = wintypes._COORD(*position) - # If the position is out of range, do nothing. - if position.Y <= 0 or position.X <= 0: - return - # Adjust for Windows' SetConsoleCursorPosition: - # 1. being 0-based, while ANSI is 1-based. - # 2. expecting (x,y), while ANSI uses (y,x). - adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1) - # Adjust for viewport's scroll position - sr = GetConsoleScreenBufferInfo(STDOUT).srWindow - adjusted_position.Y += sr.Top - adjusted_position.X += sr.Left - # Resume normal processing - handle = handles[stream_id] - return _SetConsoleCursorPosition(handle, adjusted_position) - - def FillConsoleOutputCharacter(stream_id, char, length, start): - handle = handles[stream_id] - char = c_char(char) - length = wintypes.DWORD(length) - num_written = wintypes.DWORD(0) - # Note that this is hard-coded for ANSI (vs wide) bytes. - success = _FillConsoleOutputCharacterA( - handle, char, length, start, byref(num_written)) - return num_written.value - - def FillConsoleOutputAttribute(stream_id, attr, length, start): - ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' - handle = handles[stream_id] - attribute = wintypes.WORD(attr) - length = wintypes.DWORD(length) - num_written = wintypes.DWORD(0) - # Note that this is hard-coded for ANSI (vs wide) bytes. - return _FillConsoleOutputAttribute( - handle, attribute, length, start, byref(num_written)) diff --git a/pyqtgraph/util/colorama/winterm.py b/pyqtgraph/util/colorama/winterm.py deleted file mode 100644 index 9c1c818..0000000 --- a/pyqtgraph/util/colorama/winterm.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -from . import win32 - - -# from wincon.h -class WinColor(object): - BLACK = 0 - BLUE = 1 - GREEN = 2 - CYAN = 3 - RED = 4 - MAGENTA = 5 - YELLOW = 6 - GREY = 7 - -# from wincon.h -class WinStyle(object): - NORMAL = 0x00 # dim text, dim background - BRIGHT = 0x08 # bright text, dim background - - -class WinTerm(object): - - def __init__(self): - self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes - self.set_attrs(self._default) - self._default_fore = self._fore - self._default_back = self._back - self._default_style = self._style - - def get_attrs(self): - return self._fore + self._back * 16 + self._style - - def set_attrs(self, value): - self._fore = value & 7 - self._back = (value >> 4) & 7 - self._style = value & WinStyle.BRIGHT - - def reset_all(self, on_stderr=None): - self.set_attrs(self._default) - self.set_console(attrs=self._default) - - def fore(self, fore=None, on_stderr=False): - if fore is None: - fore = self._default_fore - self._fore = fore - self.set_console(on_stderr=on_stderr) - - def back(self, back=None, on_stderr=False): - if back is None: - back = self._default_back - self._back = back - self.set_console(on_stderr=on_stderr) - - def style(self, style=None, on_stderr=False): - if style is None: - style = self._default_style - self._style = style - self.set_console(on_stderr=on_stderr) - - def set_console(self, attrs=None, on_stderr=False): - if attrs is None: - attrs = self.get_attrs() - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - win32.SetConsoleTextAttribute(handle, attrs) - - def get_position(self, handle): - position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition - # Because Windows coordinates are 0-based, - # and win32.SetConsoleCursorPosition expects 1-based. - position.X += 1 - position.Y += 1 - return position - - def set_cursor_position(self, position=None, on_stderr=False): - if position is None: - #I'm not currently tracking the position, so there is no default. - #position = self.get_position() - return - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - win32.SetConsoleCursorPosition(handle, position) - - def cursor_up(self, num_rows=0, on_stderr=False): - if num_rows == 0: - return - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - position = self.get_position(handle) - adjusted_position = (position.Y - num_rows, position.X) - self.set_cursor_position(adjusted_position, on_stderr) - - def erase_data(self, mode=0, on_stderr=False): - # 0 (or None) should clear from the cursor to the end of the screen. - # 1 should clear from the cursor to the beginning of the screen. - # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) - # - # At the moment, I only support mode 2. From looking at the API, it - # should be possible to calculate a different number of bytes to clear, - # and to do so relative to the cursor position. - if mode[0] not in (2,): - return - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - # here's where we'll home the cursor - coord_screen = win32.COORD(0,0) - csbi = win32.GetConsoleScreenBufferInfo(handle) - # get the number of character cells in the current buffer - dw_con_size = csbi.dwSize.X * csbi.dwSize.Y - # fill the entire screen with blanks - win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) - # now set the buffer's attributes accordingly - win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ) - # put the cursor at (0, 0) - win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) diff --git a/pyqtgraph/util/cprint.py b/pyqtgraph/util/cprint.py deleted file mode 100644 index 8b4fa20..0000000 --- a/pyqtgraph/util/cprint.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Cross-platform color text printing - -Based on colorama (see pyqtgraph/util/colorama/README.txt) -""" -import sys, re - -from .colorama.winterm import WinTerm, WinColor, WinStyle -from .colorama.win32 import windll -from ..python2_3 import basestring - -_WIN = sys.platform.startswith('win') -if windll is not None: - winterm = WinTerm() -else: - _WIN = False - -def winset(reset=False, fore=None, back=None, style=None, stderr=False): - if reset: - winterm.reset_all() - if fore is not None: - winterm.fore(fore, stderr) - if back is not None: - winterm.back(back, stderr) - if style is not None: - winterm.style(style, stderr) - -ANSI = {} -WIN = {} -for i,color in enumerate(['BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']): - globals()[color] = i - globals()['BR_' + color] = i + 8 - globals()['BACK_' + color] = i + 40 - ANSI[i] = "\033[%dm" % (30+i) - ANSI[i+8] = "\033[2;%dm" % (30+i) - ANSI[i+40] = "\033[%dm" % (40+i) - color = 'GREY' if color == 'WHITE' else color - WIN[i] = {'fore': getattr(WinColor, color), 'style': WinStyle.NORMAL} - WIN[i+8] = {'fore': getattr(WinColor, color), 'style': WinStyle.BRIGHT} - WIN[i+40] = {'back': getattr(WinColor, color)} - -RESET = -1 -ANSI[RESET] = "\033[0m" -WIN[RESET] = {'reset': True} - - -def cprint(stream, *args, **kwds): - """ - Print with color. Examples:: - - # colors are BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE - cprint('stdout', RED, 'This is in red. ', RESET, 'and this is normal\n') - - # Adding BR_ before the color manes it bright - cprint('stdout', BR_GREEN, 'This is bright green.\n', RESET) - - # Adding BACK_ changes background color - cprint('stderr', BACK_BLUE, WHITE, 'This is white-on-blue.', -1) - - # Integers 0-7 for normal, 8-15 for bright, and 40-47 for background. - # -1 to reset. - cprint('stderr', 1, 'This is in red.', -1) - - """ - if isinstance(stream, basestring): - stream = kwds.get('stream', 'stdout') - err = stream == 'stderr' - stream = getattr(sys, stream) - else: - err = kwds.get('stderr', False) - - if hasattr(stream, 'isatty') and stream.isatty(): - if _WIN: - # convert to win32 calls - for arg in args: - if isinstance(arg, basestring): - stream.write(arg) - else: - kwds = WIN[arg] - winset(stderr=err, **kwds) - else: - # convert to ANSI - for arg in args: - if isinstance(arg, basestring): - stream.write(arg) - else: - stream.write(ANSI[arg]) - else: - # ignore colors - for arg in args: - if isinstance(arg, basestring): - stream.write(arg) - -def cout(*args): - """Shorthand for cprint('stdout', ...)""" - cprint('stdout', *args) - -def cerr(*args): - """Shorthand for cprint('stderr', ...)""" - cprint('stderr', *args) - - diff --git a/pyqtgraph/util/cupy_helper.py b/pyqtgraph/util/cupy_helper.py deleted file mode 100644 index fadfaeb..0000000 --- a/pyqtgraph/util/cupy_helper.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -from warnings import warn - -from .. import getConfigOption - -def getCupy(): - if getConfigOption("useCupy"): - try: - import cupy - except ImportError: - warn("cupy library could not be loaded, but 'useCupy' is set.") - return None - if os.name == "nt" and cupy.cuda.runtime.runtimeGetVersion() < 11000: - warn("In Windows, CUDA toolkit should be version 11 or higher, or some functions may misbehave.") - return cupy - else: - return None diff --git a/pyqtgraph/util/garbage_collector.py b/pyqtgraph/util/garbage_collector.py deleted file mode 100644 index 0ea42dc..0000000 --- a/pyqtgraph/util/garbage_collector.py +++ /dev/null @@ -1,50 +0,0 @@ -import gc - -from ..Qt import QtCore - -class GarbageCollector(object): - ''' - Disable automatic garbage collection and instead collect manually - on a timer. - - This is done to ensure that garbage collection only happens in the GUI - thread, as otherwise Qt can crash. - - Credit: Erik Janssens - Source: http://pydev.blogspot.com/2014/03/should-python-garbage-collector-be.html - ''' - - def __init__(self, interval=1.0, debug=False): - self.debug = debug - if debug: - gc.set_debug(gc.DEBUG_LEAK) - - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.check) - - self.threshold = gc.get_threshold() - gc.disable() - self.timer.start(interval * 1000) - - def check(self): - #return self.debug_cycles() # uncomment to just debug cycles - l0, l1, l2 = gc.get_count() - if self.debug: - print('gc_check called:', l0, l1, l2) - if l0 > self.threshold[0]: - num = gc.collect(0) - if self.debug: - print('collecting gen 0, found: %d unreachable' % num) - if l1 > self.threshold[1]: - num = gc.collect(1) - if self.debug: - print('collecting gen 1, found: %d unreachable' % num) - if l2 > self.threshold[2]: - num = gc.collect(2) - if self.debug: - print('collecting gen 2, found: %d unreachable' % num) - - def debug_cycles(self): - gc.collect() - for obj in gc.garbage: - print(obj, repr(obj), type(obj)) diff --git a/pyqtgraph/util/get_resolution.py b/pyqtgraph/util/get_resolution.py deleted file mode 100644 index 421afab..0000000 --- a/pyqtgraph/util/get_resolution.py +++ /dev/null @@ -1,18 +0,0 @@ -from .. import mkQApp -from ..Qt import QtGui - -def test_screenInformation(): - # a qApp is still needed, otherwise screen is None - qApp = mkQApp() - screen = QtGui.QGuiApplication.primaryScreen() - screens = QtGui.QGuiApplication.screens() - resolution = screen.size() - availableResolution = screen.availableSize() - print("Screen resolution: {}x{}".format(resolution.width(), resolution.height())) - print("Available geometry: {}x{}".format(availableResolution.width(), availableResolution.height())) - print("Number of Screens: {}".format(len(screens))) - return None - - -if __name__ == "__main__": - test_screenInformation() \ No newline at end of file diff --git a/pyqtgraph/util/lru_cache.py b/pyqtgraph/util/lru_cache.py deleted file mode 100644 index 83ca01e..0000000 --- a/pyqtgraph/util/lru_cache.py +++ /dev/null @@ -1,127 +0,0 @@ -import warnings -warnings.warn( - "No longer used in pyqtgraph. Will be removed in 0.13", - DeprecationWarning, stacklevel=2 -) - -import operator -import sys -import itertools - - -_IS_PY3 = sys.version_info[0] == 3 - -class LRUCache(object): - ''' - This LRU cache should be reasonable for short collections (until around 100 items), as it does a - sort on the items if the collection would become too big (so, it is very fast for getting and - setting but when its size would become higher than the max size it does one sort based on the - internal time to decide which items should be removed -- which should be Ok if the resizeTo - isn't too close to the maxSize so that it becomes an operation that doesn't happen all the - time). - ''' - - def __init__(self, maxSize=100, resizeTo=70): - ''' - ============== ========================================================= - **Arguments:** - maxSize (int) This is the maximum size of the cache. When some - item is added and the cache would become bigger than - this, it's resized to the value passed on resizeTo. - resizeTo (int) When a resize operation happens, this is the size - of the final cache. - ============== ========================================================= - ''' - assert resizeTo < maxSize - self.maxSize = maxSize - self.resizeTo = resizeTo - self._counter = 0 - self._dict = {} - if _IS_PY3: - self._nextTime = itertools.count(0).__next__ - else: - self._nextTime = itertools.count(0).next - - def __getitem__(self, key): - item = self._dict[key] - item[2] = self._nextTime() - return item[1] - - def __len__(self): - return len(self._dict) - - def __setitem__(self, key, value): - item = self._dict.get(key) - if item is None: - if len(self._dict) + 1 > self.maxSize: - self._resizeTo() - - item = [key, value, self._nextTime()] - self._dict[key] = item - else: - item[1] = value - item[2] = self._nextTime() - - def __delitem__(self, key): - del self._dict[key] - - def get(self, key, default=None): - try: - return self[key] - except KeyError: - return default - - def clear(self): - self._dict.clear() - - if _IS_PY3: - def values(self): - return [i[1] for i in self._dict.values()] - - def keys(self): - return [x[0] for x in self._dict.values()] - - def _resizeTo(self): - ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resizeTo] - for i in ordered: - del self._dict[i[0]] - - def items(self, accessTime=False): - ''' - :param bool accessTime: - If True sorts the returned items by the internal access time. - ''' - if accessTime: - for x in sorted(self._dict.values(), key=operator.itemgetter(2)): - yield x[0], x[1] - else: - for x in self._dict.items(): - yield x[0], x[1] - - else: - def values(self): - return [i[1] for i in self._dict.values()] - - def keys(self): - return [x[0] for x in self._dict.values()] - - - def _resizeTo(self): - ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resizeTo] - for i in ordered: - del self._dict[i[0]] - - def items(self, accessTime=False): - ''' - ============= ====================================================== - **Arguments** - accessTime (bool) If True sorts the returned items by the - internal access time. - ============= ====================================================== - ''' - if accessTime: - for x in sorted(self._dict.values(), key=operator.itemgetter(2)): - yield x[0], x[1] - else: - for x in self._dict.items(): - yield x[0], x[1] diff --git a/pyqtgraph/util/mutex.py b/pyqtgraph/util/mutex.py deleted file mode 100644 index c03c65c..0000000 --- a/pyqtgraph/util/mutex.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -import traceback -from ..Qt import QtCore - - -class Mutex(QtCore.QMutex): - """ - Subclass of QMutex that provides useful debugging information during - deadlocks--tracebacks are printed for both the code location that is - attempting to lock the mutex as well as the location that has already - acquired the lock. - - Also provides __enter__ and __exit__ methods for use in "with" statements. - """ - def __init__(self, *args, **kargs): - if kargs.get('recursive', False): - args = (QtCore.QMutex.Recursive,) - QtCore.QMutex.__init__(self, *args) - self.l = QtCore.QMutex() ## for serializing access to self.tb - self.tb = [] - self.debug = kargs.pop('debug', False) ## True to enable debugging functions - - def tryLock(self, timeout=None, id=None): - if timeout is None: - locked = QtCore.QMutex.tryLock(self) - else: - locked = QtCore.QMutex.tryLock(self, timeout) - - if self.debug and locked: - self.l.lock() - try: - if id is None: - self.tb.append(''.join(traceback.format_stack()[:-1])) - else: - self.tb.append(" " + str(id)) - #print 'trylock', self, len(self.tb) - finally: - self.l.unlock() - return locked - - def lock(self, id=None): - c = 0 - waitTime = 5000 # in ms - while True: - if self.tryLock(waitTime, id): - break - c += 1 - if self.debug: - self.l.lock() - try: - print("Waiting for mutex lock (%0.1f sec). Traceback follows:" - % (c*waitTime/1000.)) - traceback.print_stack() - if len(self.tb) > 0: - print("Mutex is currently locked from:\n") - print(self.tb[-1]) - else: - print("Mutex is currently locked from [???]") - finally: - self.l.unlock() - #print 'lock', self, len(self.tb) - - def unlock(self): - QtCore.QMutex.unlock(self) - if self.debug: - self.l.lock() - try: - #print 'unlock', self, len(self.tb) - if len(self.tb) > 0: - self.tb.pop() - else: - raise Exception("Attempt to unlock mutex before it has been locked") - finally: - self.l.unlock() - - def acquire(self, blocking=True): - """Mimics threading.Lock.acquire() to allow this class as a drop-in replacement. - """ - return self.tryLock() - - def release(self): - """Mimics threading.Lock.release() to allow this class as a drop-in replacement. - """ - self.unlock() - - def depth(self): - self.l.lock() - n = len(self.tb) - self.l.unlock() - return n - - def traceback(self): - self.l.lock() - try: - ret = self.tb[:] - finally: - self.l.unlock() - return ret - - def __exit__(self, *args): - self.unlock() - - def __enter__(self): - self.lock() - return self - - -class RecursiveMutex(Mutex): - """Mimics threading.RLock class. - """ - def __init__(self, **kwds): - kwds['recursive'] = True - Mutex.__init__(self, **kwds) - diff --git a/pyqtgraph/util/pil_fix.py b/pyqtgraph/util/pil_fix.py deleted file mode 100644 index 6c0985e..0000000 --- a/pyqtgraph/util/pil_fix.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Importing this module installs support for 16-bit images in PIL. -This works by patching objects in the PIL namespace; no files are -modified. -""" - -import warnings -warnings.warn( - "Not used in pyqtgraph. Will be removed in 0.13", - DeprecationWarning, stacklevel=2 -) - -from PIL import Image - -if Image.VERSION == '1.1.7': - Image._MODE_CONV["I;16"] = ('%su2' % Image._ENDIAN, None) - Image._fromarray_typemap[((1, 1), " ndmax: - raise ValueError("Too many dimensions.") - - size = shape[:2][::-1] - if strides is not None: - obj = obj.tostring() - - return frombuffer(mode, size, obj, "raw", mode, 0, 1) - - Image.fromarray=fromarray \ No newline at end of file diff --git a/pyqtgraph/widgets/BusyCursor.py b/pyqtgraph/widgets/BusyCursor.py deleted file mode 100644 index 29fc305..0000000 --- a/pyqtgraph/widgets/BusyCursor.py +++ /dev/null @@ -1,35 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB - -__all__ = ['BusyCursor'] - -class BusyCursor(object): - """Class for displaying a busy mouse cursor during long operations. - Usage:: - - with pyqtgraph.BusyCursor(): - doLongOperation() - - May be nested. If called from a non-gui thread, then the cursor will not be affected. - """ - active = [] - - def __enter__(self): - app = QtCore.QCoreApplication.instance() - isGuiThread = (app is not None) and (QtCore.QThread.currentThread() == app.thread()) - if isGuiThread and QtGui.QApplication.instance() is not None: - if QT_LIB == 'PySide': - # pass CursorShape rather than QCursor for PySide - # see https://bugreports.qt.io/browse/PYSIDE-243 - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - else: - QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) - BusyCursor.active.append(self) - self._active = True - else: - self._active = False - - def __exit__(self, *args): - if self._active: - BusyCursor.active.pop(-1) - if len(BusyCursor.active) == 0: - QtGui.QApplication.restoreOverrideCursor() diff --git a/pyqtgraph/widgets/CheckTable.py b/pyqtgraph/widgets/CheckTable.py deleted file mode 100644 index 2201512..0000000 --- a/pyqtgraph/widgets/CheckTable.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from . import VerticalLabel - -__all__ = ['CheckTable'] - -class CheckTable(QtGui.QWidget): - - sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) - - def __init__(self, columns): - QtGui.QWidget.__init__(self) - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.setLayout(self.layout) - self.headers = [] - self.columns = columns - col = 1 - for c in columns: - label = VerticalLabel.VerticalLabel(c, orientation='vertical') - self.headers.append(label) - self.layout.addWidget(label, 0, col) - col += 1 - - self.rowNames = [] - self.rowWidgets = [] - self.oldRows = {} ## remember settings from removed rows; reapply if they reappear. - - - def updateRows(self, rows): - for r in self.rowNames[:]: - if r not in rows: - self.removeRow(r) - for r in rows: - if r not in self.rowNames: - self.addRow(r) - - def addRow(self, name): - label = QtGui.QLabel(name) - row = len(self.rowNames)+1 - self.layout.addWidget(label, row, 0) - checks = [] - col = 1 - for c in self.columns: - check = QtGui.QCheckBox('') - check.col = c - check.row = name - self.layout.addWidget(check, row, col) - checks.append(check) - if name in self.oldRows: - check.setChecked(self.oldRows[name][col]) - col += 1 - #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) - check.stateChanged.connect(self.checkChanged) - self.rowNames.append(name) - self.rowWidgets.append([label] + checks) - - def removeRow(self, name): - row = self.rowNames.index(name) - self.oldRows[name] = self.saveState()['rows'][row] ## save for later - self.rowNames.pop(row) - for w in self.rowWidgets[row]: - w.setParent(None) - #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) - if isinstance(w, QtGui.QCheckBox): - w.stateChanged.disconnect(self.checkChanged) - self.rowWidgets.pop(row) - for i in range(row, len(self.rowNames)): - widgets = self.rowWidgets[i] - for j in range(len(widgets)): - widgets[j].setParent(None) - self.layout.addWidget(widgets[j], i+1, j) - - def checkChanged(self, state): - check = QtCore.QObject.sender(self) - #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) - self.sigStateChanged.emit(check.row, check.col, state) - - def saveState(self): - rows = [] - for i in range(len(self.rowNames)): - row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] - rows.append(row) - return {'cols': self.columns, 'rows': rows} - - def restoreState(self, state): - rows = [r[0] for r in state['rows']] - self.updateRows(rows) - for r in state['rows']: - rowNum = self.rowNames.index(r[0]) - for i in range(1, len(r)): - self.rowWidgets[rowNum][i].setChecked(r[i]) - diff --git a/pyqtgraph/widgets/ColorButton.py b/pyqtgraph/widgets/ColorButton.py deleted file mode 100644 index 11a5ac1..0000000 --- a/pyqtgraph/widgets/ColorButton.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from .. import functions as functions - -__all__ = ['ColorButton'] - -class ColorButton(QtGui.QPushButton): - """ - **Bases:** QtGui.QPushButton - - Button displaying a color and allowing the user to select a new color. - - ====================== ============================================================ - **Signals:** - sigColorChanging(self) emitted whenever a new color is picked in the color dialog - sigColorChanged(self) emitted when the selected color is accepted (user clicks OK) - ====================== ============================================================ - """ - sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog - sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK) - - def __init__(self, parent=None, color=(128,128,128)): - QtGui.QPushButton.__init__(self, parent) - self.setColor(color) - self.colorDialog = QtGui.QColorDialog() - self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) - self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) - self.colorDialog.currentColorChanged.connect(self.dialogColorChanged) - self.colorDialog.rejected.connect(self.colorRejected) - self.colorDialog.colorSelected.connect(self.colorSelected) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) - self.clicked.connect(self.selectColor) - self.setMinimumHeight(15) - self.setMinimumWidth(15) - - def paintEvent(self, ev): - super().paintEvent(ev) - p = QtGui.QPainter(self) - rect = self.rect().adjusted(6, 6, -6, -6) - ## draw white base, then texture for indicating transparency, then actual color - p.setBrush(functions.mkBrush('w')) - p.drawRect(rect) - p.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) - p.drawRect(rect) - p.setBrush(functions.mkBrush(self._color)) - p.drawRect(rect) - p.end() - - def setColor(self, color, finished=True): - """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" - self._color = functions.mkColor(color) - self.update() - if finished: - self.sigColorChanged.emit(self) - else: - self.sigColorChanging.emit(self) - - def selectColor(self): - self.origColor = self.color() - self.colorDialog.setCurrentColor(self.color()) - self.colorDialog.open() - - def dialogColorChanged(self, color): - if color.isValid(): - self.setColor(color, finished=False) - - def colorRejected(self): - self.setColor(self.origColor, finished=False) - - def colorSelected(self, color): - self.setColor(self._color, finished=True) - - def saveState(self): - return functions.colorTuple(self._color) - - def restoreState(self, state): - self.setColor(state) - - def color(self, mode='qcolor'): - color = functions.mkColor(self._color) - if mode == 'qcolor': - return color - elif mode == 'byte': - return (color.red(), color.green(), color.blue(), color.alpha()) - elif mode == 'float': - return (color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) - - def widgetGroupInterface(self): - return (self.sigColorChanged, ColorButton.saveState, ColorButton.restoreState) - diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py deleted file mode 100644 index 5d1e568..0000000 --- a/pyqtgraph/widgets/ColorMapWidget.py +++ /dev/null @@ -1,276 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import parametertree as ptree -import numpy as np -from collections import OrderedDict -from .. import functions as fn - -__all__ = ['ColorMapWidget'] - -class ColorMapWidget(ptree.ParameterTree): - """ - This class provides a widget allowing the user to customize color mapping - for multi-column data. Given a list of field names, the user may specify - multiple criteria for assigning colors to each record in a numpy record array. - Multiple criteria are evaluated and combined into a single color for each - record by user-defined compositing methods. - - For simpler color mapping using a single gradient editor, see - :class:`GradientWidget ` - """ - sigColorMapChanged = QtCore.Signal(object) - - def __init__(self, parent=None): - ptree.ParameterTree.__init__(self, parent=parent, showHeader=False) - - self.params = ColorMapParameter() - self.setParameters(self.params) - self.params.sigTreeStateChanged.connect(self.mapChanged) - - ## wrap a couple methods - self.setFields = self.params.setFields - self.map = self.params.map - - def mapChanged(self): - self.sigColorMapChanged.emit(self) - - def widgetGroupInterface(self): - return (self.sigColorMapChanged, self.saveState, self.restoreState) - - def saveState(self): - return self.params.saveState() - - def restoreState(self, state): - self.params.restoreState(state) - - def addColorMap(self, name): - """Add a new color mapping and return the created parameter. - """ - return self.params.addNew(name) - - -class ColorMapParameter(ptree.types.GroupParameter): - sigColorMapChanged = QtCore.Signal(object) - - def __init__(self): - self.fields = {} - ptree.types.GroupParameter.__init__(self, name='Color Map', addText='Add Mapping..', addList=[]) - self.sigTreeStateChanged.connect(self.mapChanged) - - def mapChanged(self): - self.sigColorMapChanged.emit(self) - - def addNew(self, name): - fieldSpec = self.fields[name] - - mode = fieldSpec.get('mode', 'range') - if mode == 'range': - item = RangeColorMapItem(name, self.fields[name]) - elif mode == 'enum': - item = EnumColorMapItem(name, self.fields[name]) - - defaults = fieldSpec.get('defaults', {}) - for k, v in defaults.items(): - if k == 'colormap': - if mode == 'range': - item.setValue(v) - elif mode == 'enum': - children = item.param('Values').children() - for i, child in enumerate(children): - try: - child.setValue(v[i]) - except IndexError('No default color set for child %s' % child.name()): - continue - else: - item[k] = v - - self.addChild(item) - return item - - def fieldNames(self): - return list(self.fields.keys()) - - def setFields(self, fields): - """ - Set the list of fields to be used by the mapper. - - The format of *fields* is:: - - [ (fieldName, {options}), ... ] - - ============== ============================================================ - Field Options: - mode Either 'range' or 'enum' (default is range). For 'range', - The user may specify a gradient of colors to be applied - linearly across a specific range of values. For 'enum', - the user specifies a single color for each unique value - (see *values* option). - units String indicating the units of the data for this field. - values List of unique values for which the user may assign a - color when mode=='enum'. Optionally may specify a dict - instead {value: name}. - defaults Dict of default values to apply to color map items when - they are created. Valid keys are 'colormap' to provide - a default color map, or otherwise they a string or tuple - indicating the parameter to be set, such as 'Operation' or - ('Channels..', 'Red'). - ============== ============================================================ - """ - self.fields = OrderedDict(fields) - #self.fields = fields - #self.fields.sort() - names = self.fieldNames() - self.setAddList(names) - - def map(self, data, mode='byte'): - """ - Return an array of colors corresponding to *data*. - - ============== ================================================================= - **Arguments:** - data A numpy record array where the fields in data.dtype match those - defined by a prior call to setFields(). - mode Either 'byte' or 'float'. For 'byte', the method returns an array - of dtype ubyte with values scaled 0-255. For 'float', colors are - returned as 0.0-1.0 float values. - ============== ================================================================= - """ - if isinstance(data, dict): - data = np.array([tuple(data.values())], dtype=[(k, float) for k in data.keys()]) - - colors = np.zeros((len(data),4)) - for item in self.children(): - if not item['Enabled']: - continue - chans = item.param('Channels..') - mask = np.empty((len(data), 4), dtype=bool) - for i,f in enumerate(['Red', 'Green', 'Blue', 'Alpha']): - mask[:,i] = chans[f] - - colors2 = item.map(data) - - op = item['Operation'] - if op == 'Add': - colors[mask] = colors[mask] + colors2[mask] - elif op == 'Multiply': - colors[mask] *= colors2[mask] - elif op == 'Overlay': - a = colors2[:,3:4] - c3 = colors * (1-a) + colors2 * a - c3[:,3:4] = colors[:,3:4] + (1-colors[:,3:4]) * a - colors = c3 - elif op == 'Set': - colors[mask] = colors2[mask] - - colors = np.clip(colors, 0, 1) - if mode == 'byte': - colors = (colors * 255).astype(np.ubyte) - - return colors - - def saveState(self): - items = OrderedDict() - for item in self: - itemState = item.saveState(filter='user') - itemState['field'] = item.fieldName - items[item.name()] = itemState - state = {'fields': self.fields, 'items': items} - return state - - def restoreState(self, state): - if 'fields' in state: - self.setFields(state['fields']) - for name, itemState in state['items'].items(): - item = self.addNew(itemState['field']) - item.restoreState(itemState) - - -class RangeColorMapItem(ptree.types.SimpleParameter): - mapType = 'range' - - def __init__(self, name, opts): - self.fieldName = name - units = opts.get('units', '') - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='colormap', removable=True, renamable=True, - children=[ - #dict(name="Field", type='list', value=name, values=fields), - dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), - dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), - dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), - dict(name='Channels..', type='group', expanded=False, children=[ - dict(name='Red', type='bool', value=True), - dict(name='Green', type='bool', value=True), - dict(name='Blue', type='bool', value=True), - dict(name='Alpha', type='bool', value=True), - ]), - dict(name='Enabled', type='bool', value=True), - dict(name='NaN', type='color'), - ]) - - def map(self, data): - data = data[self.fieldName] - - scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) - cmap = self.value() - colors = cmap.map(scaled, mode='float') - - mask = np.isnan(data) | np.isinf(data) - nanColor = self['NaN'] - nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) - colors[mask] = nanColor - - return colors - -class EnumColorMapItem(ptree.types.GroupParameter): - mapType = 'enum' - - def __init__(self, name, opts): - self.fieldName = name - vals = opts.get('values', []) - if isinstance(vals, list): - vals = OrderedDict([(v,str(v)) for v in vals]) - childs = [{'name': v, 'type': 'color'} for v in vals] - - childs = [] - for val,vname in vals.items(): - ch = ptree.Parameter.create(name=vname, type='color') - ch.maskValue = val - childs.append(ch) - - ptree.types.GroupParameter.__init__(self, - name=name, autoIncrementName=True, removable=True, renamable=True, - children=[ - dict(name='Values', type='group', children=childs), - dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), - dict(name='Channels..', type='group', expanded=False, children=[ - dict(name='Red', type='bool', value=True), - dict(name='Green', type='bool', value=True), - dict(name='Blue', type='bool', value=True), - dict(name='Alpha', type='bool', value=True), - ]), - dict(name='Enabled', type='bool', value=True), - dict(name='Default', type='color'), - ]) - - def map(self, data): - data = data[self.fieldName] - colors = np.empty((len(data), 4)) - default = np.array(fn.colorTuple(self['Default'])) / 255. - colors[:] = default - - for v in self.param('Values'): - mask = data == v.maskValue - c = np.array(fn.colorTuple(v.value())) / 255. - colors[mask] = c - #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) - #cmap = self.value() - #colors = cmap.map(scaled, mode='float') - - #mask = np.isnan(data) | np.isinf(data) - #nanColor = self['NaN'] - #nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) - #colors[mask] = nanColor - - return colors - - diff --git a/pyqtgraph/widgets/ComboBox.py b/pyqtgraph/widgets/ComboBox.py deleted file mode 100644 index 1e29431..0000000 --- a/pyqtgraph/widgets/ComboBox.py +++ /dev/null @@ -1,245 +0,0 @@ -import sys -from ..Qt import QtGui, QtCore -from ..SignalProxy import SignalProxy -from collections import OrderedDict -from ..python2_3 import asUnicode, basestring - - -class ComboBox(QtGui.QComboBox): - """Extends QComboBox to add extra functionality. - - * Handles dict mappings -- user selects a text key, and the ComboBox indicates - the selected value. - * Requires item strings to be unique - * Remembers selected value if list is cleared and subsequently repopulated - * setItems() replaces the items in the ComboBox and blocks signals if the - value ultimately does not change. - """ - - - def __init__(self, parent=None, items=None, default=None): - QtGui.QComboBox.__init__(self, parent) - self.currentIndexChanged.connect(self.indexChanged) - self._ignoreIndexChange = False - - #self.value = default - if 'darwin' in sys.platform: ## because MacOSX can show names that are wider than the comboBox - self.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - #self.setMinimumContentsLength(10) - self._chosenText = None - self._items = OrderedDict() - - if items is not None: - self.setItems(items) - if default is not None: - self.setValue(default) - - def setValue(self, value): - """Set the selected item to the first one having the given value.""" - text = None - for k,v in self._items.items(): - if v == value: - text = k - break - if text is None: - raise ValueError(value) - - self.setText(text) - - def setText(self, text): - """Set the selected item to the first one having the given text.""" - ind = self.findText(text) - if ind == -1: - raise ValueError(text) - #self.value = value - self.setCurrentIndex(ind) - - def value(self): - """ - If items were given as a list of strings, then return the currently - selected text. If items were given as a dict, then return the value - corresponding to the currently selected key. If the combo list is empty, - return None. - """ - if self.count() == 0: - return None - text = asUnicode(self.currentText()) - return self._items[text] - - def ignoreIndexChange(func): - # Decorator that prevents updates to self._chosenText - def fn(self, *args, **kwds): - prev = self._ignoreIndexChange - self._ignoreIndexChange = True - try: - ret = func(self, *args, **kwds) - finally: - self._ignoreIndexChange = prev - return ret - return fn - - def blockIfUnchanged(func): - # decorator that blocks signal emission during complex operations - # and emits currentIndexChanged only if the value has actually - # changed at the end. - def fn(self, *args, **kwds): - prevVal = self.value() - blocked = self.signalsBlocked() - self.blockSignals(True) - try: - ret = func(self, *args, **kwds) - finally: - self.blockSignals(blocked) - - # only emit if the value has changed - if self.value() != prevVal: - self.currentIndexChanged.emit(self.currentIndex()) - - return ret - return fn - - @ignoreIndexChange - @blockIfUnchanged - def setItems(self, items): - """ - *items* may be a list, a tuple, or a dict. - If a dict is given, then the keys are used to populate the combo box - and the values will be used for both value() and setValue(). - """ - prevVal = self.value() - - self.blockSignals(True) - try: - self.clear() - self.addItems(items) - finally: - self.blockSignals(False) - - # only emit if we were not able to re-set the original value - if self.value() != prevVal: - self.currentIndexChanged.emit(self.currentIndex()) - - def items(self): - return self.items.copy() - - def updateList(self, items): - # for backward compatibility - return self.setItems(items) - - def indexChanged(self, index): - # current index has changed; need to remember new 'chosen text' - if self._ignoreIndexChange: - return - self._chosenText = asUnicode(self.currentText()) - - def setCurrentIndex(self, index): - QtGui.QComboBox.setCurrentIndex(self, index) - - def itemsChanged(self): - # try to set the value to the last one selected, if it is available. - if self._chosenText is not None: - try: - self.setText(self._chosenText) - except ValueError: - pass - - @ignoreIndexChange - def insertItem(self, *args): - raise NotImplementedError() - #QtGui.QComboBox.insertItem(self, *args) - #self.itemsChanged() - - @ignoreIndexChange - def insertItems(self, *args): - raise NotImplementedError() - #QtGui.QComboBox.insertItems(self, *args) - #self.itemsChanged() - - @ignoreIndexChange - def addItem(self, *args, **kwds): - # Need to handle two different function signatures for QComboBox.addItem - try: - if isinstance(args[0], basestring): - text = args[0] - if len(args) == 2: - value = args[1] - else: - value = kwds.get('value', text) - else: - text = args[1] - if len(args) == 3: - value = args[2] - else: - value = kwds.get('value', text) - - except IndexError: - raise TypeError("First or second argument of addItem must be a string.") - - if text in self._items: - raise Exception('ComboBox already has item named "%s".' % text) - - self._items[text] = value - QtGui.QComboBox.addItem(self, *args) - self.itemsChanged() - - def setItemValue(self, name, value): - if name not in self._items: - self.addItem(name, value) - else: - self._items[name] = value - - @ignoreIndexChange - @blockIfUnchanged - def addItems(self, items): - if isinstance(items, list) or isinstance(items, tuple): - texts = items - items = dict([(x, x) for x in items]) - elif isinstance(items, dict): - texts = list(items.keys()) - else: - raise TypeError("items argument must be list or dict or tuple (got %s)." % type(items)) - - for t in texts: - if t in self._items: - raise Exception('ComboBox already has item named "%s".' % t) - - - for k,v in items.items(): - self._items[k] = v - QtGui.QComboBox.addItems(self, list(texts)) - - self.itemsChanged() - - @ignoreIndexChange - def clear(self): - self._items = OrderedDict() - QtGui.QComboBox.clear(self) - self.itemsChanged() - - def saveState(self): - ind = self.currentIndex() - data = self.itemData(ind) - #if not data.isValid(): - if data is not None: - try: - if not data.isValid(): - data = None - else: - data = data.toInt()[0] - except AttributeError: - pass - if data is None: - return asUnicode(self.itemText(ind)) - else: - return data - - def restoreState(self, v): - if type(v) is int: - ind = self.findData(v) - if ind > -1: - self.setCurrentIndex(ind) - return - self.setCurrentIndex(self.findText(str(v))) - - def widgetGroupInterface(self): - return (self.currentIndexChanged, self.saveState, self.restoreState) diff --git a/pyqtgraph/widgets/DataFilterWidget.py b/pyqtgraph/widgets/DataFilterWidget.py deleted file mode 100644 index 55af95c..0000000 --- a/pyqtgraph/widgets/DataFilterWidget.py +++ /dev/null @@ -1,209 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import parametertree as ptree -import numpy as np -from collections import OrderedDict -from .. import functions as fn -from ..python2_3 import basestring - -__all__ = ['DataFilterWidget'] - - -class DataFilterWidget(ptree.ParameterTree): - """ - This class allows the user to filter multi-column data sets by specifying - multiple criteria - - Wraps methods from DataFilterParameter: setFields, generateMask, - filterData, and describe. - """ - - sigFilterChanged = QtCore.Signal(object) - - def __init__(self): - ptree.ParameterTree.__init__(self, showHeader=False) - self.params = DataFilterParameter() - - self.setParameters(self.params) - self.params.sigFilterChanged.connect(self.sigFilterChanged) - - self.setFields = self.params.setFields - self.generateMask = self.params.generateMask - self.filterData = self.params.filterData - self.describe = self.params.describe - - def parameters(self): - return self.params - - def addFilter(self, name): - """Add a new filter and return the created parameter item. - """ - return self.params.addNew(name) - - -class DataFilterParameter(ptree.types.GroupParameter): - """A parameter group that specifies a set of filters to apply to tabular data. - """ - sigFilterChanged = QtCore.Signal(object) - - def __init__(self): - self.fields = {} - ptree.types.GroupParameter.__init__(self, name='Data Filter', addText='Add filter..', addList=[]) - self.sigTreeStateChanged.connect(self.filterChanged) - - def filterChanged(self): - self.sigFilterChanged.emit(self) - - def addNew(self, name): - mode = self.fields[name].get('mode', 'range') - if mode == 'range': - child = self.addChild(RangeFilterItem(name, self.fields[name])) - elif mode == 'enum': - child = self.addChild(EnumFilterItem(name, self.fields[name])) - return child - - def fieldNames(self): - return self.fields.keys() - - def setFields(self, fields): - """Set the list of fields that are available to be filtered. - - *fields* must be a dict or list of tuples that maps field names - to a specification describing the field. Each specification is - itself a dict with either ``'mode':'range'`` or ``'mode':'enum'``:: - - filter.setFields([ - ('field1', {'mode': 'range'}), - ('field2', {'mode': 'enum', 'values': ['val1', 'val2', 'val3']}), - ('field3', {'mode': 'enum', 'values': {'val1':True, 'val2':False, 'val3':True}}), - ]) - """ - with fn.SignalBlock(self.sigTreeStateChanged, self.filterChanged): - self.fields = OrderedDict(fields) - names = self.fieldNames() - self.setAddList(names) - - # update any existing filters - for ch in self.children(): - name = ch.fieldName - if name in fields: - ch.updateFilter(fields[name]) - self.sigFilterChanged.emit(self) - - def filterData(self, data): - if len(data) == 0: - return data - return data[self.generateMask(data)] - - def generateMask(self, data): - """Return a boolean mask indicating whether each item in *data* passes - the filter critera. - """ - mask = np.ones(len(data), dtype=bool) - if len(data) == 0: - return mask - for fp in self: - if fp.value() is False: - continue - mask &= fp.generateMask(data, mask.copy()) - #key, mn, mx = fp.fieldName, fp['Min'], fp['Max'] - - #vals = data[key] - #mask &= (vals >= mn) - #mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections - return mask - - def describe(self): - """Return a list of strings describing the currently enabled filters.""" - desc = [] - for fp in self: - if fp.value() is False: - continue - desc.append(fp.describe()) - return desc - - -class RangeFilterItem(ptree.types.SimpleParameter): - def __init__(self, name, opts): - self.fieldName = name - units = opts.get('units', '') - self.units = units - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, - children=[ - #dict(name="Field", type='list', value=name, values=fields), - dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), - dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), - ]) - - def generateMask(self, data, mask): - vals = data[self.fieldName][mask] - mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections - return mask - - def describe(self): - return "%s < %s < %s" % (fn.siFormat(self['Min'], suffix=self.units), self.fieldName, fn.siFormat(self['Max'], suffix=self.units)) - - def updateFilter(self, opts): - pass - - -class EnumFilterItem(ptree.types.SimpleParameter): - def __init__(self, name, opts): - self.fieldName = name - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True) - self.setEnumVals(opts) - - def generateMask(self, data, startMask): - vals = data[self.fieldName][startMask] - mask = np.ones(len(vals), dtype=bool) - otherMask = np.ones(len(vals), dtype=bool) - for c in self: - key = c.maskValue - if key == '__other__': - m = ~otherMask - else: - m = vals != key - otherMask &= m - if c.value() is False: - mask &= m - startMask[startMask] = mask - return startMask - - def describe(self): - vals = [ch.name() for ch in self if ch.value() is True] - return "%s: %s" % (self.fieldName, ', '.join(vals)) - - def updateFilter(self, opts): - self.setEnumVals(opts) - - def setEnumVals(self, opts): - vals = opts.get('values', {}) - - prevState = {} - for ch in self.children(): - prevState[ch.name()] = ch.value() - self.removeChild(ch) - - if not isinstance(vals, dict): - vals = OrderedDict([(v,(str(v), True)) for v in vals]) - - # Each filterable value can come with either (1) a string name, (2) a bool - # indicating whether the value is enabled by default, or (3) a tuple providing - # both. - for val,valopts in vals.items(): - if isinstance(valopts, bool): - enabled = valopts - vname = str(val) - elif isinstance(valopts, basestring): - enabled = True - vname = valopts - elif isinstance(valopts, tuple): - vname, enabled = valopts - - ch = ptree.Parameter.create(name=vname, type='bool', value=prevState.get(vname, enabled)) - ch.maskValue = val - self.addChild(ch) - ch = ptree.Parameter.create(name='(other)', type='bool', value=prevState.get('(other)', True)) - ch.maskValue = '__other__' - self.addChild(ch) diff --git a/pyqtgraph/widgets/DataTreeWidget.py b/pyqtgraph/widgets/DataTreeWidget.py deleted file mode 100644 index 27b979a..0000000 --- a/pyqtgraph/widgets/DataTreeWidget.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from collections import OrderedDict -from .TableWidget import TableWidget -from ..python2_3 import asUnicode -import types, traceback -import numpy as np - -try: - import metaarray - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - -__all__ = ['DataTreeWidget'] - -class DataTreeWidget(QtGui.QTreeWidget): - """ - Widget for displaying hierarchical python data structures - (eg, nested dicts, lists, and arrays) - """ - def __init__(self, parent=None, data=None): - QtGui.QTreeWidget.__init__(self, parent) - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setData(data) - self.setColumnCount(3) - self.setHeaderLabels(['key / index', 'type', 'value']) - self.setAlternatingRowColors(True) - - def setData(self, data, hideRoot=False): - """data should be a dictionary.""" - self.clear() - self.widgets = [] - self.nodes = {} - self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) - self.expandToDepth(3) - self.resizeColumnToContents(0) - - def buildTree(self, data, parent, name='', hideRoot=False, path=()): - if hideRoot: - node = parent - else: - node = QtGui.QTreeWidgetItem([name, "", ""]) - parent.addChild(node) - - # record the path to the node so it can be retrieved later - # (this is used by DiffTreeWidget) - self.nodes[path] = node - - typeStr, desc, childs, widget = self.parse(data) - node.setText(1, typeStr) - node.setText(2, desc) - - # Truncate description and add text box if needed - if len(desc) > 100: - desc = desc[:97] + '...' - if widget is None: - widget = QtGui.QPlainTextEdit(asUnicode(data)) - widget.setMaximumHeight(200) - widget.setReadOnly(True) - - # Add widget to new subnode - if widget is not None: - self.widgets.append(widget) - subnode = QtGui.QTreeWidgetItem(["", "", ""]) - node.addChild(subnode) - self.setItemWidget(subnode, 0, widget) - subnode.setFirstColumnSpanned(True) - - # recurse to children - for key, data in childs.items(): - self.buildTree(data, node, asUnicode(key), path=path+(key,)) - - def parse(self, data): - """ - Given any python object, return: - * type - * a short string representation - * a dict of sub-objects to be parsed - * optional widget to display as sub-node - """ - # defaults for all objects - typeStr = type(data).__name__ - if typeStr == 'instance': - typeStr += ": " + data.__class__.__name__ - widget = None - desc = "" - childs = {} - - # type-specific changes - if isinstance(data, dict): - desc = "length=%d" % len(data) - if isinstance(data, OrderedDict): - childs = data - else: - try: - childs = OrderedDict(sorted(data.items())) - except TypeError: # if sorting falls - childs = OrderedDict(data.items()) - elif isinstance(data, (list, tuple)): - desc = "length=%d" % len(data) - childs = OrderedDict(enumerate(data)) - elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - childs = OrderedDict([ - ('data', data.view(np.ndarray)), - ('meta', data.infoCopy()) - ]) - elif isinstance(data, np.ndarray): - desc = "shape=%s dtype=%s" % (data.shape, data.dtype) - table = TableWidget() - table.setData(data) - table.setMaximumHeight(200) - widget = table - elif isinstance(data, types.TracebackType): ## convert traceback to a list of strings - frames = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) - #childs = OrderedDict([ - #(i, {'file': child[0], 'line': child[1], 'function': child[2], 'code': child[3]}) - #for i, child in enumerate(frames)]) - #childs = OrderedDict([(i, ch) for i,ch in enumerate(frames)]) - widget = QtGui.QPlainTextEdit(asUnicode('\n'.join(frames))) - widget.setMaximumHeight(200) - widget.setReadOnly(True) - else: - desc = asUnicode(data) - - return typeStr, desc, childs, widget - diff --git a/pyqtgraph/widgets/DiffTreeWidget.py b/pyqtgraph/widgets/DiffTreeWidget.py deleted file mode 100644 index 53f575e..0000000 --- a/pyqtgraph/widgets/DiffTreeWidget.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from collections import OrderedDict -from .DataTreeWidget import DataTreeWidget -from .. import functions as fn -import types, traceback -import numpy as np - -__all__ = ['DiffTreeWidget'] - - -class DiffTreeWidget(QtGui.QWidget): - """ - Widget for displaying differences between hierarchical python data structures - (eg, nested dicts, lists, and arrays) - """ - def __init__(self, parent=None, a=None, b=None): - QtGui.QWidget.__init__(self, parent) - self.layout = QtGui.QHBoxLayout() - self.setLayout(self.layout) - self.trees = [DataTreeWidget(self), DataTreeWidget(self)] - for t in self.trees: - self.layout.addWidget(t) - if a is not None: - self.setData(a, b) - - def setData(self, a, b): - """ - Set the data to be compared in this widget. - """ - self.data = (a, b) - self.trees[0].setData(a) - self.trees[1].setData(b) - - return self.compare(a, b) - - def compare(self, a, b, path=()): - """ - Compare data structure *a* to structure *b*. - - Return True if the objects match completely. - Otherwise, return a structure that describes the differences: - - { 'type': bool - 'len': bool, - 'str': bool, - 'shape': bool, - 'dtype': bool, - 'mask': array, - } - - - """ - bad = (255, 200, 200) - diff = [] - # generate typestr, desc, childs for each object - typeA, descA, childsA, _ = self.trees[0].parse(a) - typeB, descB, childsB, _ = self.trees[1].parse(b) - - if typeA != typeB: - self.setColor(path, 1, bad) - if descA != descB: - self.setColor(path, 2, bad) - - if isinstance(a, dict) and isinstance(b, dict): - keysA = set(a.keys()) - keysB = set(b.keys()) - for key in keysA - keysB: - self.setColor(path+(key,), 0, bad, tree=0) - for key in keysB - keysA: - self.setColor(path+(key,), 0, bad, tree=1) - for key in keysA & keysB: - self.compare(a[key], b[key], path+(key,)) - - elif isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)): - for i in range(max(len(a), len(b))): - if len(a) <= i: - self.setColor(path+(i,), 0, bad, tree=1) - elif len(b) <= i: - self.setColor(path+(i,), 0, bad, tree=0) - else: - self.compare(a[i], b[i], path+(i,)) - - elif isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and a.shape == b.shape: - tableNodes = [tree.nodes[path].child(0) for tree in self.trees] - if a.dtype.fields is None and b.dtype.fields is None: - eq = self.compareArrays(a, b) - if not np.all(eq): - for n in tableNodes: - n.setBackground(0, fn.mkBrush(bad)) - #for i in np.argwhere(~eq): - - else: - if a.dtype == b.dtype: - for i,k in enumerate(a.dtype.fields.keys()): - eq = self.compareArrays(a[k], b[k]) - if not np.all(eq): - for n in tableNodes: - n.setBackground(0, fn.mkBrush(bad)) - #for j in np.argwhere(~eq): - - # dict: compare keys, then values where keys match - # list: - # array: compare elementwise for same shape - - def compareArrays(self, a, b): - intnan = -9223372036854775808 # happens when np.nan is cast to int - anans = np.isnan(a) | (a == intnan) - bnans = np.isnan(b) | (b == intnan) - eq = anans == bnans - mask = ~anans - eq[mask] = np.allclose(a[mask], b[mask]) - return eq - - def setColor(self, path, column, color, tree=None): - brush = fn.mkBrush(color) - - # Color only one tree if specified. - if tree is None: - trees = self.trees - else: - trees = [self.trees[tree]] - - for tree in trees: - item = tree.nodes[path] - item.setBackground(column, brush) - - def _compare(self, a, b): - """ - Compare data structure *a* to structure *b*. - """ - # Check test structures are the same - assert type(info) is type(expect) - if hasattr(info, '__len__'): - assert len(info) == len(expect) - - if isinstance(info, dict): - for k in info: - assert k in expect - for k in expect: - assert k in info - self.compare_results(info[k], expect[k]) - elif isinstance(info, list): - for i in range(len(info)): - self.compare_results(info[i], expect[i]) - elif isinstance(info, np.ndarray): - assert info.shape == expect.shape - assert info.dtype == expect.dtype - if info.dtype.fields is None: - intnan = -9223372036854775808 # happens when np.nan is cast to int - inans = np.isnan(info) | (info == intnan) - enans = np.isnan(expect) | (expect == intnan) - assert np.all(inans == enans) - mask = ~inans - assert np.allclose(info[mask], expect[mask]) - else: - for k in info.dtype.fields.keys(): - self.compare_results(info[k], expect[k]) - else: - try: - assert info == expect - except Exception: - raise NotImplementedError("Cannot compare objects of type %s" % type(info)) - \ No newline at end of file diff --git a/pyqtgraph/widgets/FeedbackButton.py b/pyqtgraph/widgets/FeedbackButton.py deleted file mode 100644 index 30114d4..0000000 --- a/pyqtgraph/widgets/FeedbackButton.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui - -__all__ = ['FeedbackButton'] - -class FeedbackButton(QtGui.QPushButton): - """ - QPushButton which flashes success/failure indication for slow or asynchronous procedures. - """ - - - ### For thread-safetyness - sigCallSuccess = QtCore.Signal(object, object, object) - sigCallFailure = QtCore.Signal(object, object, object) - sigCallProcess = QtCore.Signal(object, object, object) - sigReset = QtCore.Signal() - - def __init__(self, *args): - QtGui.QPushButton.__init__(self, *args) - self.origStyle = None - self.origText = self.text() - self.origStyle = self.styleSheet() - self.origTip = self.toolTip() - self.limitedTime = True - - - #self.textTimer = QtCore.QTimer() - #self.tipTimer = QtCore.QTimer() - #self.textTimer.timeout.connect(self.setText) - #self.tipTimer.timeout.connect(self.setToolTip) - - self.sigCallSuccess.connect(self.success) - self.sigCallFailure.connect(self.failure) - self.sigCallProcess.connect(self.processing) - self.sigReset.connect(self.reset) - - - def feedback(self, success, message=None, tip="", limitedTime=True): - """Calls success() or failure(). If you want the message to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action.Threadsafe.""" - if success: - self.success(message, tip, limitedTime=limitedTime) - else: - self.failure(message, tip, limitedTime=limitedTime) - - def success(self, message=None, tip="", limitedTime=True): - """Displays specified message on button and flashes button green to let user know action was successful. If you want the success to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe.""" - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(True) - #print "success" - self.startBlink("#0F0", message, tip, limitedTime=limitedTime) - else: - self.sigCallSuccess.emit(message, tip, limitedTime) - - def failure(self, message=None, tip="", limitedTime=True): - """Displays specified message on button and flashes button red to let user know there was an error. If you want the error to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe. """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(True) - #print "fail" - self.startBlink("#F00", message, tip, limitedTime=limitedTime) - else: - self.sigCallFailure.emit(message, tip, limitedTime) - - def processing(self, message="Processing..", tip="", processEvents=True): - """Displays specified message on button to let user know the action is in progress. Threadsafe. """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(False) - self.setText(message, temporary=True) - self.setToolTip(tip, temporary=True) - if processEvents: - QtGui.QApplication.processEvents() - else: - self.sigCallProcess.emit(message, tip, processEvents) - - - def reset(self): - """Resets the button to its original text and style. Threadsafe.""" - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.limitedTime = True - self.setText() - self.setToolTip() - self.setStyleSheet() - else: - self.sigReset.emit() - - def startBlink(self, color, message=None, tip="", limitedTime=True): - #if self.origStyle is None: - #self.origStyle = self.styleSheet() - #self.origText = self.text() - self.setFixedHeight(self.height()) - - if message is not None: - self.setText(message, temporary=True) - self.setToolTip(tip, temporary=True) - self.count = 0 - #self.indStyle = "QPushButton {border: 2px solid %s; border-radius: 5px}" % color - self.indStyle = "QPushButton {background-color: %s}" % color - self.limitedTime = limitedTime - self.borderOn() - if limitedTime: - QtCore.QTimer.singleShot(2000, self.setText) - QtCore.QTimer.singleShot(10000, self.setToolTip) - - def borderOn(self): - self.setStyleSheet(self.indStyle, temporary=True) - if self.limitedTime or self.count <=2: - QtCore.QTimer.singleShot(100, self.borderOff) - - - def borderOff(self): - self.setStyleSheet() - self.count += 1 - if self.count >= 2: - if self.limitedTime: - return - QtCore.QTimer.singleShot(30, self.borderOn) - - - def setText(self, text=None, temporary=False): - if text is None: - text = self.origText - #print text - QtGui.QPushButton.setText(self, text) - if not temporary: - self.origText = text - - def setToolTip(self, text=None, temporary=False): - if text is None: - text = self.origTip - QtGui.QPushButton.setToolTip(self, text) - if not temporary: - self.origTip = text - - def setStyleSheet(self, style=None, temporary=False): - if style is None: - style = self.origStyle - QtGui.QPushButton.setStyleSheet(self, style) - if not temporary: - self.origStyle = style - - -if __name__ == '__main__': - import time - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - btn = FeedbackButton("Button") - fail = True - def click(): - btn.processing("Hold on..") - time.sleep(2.0) - - global fail - fail = not fail - if fail: - btn.failure(message="FAIL.", tip="There was a failure. Get over it.") - else: - btn.success(message="Bueno!") - btn.clicked.connect(click) - win.setCentralWidget(btn) - win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/FileDialog.py b/pyqtgraph/widgets/FileDialog.py deleted file mode 100644 index faa0994..0000000 --- a/pyqtgraph/widgets/FileDialog.py +++ /dev/null @@ -1,14 +0,0 @@ -from ..Qt import QtGui, QtCore -import sys - -__all__ = ['FileDialog'] - -class FileDialog(QtGui.QFileDialog): - ## Compatibility fix for OSX: - ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog - - def __init__(self, *args): - QtGui.QFileDialog.__init__(self, *args) - - if sys.platform == 'darwin': - self.setOption(QtGui.QFileDialog.DontUseNativeDialog) \ No newline at end of file diff --git a/pyqtgraph/widgets/GradientWidget.py b/pyqtgraph/widgets/GradientWidget.py deleted file mode 100644 index c5396d7..0000000 --- a/pyqtgraph/widgets/GradientWidget.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore, QtWidgets, QT_LIB -from .GraphicsView import GraphicsView -from ..graphicsItems.GradientEditorItem import GradientEditorItem -import weakref -import numpy as np - -__all__ = ['GradientWidget'] - - -class GradientWidget(GraphicsView): - """ - Widget displaying an editable color gradient. The user may add, move, recolor, - or remove colors from the gradient. Additionally, a context menu allows the - user to select from pre-defined gradients. - """ - sigGradientChanged = QtCore.Signal(object) - sigGradientChangeFinished = QtCore.Signal(object) - - def __init__(self, parent=None, orientation='bottom', *args, **kargs): - """ - The *orientation* argument may be 'bottom', 'top', 'left', or 'right' - indicating whether the gradient is displayed horizontally (top, bottom) - or vertically (left, right) and on what side of the gradient the editable - ticks will appear. - - All other arguments are passed to - :func:`GradientEditorItem.__init__ `. - - Note: For convenience, this class wraps methods from - :class:`GradientEditorItem `. - """ - GraphicsView.__init__(self, parent, useOpenGL=False, background=None) - self.maxDim = 31 - kargs['tickPen'] = 'k' - self.item = GradientEditorItem(*args, **kargs) - self.item.sigGradientChanged.connect(self.sigGradientChanged) - self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished) - self.setCentralItem(self.item) - self.setOrientation(orientation) - self.setCacheMode(self.CacheNone) - self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) - - if QT_LIB == 'PyQt6': - # PyQt6 doesn't allow or-ing of different enum types - # so we need to take its value property - NoFrame = QtWidgets.QFrame.Shape.NoFrame.value - Plain = QtWidgets.QFrame.Shadow.Plain.value - else: - NoFrame = QtWidgets.QFrame.NoFrame - Plain = QtWidgets.QFrame.Plain - frame_style = NoFrame | Plain - - self.setFrameStyle(frame_style) - #self.setBackgroundRole(QtGui.QPalette.NoRole) - #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) - #self.setAutoFillBackground(False) - #self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False) - #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) - - def setOrientation(self, ort): - """Set the orientation of the widget. May be one of 'bottom', 'top', - 'left', or 'right'.""" - self.item.setOrientation(ort) - self.orientation = ort - self.setMaxDim() - - def setMaxDim(self, mx=None): - if mx is None: - mx = self.maxDim - else: - self.maxDim = mx - - if self.orientation in ['bottom', 'top']: - self.setFixedHeight(mx) - self.setMaximumWidth(16777215) - else: - self.setFixedWidth(mx) - self.setMaximumHeight(16777215) - - def __getattr__(self, attr): - ### wrap methods from GradientEditorItem - return getattr(self.item, attr) - - def widgetGroupInterface(self): - return (self.sigGradientChanged, self.saveState, self.restoreState) - - diff --git a/pyqtgraph/widgets/GraphicsLayoutWidget.py b/pyqtgraph/widgets/GraphicsLayoutWidget.py deleted file mode 100644 index 6249ba2..0000000 --- a/pyqtgraph/widgets/GraphicsLayoutWidget.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, mkQApp -from ..graphicsItems.GraphicsLayout import GraphicsLayout -from .GraphicsView import GraphicsView - -__all__ = ['GraphicsLayoutWidget'] -class GraphicsLayoutWidget(GraphicsView): - """ - Convenience class consisting of a :class:`GraphicsView - ` with a single :class:`GraphicsLayout - ` as its central item. - - This widget is an easy starting point for generating multi-panel figures. - Example:: - - w = pg.GraphicsLayoutWidget() - p1 = w.addPlot(row=0, col=0) - p2 = w.addPlot(row=0, col=1) - v = w.addViewBox(row=1, col=0, colspan=2) - - ========= ================================================================= - parent (QWidget or None) The parent widget. - show (bool) If True, then immediately show the widget after it is - created. If the widget has no parent, then it will be shown - inside a new window. - size (width, height) tuple. Optionally resize the widget. Note: if - this widget is placed inside a layout, then this argument has no - effect. - title (str or None) If specified, then set the window title for this - widget. - kargs All extra arguments are passed to - :meth:`GraphicsLayout.__init__ - ` - ========= ================================================================= - - - This class wraps several methods from its internal GraphicsLayout: - :func:`nextRow ` - :func:`nextColumn ` - :func:`addPlot ` - :func:`addViewBox ` - :func:`addItem ` - :func:`getItem ` - :func:`addLabel ` - :func:`addLayout ` - :func:`removeItem ` - :func:`itemIndex ` - :func:`clear ` - """ - def __init__(self, parent=None, show=False, size=None, title=None, **kargs): - mkQApp() - GraphicsView.__init__(self, parent) - self.ci = GraphicsLayout(**kargs) - for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear']: - setattr(self, n, getattr(self.ci, n)) - self.setCentralItem(self.ci) - - if size is not None: - self.resize(*size) - - if title is not None: - self.setWindowTitle(title) - - if show is True: - self.show() diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py deleted file mode 100644 index 3054f0e..0000000 --- a/pyqtgraph/widgets/GraphicsView.py +++ /dev/null @@ -1,403 +0,0 @@ -# -*- coding: utf-8 -*- -""" -GraphicsView.py - Extension of QGraphicsView -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from ..Qt import QtCore, QtGui, QtWidgets, QT_LIB -from ..Point import Point -import sys, os -from .FileDialog import FileDialog -from ..GraphicsScene import GraphicsScene -import numpy as np -from .. import functions as fn -from .. import debug as debug -from .. import getConfigOption - -__all__ = ['GraphicsView'] - - -class GraphicsView(QtGui.QGraphicsView): - """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the - viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget - that is automatically scaled to the full view geometry. - - This widget is the basis for :class:`PlotWidget `, - :class:`GraphicsLayoutWidget `, and the view widget in - :class:`ImageView `. - - By default, the view coordinate system matches the widget's pixel coordinates and - automatically updates when the view is resized. This can be overridden by setting - autoPixelRange=False. The exact visible range can be set with setRange(). - - The view can be panned using the middle mouse button and scaled using the right mouse button if - enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality).""" - - sigDeviceRangeChanged = QtCore.Signal(object, object) - sigDeviceTransformChanged = QtCore.Signal(object) - sigMouseReleased = QtCore.Signal(object) - sigSceneMouseMoved = QtCore.Signal(object) - #sigRegionChanged = QtCore.Signal(object) - sigScaleChanged = QtCore.Signal(object) - lastFileDir = None - - def __init__(self, parent=None, useOpenGL=None, background='default'): - """ - ============== ============================================================ - **Arguments:** - parent Optional parent widget - useOpenGL If True, the GraphicsView will use OpenGL to do all of its - rendering. This can improve performance on some systems, - but may also introduce bugs (the combination of - QGraphicsView and QOpenGLWidget is still an 'experimental' - feature of Qt) - background Set the background color of the GraphicsView. Accepts any - single argument accepted by - :func:`mkColor `. By - default, the background color is determined using the - 'backgroundColor' configuration option (see - :func:`setConfigOptions `). - ============== ============================================================ - """ - - self.closed = False - - QtGui.QGraphicsView.__init__(self, parent) - - # This connects a cleanup function to QApplication.aboutToQuit. It is - # called from here because we have no good way to react when the - # QApplication is created by the user. - # See pyqtgraph.__init__.py - from .. import _connectCleanup - _connectCleanup() - - if useOpenGL is None: - useOpenGL = getConfigOption('useOpenGL') - - self.useOpenGL(useOpenGL) - - self.setCacheMode(self.CacheBackground) - - ## This might help, but it's probably dangerous in the general case.. - #self.setOptimizationFlag(self.DontSavePainterState, True) - - self.setBackgroundRole(QtGui.QPalette.NoRole) - self.setBackground(background) - - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setFrameShape(QtGui.QFrame.NoFrame) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) - self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) - self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) - - - self.lockedViewports = [] - self.lastMousePos = None - self.setMouseTracking(True) - self.aspectLocked = False - self.range = QtCore.QRectF(0, 0, 1, 1) - self.autoPixelRange = True - self.currentItem = None - self.clearMouse() - self.updateMatrix() - # GraphicsScene must have parent or expect crashes! - self.sceneObj = GraphicsScene(parent=self) - self.setScene(self.sceneObj) - - ## Workaround for PySide crash - ## This ensures that the scene will outlive the view. - if QT_LIB == 'PySide': - self.sceneObj._view_ref_workaround = self - - ## by default we set up a central widget with a grid layout. - ## this can be replaced if needed. - self.centralWidget = None - self.setCentralItem(QtGui.QGraphicsWidget()) - self.centralLayout = QtGui.QGraphicsGridLayout() - self.centralWidget.setLayout(self.centralLayout) - - self.mouseEnabled = False - self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False) - self.clickAccepted = False - - def setAntialiasing(self, aa): - """Enable or disable default antialiasing. - Note that this will only affect items that do not specify their own antialiasing options.""" - if aa: - self.setRenderHints(self.renderHints() | QtGui.QPainter.Antialiasing) - else: - self.setRenderHints(self.renderHints() & ~QtGui.QPainter.Antialiasing) - - def setBackground(self, background): - """ - Set the background color of the GraphicsView. - To use the defaults specified py pyqtgraph.setConfigOption, use background='default'. - To make the background transparent, use background=None. - """ - self._background = background - if background == 'default': - background = getConfigOption('background') - brush = fn.mkBrush(background) - self.setBackgroundBrush(brush) - - def paintEvent(self, ev): - self.scene().prepareForPaint() - return super().paintEvent(ev) - - def render(self, *args, **kwds): - self.scene().prepareForPaint() - return super().render(*args, **kwds) - - - def close(self): - self.centralWidget = None - self.scene().clear() - self.currentItem = None - self.sceneObj = None - self.closed = True - self.setViewport(None) - super(GraphicsView, self).close() - - def useOpenGL(self, b=True): - if b: - HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') - if not HAVE_OPENGL: - raise Exception("Requested to use OpenGL with QGraphicsView, but QOpenGLWidget is not available.") - - v = QtWidgets.QOpenGLWidget() - else: - v = QtGui.QWidget() - - self.setViewport(v) - - def keyPressEvent(self, ev): - self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene - ## (view likes to eat arrow key events) - - - def setCentralItem(self, item): - return self.setCentralWidget(item) - - def setCentralWidget(self, item): - """Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically - resize whenever the GraphicsView is resized).""" - if self.centralWidget is not None: - self.scene().removeItem(self.centralWidget) - self.centralWidget = item - if item is not None: - self.sceneObj.addItem(item) - self.resizeEvent(None) - - def addItem(self, *args): - return self.scene().addItem(*args) - - def removeItem(self, *args): - return self.scene().removeItem(*args) - - def enableMouse(self, b=True): - self.mouseEnabled = b - self.autoPixelRange = (not b) - - def clearMouse(self): - self.mouseTrail = [] - self.lastButtonReleased = None - - def resizeEvent(self, ev): - if self.closed: - return - if self.autoPixelRange: - self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) - GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. - self.updateMatrix() - - def updateMatrix(self, propagate=True): - self.setSceneRect(self.range) - if self.autoPixelRange: - self.resetTransform() - else: - if self.aspectLocked: - self.fitInView(self.range, QtCore.Qt.KeepAspectRatio) - else: - self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) - - if propagate: - for v in self.lockedViewports: - v.setXRange(self.range, padding=0) - - self.sigDeviceRangeChanged.emit(self, self.range) - self.sigDeviceTransformChanged.emit(self) - - def viewRect(self): - """Return the boundaries of the view in scene coordinates""" - ## easier to just return self.range ? - r = QtCore.QRectF(self.rect()) - return self.viewportTransform().inverted()[0].mapRect(r) - - def visibleRange(self): - ## for backward compatibility - return self.viewRect() - - def translate(self, dx, dy): - self.range.adjust(dx, dy, dx, dy) - self.updateMatrix() - - def scale(self, sx, sy, center=None): - scale = [sx, sy] - if self.aspectLocked: - scale[0] = scale[1] - - if self.scaleCenter: - center = None - if center is None: - center = self.range.center() - - w = self.range.width() / scale[0] - h = self.range.height() / scale[1] - self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) - - self.updateMatrix() - self.sigScaleChanged.emit(self) - - def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): - if disableAutoPixel: - self.autoPixelRange=False - if newRect is None: - newRect = self.visibleRange() - padding = 0 - - padding = Point(padding) - newRect = QtCore.QRectF(newRect) - pw = newRect.width() * padding[0] - ph = newRect.height() * padding[1] - newRect = newRect.adjusted(-pw, -ph, pw, ph) - scaleChanged = False - if self.range.width() != newRect.width() or self.range.height() != newRect.height(): - scaleChanged = True - self.range = newRect - #print "New Range:", self.range - if self.centralWidget is not None: - self.centralWidget.setGeometry(self.range) - self.updateMatrix(propagate) - if scaleChanged: - self.sigScaleChanged.emit(self) - - def scaleToImage(self, image): - """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" - pxSize = image.pixelSize() - image.setPxMode(True) - try: - self.sigScaleChanged.disconnect(image.setScaledMode) - except (TypeError, RuntimeError): - pass - tl = image.sceneBoundingRect().topLeft() - w = self.size().width() * pxSize[0] - h = self.size().height() * pxSize[1] - range = QtCore.QRectF(tl.x(), tl.y(), w, h) - GraphicsView.setRange(self, range, padding=0) - self.sigScaleChanged.connect(image.setScaledMode) - - - - def lockXRange(self, v1): - if not v1 in self.lockedViewports: - self.lockedViewports.append(v1) - - def setXRange(self, r, padding=0.05): - r1 = QtCore.QRectF(self.range) - r1.setLeft(r.left()) - r1.setRight(r.right()) - GraphicsView.setRange(self, r1, padding=[padding, 0], propagate=False) - - def setYRange(self, r, padding=0.05): - r1 = QtCore.QRectF(self.range) - r1.setTop(r.top()) - r1.setBottom(r.bottom()) - GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) - - def wheelEvent(self, ev): - super().wheelEvent(ev) - if not self.mouseEnabled: - return - delta = 0 - if QT_LIB in ['PyQt4', 'PySide']: - delta = ev.delta() - else: - delta = ev.angleDelta().x() - if delta == 0: - delta = ev.angleDelta().y() - - sc = 1.001 ** delta - #self.scale *= sc - #self.updateMatrix() - self.scale(sc, sc) - - def setAspectLocked(self, s): - self.aspectLocked = s - - def leaveEvent(self, ev): - self.scene().leaveEvent(ev) ## inform scene when mouse leaves - - def mousePressEvent(self, ev): - super().mousePressEvent(ev) - - - if not self.mouseEnabled: - return - lpos = ev.localPos() - self.lastMousePos = lpos - self.mousePressPos = lpos - self.clickAccepted = ev.isAccepted() - if not self.clickAccepted: - self.scene().clearSelection() - return ## Everything below disabled for now.. - - def mouseReleaseEvent(self, ev): - super().mouseReleaseEvent(ev) - if not self.mouseEnabled: - return - self.sigMouseReleased.emit(ev) - self.lastButtonReleased = ev.button() - return ## Everything below disabled for now.. - - def mouseMoveEvent(self, ev): - lpos = ev.localPos() - if self.lastMousePos is None: - self.lastMousePos = lpos - delta = Point(lpos - self.lastMousePos) - self.lastMousePos = lpos - - super().mouseMoveEvent(ev) - if not self.mouseEnabled: - return - self.sigSceneMouseMoved.emit(self.mapToScene(lpos)) - - if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. - return - - if ev.buttons() == QtCore.Qt.RightButton: - delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) - scale = 1.01 ** delta - self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) - self.sigDeviceRangeChanged.emit(self, self.range) - - elif ev.buttons() in [QtCore.Qt.MiddleButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button. - px = self.pixelSize() - tr = -delta * px - - self.translate(tr[0], tr[1]) - self.sigDeviceRangeChanged.emit(self, self.range) - - def pixelSize(self): - """Return vector with the length and width of one view pixel in scene coordinates""" - p0 = Point(0,0) - p1 = Point(1,1) - tr = self.transform().inverted()[0] - p01 = tr.map(p0) - p11 = tr.map(p1) - return Point(p11 - p01) - - def dragEnterEvent(self, ev): - ev.ignore() ## not sure why, but for some reason this class likes to consume drag events diff --git a/pyqtgraph/widgets/GroupBox.py b/pyqtgraph/widgets/GroupBox.py deleted file mode 100644 index 4325011..0000000 --- a/pyqtgraph/widgets/GroupBox.py +++ /dev/null @@ -1,93 +0,0 @@ -from ..Qt import QtGui, QtCore -from .PathButton import PathButton -from ..python2_3 import basestring - - -class GroupBox(QtGui.QGroupBox): - """Subclass of QGroupBox that implements collapse handle. - """ - sigCollapseChanged = QtCore.Signal(object) - - def __init__(self, *args): - QtGui.QGroupBox.__init__(self, *args) - - self._collapsed = False - # We modify the size policy when the group box is collapsed, so - # keep track of the last requested policy: - self._lastSizePlocy = self.sizePolicy() - - self.closePath = QtGui.QPainterPath() - self.closePath.moveTo(0, -1) - self.closePath.lineTo(0, 1) - self.closePath.lineTo(1, 0) - self.closePath.lineTo(0, -1) - - self.openPath = QtGui.QPainterPath() - self.openPath.moveTo(-1, 0) - self.openPath.lineTo(1, 0) - self.openPath.lineTo(0, 1) - self.openPath.lineTo(-1, 0) - - self.collapseBtn = PathButton(path=self.openPath, size=(12, 12), margin=0) - self.collapseBtn.setStyleSheet(""" - border: none; - """) - self.collapseBtn.setPen('k') - self.collapseBtn.setBrush('w') - self.collapseBtn.setParent(self) - self.collapseBtn.move(3, 3) - self.collapseBtn.setFlat(True) - - self.collapseBtn.clicked.connect(self.toggleCollapsed) - - if len(args) > 0 and isinstance(args[0], basestring): - self.setTitle(args[0]) - - def toggleCollapsed(self): - self.setCollapsed(not self._collapsed) - - def collapsed(self): - return self._collapsed - - def setCollapsed(self, c): - if c == self._collapsed: - return - - if c is True: - self.collapseBtn.setPath(self.closePath) - self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred, closing=True) - elif c is False: - self.collapseBtn.setPath(self.openPath) - self.setSizePolicy(self._lastSizePolicy) - else: - raise TypeError("Invalid argument %r; must be bool." % c) - - for ch in self.children(): - if isinstance(ch, QtGui.QWidget) and ch is not self.collapseBtn: - ch.setVisible(not c) - - self._collapsed = c - self.sigCollapseChanged.emit(c) - - def setSizePolicy(self, *args, **kwds): - QtGui.QGroupBox.setSizePolicy(self, *args) - if kwds.pop('closing', False) is True: - self._lastSizePolicy = self.sizePolicy() - - def setHorizontalPolicy(self, *args): - QtGui.QGroupBox.setHorizontalPolicy(self, *args) - self._lastSizePolicy = self.sizePolicy() - - def setVerticalPolicy(self, *args): - QtGui.QGroupBox.setVerticalPolicy(self, *args) - self._lastSizePolicy = self.sizePolicy() - - def setTitle(self, title): - # Leave room for button - QtGui.QGroupBox.setTitle(self, " " + title) - - def widgetGroupInterface(self): - return (self.sigCollapseChanged, - GroupBox.collapsed, - GroupBox.setCollapsed, - True) diff --git a/pyqtgraph/widgets/HistogramLUTWidget.py b/pyqtgraph/widgets/HistogramLUTWidget.py deleted file mode 100644 index 5259900..0000000 --- a/pyqtgraph/widgets/HistogramLUTWidget.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. -This is a wrapper around HistogramLUTItem -""" - -from ..Qt import QtGui, QtCore -from .GraphicsView import GraphicsView -from ..graphicsItems.HistogramLUTItem import HistogramLUTItem - -__all__ = ['HistogramLUTWidget'] - - -class HistogramLUTWidget(GraphicsView): - - def __init__(self, parent=None, *args, **kargs): - background = kargs.pop('background', 'default') - GraphicsView.__init__(self, parent, useOpenGL=False, background=background) - self.item = HistogramLUTItem(*args, **kargs) - self.setCentralItem(self.item) - self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) - self.setMinimumWidth(95) - - - def sizeHint(self): - return QtCore.QSize(115, 200) - - - - def __getattr__(self, attr): - return getattr(self.item, attr) - - - diff --git a/pyqtgraph/widgets/JoystickButton.py b/pyqtgraph/widgets/JoystickButton.py deleted file mode 100644 index 8895593..0000000 --- a/pyqtgraph/widgets/JoystickButton.py +++ /dev/null @@ -1,103 +0,0 @@ -from ..Qt import QtGui, QtCore - - -__all__ = ['JoystickButton'] - -class JoystickButton(QtGui.QPushButton): - sigStateChanged = QtCore.Signal(object, object) ## self, state - - def __init__(self, parent=None): - QtGui.QPushButton.__init__(self, parent) - self.radius = 200 - self.setCheckable(True) - self.state = None - self.setState(0, 0) - self.setFixedWidth(50) - self.setFixedHeight(50) - - - def mousePressEvent(self, ev): - self.setChecked(True) - self.pressPos = ev.localPos() - ev.accept() - - def mouseMoveEvent(self, ev): - dif = ev.localPos()-self.pressPos - self.setState(dif.x(), -dif.y()) - - def mouseReleaseEvent(self, ev): - self.setChecked(False) - self.setState(0,0) - - def wheelEvent(self, ev): - ev.accept() - - - def doubleClickEvent(self, ev): - ev.accept() - - def getState(self): - return self.state - - def setState(self, *xy): - xy = list(xy) - d = (xy[0]**2 + xy[1]**2)**0.5 - nxy = [0, 0] - for i in [0,1]: - if xy[i] == 0: - nxy[i] = 0 - else: - nxy[i] = xy[i] / d - - if d > self.radius: - d = self.radius - d = (d / self.radius) ** 2 - xy = [nxy[0] * d, nxy[1] * d] - - w2 = self.width() / 2 - h2 = self.height() / 2 - self.spotPos = QtCore.QPoint( - int(w2 * (1 + xy[0])), - int(h2 * (1 - xy[1])) - ) - self.update() - if self.state == xy: - return - self.state = xy - self.sigStateChanged.emit(self, self.state) - - def paintEvent(self, ev): - super().paintEvent(ev) - p = QtGui.QPainter(self) - p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) - p.drawEllipse( - self.spotPos.x() - 3, - self.spotPos.y() - 3, - 6, - 6 - ) - - def resizeEvent(self, ev): - self.setState(*self.state) - super().resizeEvent(ev) - - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - w = QtGui.QMainWindow() - b = JoystickButton() - w.setCentralWidget(b) - w.show() - w.resize(100, 100) - - def fn(b, s): - print("state changed:", s) - - b.sigStateChanged.connect(fn) - - ## Start Qt event loop unless running in interactive mode. - import sys - if sys.flags.interactive != 1: - app.exec_() - \ No newline at end of file diff --git a/pyqtgraph/widgets/LayoutWidget.py b/pyqtgraph/widgets/LayoutWidget.py deleted file mode 100644 index 6eb1c8b..0000000 --- a/pyqtgraph/widgets/LayoutWidget.py +++ /dev/null @@ -1,101 +0,0 @@ -from ..Qt import QtGui, QtCore - -__all__ = ['LayoutWidget'] -class LayoutWidget(QtGui.QWidget): - """ - Convenience class used for laying out QWidgets in a grid. - (It's just a little less effort to use than QGridLayout) - """ - - def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) - self.layout = QtGui.QGridLayout() - self.setLayout(self.layout) - self.items = {} - self.rows = {} - self.currentRow = 0 - self.currentCol = 0 - - def nextRow(self): - """Advance to next row for automatic widget placement""" - self.currentRow += 1 - self.currentCol = 0 - - def nextColumn(self, colspan=1): - """Advance to next column, while returning the current column number - (generally only for internal use--called by addWidget)""" - self.currentCol += colspan - return self.currentCol-colspan - - def nextCol(self, *args, **kargs): - """Alias of nextColumn""" - return self.nextColumn(*args, **kargs) - - - def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a QLabel with *text* and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to QLabel(). - Returns the created widget. - """ - text = QtGui.QLabel(text, **kargs) - self.addWidget(text, row, col, rowspan, colspan) - return text - - def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create an empty LayoutWidget and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`LayoutWidget.__init__ ` - Returns the created widget. - """ - layout = LayoutWidget(**kargs) - self.addWidget(layout, row, col, rowspan, colspan) - return layout - - def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1): - """ - Add a widget to the layout and place it in the next available cell (or in the cell specified). - """ - if row == 'next': - self.nextRow() - row = self.currentRow - elif row is None: - row = self.currentRow - - - if col is None: - col = self.nextCol(colspan) - - if row not in self.rows: - self.rows[row] = {} - self.rows[row][col] = item - self.items[item] = (row, col) - - self.layout.addWidget(item, row, col, rowspan, colspan) - - def getWidget(self, row, col): - """Return the widget in (*row*, *col*)""" - return self.rows[row][col] - - #def itemIndex(self, item): - #for i in range(self.layout.count()): - #if self.layout.itemAt(i).graphicsItem() is item: - #return i - #raise Exception("Could not determine index of item " + str(item)) - - #def removeItem(self, item): - #"""Remove *item* from the layout.""" - #ind = self.itemIndex(item) - #self.layout.removeAt(ind) - #self.scene().removeItem(item) - #r,c = self.items[item] - #del self.items[item] - #del self.rows[r][c] - #self.update() - - #def clear(self): - #items = [] - #for i in list(self.items.keys()): - #self.removeItem(i) - - diff --git a/pyqtgraph/widgets/MatplotlibWidget.py b/pyqtgraph/widgets/MatplotlibWidget.py deleted file mode 100644 index b3868e8..0000000 --- a/pyqtgraph/widgets/MatplotlibWidget.py +++ /dev/null @@ -1,39 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB -import matplotlib - -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar - -from matplotlib.figure import Figure - -class MatplotlibWidget(QtGui.QWidget): - """ - Implements a Matplotlib figure inside a QWidget. - Use getFigure() and redraw() to interact with matplotlib. - - Example:: - - mw = MatplotlibWidget() - subplot = mw.getFigure().add_subplot(111) - subplot.plot(x,y) - mw.draw() - """ - - def __init__(self, size=(5.0, 4.0), dpi=100): - QtGui.QWidget.__init__(self) - self.fig = Figure(size, dpi=dpi) - self.canvas = FigureCanvas(self.fig) - self.canvas.setParent(self) - self.toolbar = NavigationToolbar(self.canvas, self) - - self.vbox = QtGui.QVBoxLayout() - self.vbox.addWidget(self.toolbar) - self.vbox.addWidget(self.canvas) - - self.setLayout(self.vbox) - - def getFigure(self): - return self.fig - - def draw(self): - self.canvas.draw() diff --git a/pyqtgraph/widgets/MultiPlotWidget.py b/pyqtgraph/widgets/MultiPlotWidget.py deleted file mode 100644 index dc7583b..0000000 --- a/pyqtgraph/widgets/MultiPlotWidget.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MultiPlotWidget.py - Convenience class--GraphicsView widget displaying a MultiPlotItem -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" -from ..Qt import QtCore -from .GraphicsView import GraphicsView -from ..graphicsItems import MultiPlotItem as MultiPlotItem - -__all__ = ['MultiPlotWidget'] -class MultiPlotWidget(GraphicsView): - """Widget implementing a :class:`~pyqtgraph.GraphicsView` with a single - :class:`~pyqtgraph.MultiPlotItem` inside.""" - def __init__(self, parent=None): - self.minPlotHeight = 50 - self.mPlotItem = MultiPlotItem.MultiPlotItem() - GraphicsView.__init__(self, parent) - self.enableMouse(False) - self.setCentralItem(self.mPlotItem) - ## Explicitly wrap methods from mPlotItem - #for m in ['setData']: - #setattr(self, m, getattr(self.mPlotItem, m)) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - - def __getattr__(self, attr): ## implicitly wrap methods from plotItem - if hasattr(self.mPlotItem, attr): - m = getattr(self.mPlotItem, attr) - if hasattr(m, '__call__'): - return m - raise AttributeError(attr) - - def setMinimumPlotHeight(self, min): - """Set the minimum height for each sub-plot displayed. - - If the total height of all plots is greater than the height of the - widget, then a scroll bar will appear to provide access to the entire - set of plots. - - Added in version 0.9.9 - """ - self.minPlotHeight = min - self.resizeEvent(None) - - def widgetGroupInterface(self): - return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) - - def saveState(self): - return {} - #return self.plotItem.saveState() - - def restoreState(self, state): - pass - #return self.plotItem.restoreState(state) - - def close(self): - self.mPlotItem.close() - self.mPlotItem = None - self.setParent(None) - GraphicsView.close(self) - - def setRange(self, *args, **kwds): - GraphicsView.setRange(self, *args, **kwds) - if self.centralWidget is not None: - r = self.range - minHeight = len(self.mPlotItem.plots) * self.minPlotHeight - if r.height() < minHeight: - r.setHeight(minHeight) - r.setWidth(r.width() - self.verticalScrollBar().width()) - self.centralWidget.setGeometry(r) - - def resizeEvent(self, ev): - if self.closed: - return - if self.autoPixelRange: - self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) - MultiPlotWidget.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. - self.updateMatrix() diff --git a/pyqtgraph/widgets/PathButton.py b/pyqtgraph/widgets/PathButton.py deleted file mode 100644 index 06fe131..0000000 --- a/pyqtgraph/widgets/PathButton.py +++ /dev/null @@ -1,51 +0,0 @@ -from ..Qt import QtGui, QtCore -from .. import functions as fn - -__all__ = ['PathButton'] - - -class PathButton(QtGui.QPushButton): - """Simple PushButton extension that paints a QPainterPath centered on its face. - """ - def __init__(self, parent=None, path=None, pen='default', brush=None, size=(30,30), margin=7): - QtGui.QPushButton.__init__(self, parent) - self.margin = margin - self.path = None - if pen == 'default': - pen = 'k' - self.setPen(pen) - self.setBrush(brush) - if path is not None: - self.setPath(path) - if size is not None: - self.setFixedWidth(size[0]) - self.setFixedHeight(size[1]) - - def setBrush(self, brush): - self.brush = fn.mkBrush(brush) - - def setPen(self, *args, **kwargs): - self.pen = fn.mkPen(*args, **kwargs) - - def setPath(self, path): - self.path = path - self.update() - - def paintEvent(self, ev): - super().paintEvent(ev) - margin = self.margin - geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) - rect = self.path.boundingRect() - scale = min(geom.width() / float(rect.width()), geom.height() / float(rect.height())) - - p = QtGui.QPainter(self) - p.setRenderHint(p.Antialiasing) - p.translate(geom.center()) - p.scale(scale, scale) - p.translate(-rect.center()) - p.setPen(self.pen) - p.setBrush(self.brush) - p.drawPath(self.path) - p.end() - - diff --git a/pyqtgraph/widgets/PlotWidget.py b/pyqtgraph/widgets/PlotWidget.py deleted file mode 100644 index 610591f..0000000 --- a/pyqtgraph/widgets/PlotWidget.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PlotWidget.py - Convenience class--GraphicsView widget displaying a single PlotItem -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more information. -""" - -from ..Qt import QtCore, QtGui -from .GraphicsView import * -from ..graphicsItems.PlotItem import * - -__all__ = ['PlotWidget'] -class PlotWidget(GraphicsView): - - # signals wrapped from PlotItem / ViewBox - sigRangeChanged = QtCore.Signal(object, object) - sigTransformChanged = QtCore.Signal(object) - - """ - :class:`GraphicsView ` widget with a single - :class:`PlotItem ` inside. - - The following methods are wrapped directly from PlotItem: - :func:`addItem `, - :func:`removeItem `, - :func:`clear `, - :func:`setAxisItems `, - :func:`setXRange `, - :func:`setYRange `, - :func:`setRange `, - :func:`autoRange `, - :func:`setXLink `, - :func:`setYLink `, - :func:`viewRect `, - :func:`setMouseEnabled `, - :func:`enableAutoRange `, - :func:`disableAutoRange `, - :func:`setAspectLocked `, - :func:`setLimits `, - :func:`register `, - :func:`unregister ` - - - For all - other methods, use :func:`getPlotItem `. - """ - def __init__(self, parent=None, background='default', plotItem=None, **kargs): - """When initializing PlotWidget, *parent* and *background* are passed to - :func:`GraphicsWidget.__init__() ` - and all others are passed - to :func:`PlotItem.__init__() `.""" - GraphicsView.__init__(self, parent, background=background) - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.enableMouse(False) - if plotItem is None: - self.plotItem = PlotItem(**kargs) - else: - self.plotItem = plotItem - self.setCentralItem(self.plotItem) - ## Explicitly wrap methods from plotItem - ## NOTE: If you change this list, update the documentation above as well. - for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setAxisItems', 'setXRange', - 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', - 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', - 'setLimits', 'register', 'unregister', 'viewRect']: - setattr(self, m, getattr(self.plotItem, m)) - #QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged) - self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) - - def close(self): - self.plotItem.close() - self.plotItem = None - #self.scene().clear() - #self.mPlotItem.close() - self.setParent(None) - super(PlotWidget, self).close() - - def __getattr__(self, attr): ## implicitly wrap methods from plotItem - if hasattr(self.plotItem, attr): - m = getattr(self.plotItem, attr) - if hasattr(m, '__call__'): - return m - raise AttributeError(attr) - - def viewRangeChanged(self, view, range): - #self.emit(QtCore.SIGNAL('viewChanged'), *args) - self.sigRangeChanged.emit(self, range) - - def widgetGroupInterface(self): - return (None, PlotWidget.saveState, PlotWidget.restoreState) - - def saveState(self): - return self.plotItem.saveState() - - def restoreState(self, state): - return self.plotItem.restoreState(state) - - def getPlotItem(self): - """Return the PlotItem contained within.""" - return self.plotItem - - - diff --git a/pyqtgraph/widgets/ProgressDialog.py b/pyqtgraph/widgets/ProgressDialog.py deleted file mode 100644 index 989a8d6..0000000 --- a/pyqtgraph/widgets/ProgressDialog.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from .. import ptime - -__all__ = ['ProgressDialog'] - - -class ProgressDialog(QtGui.QProgressDialog): - """ - Extends QProgressDialog: - - * Adds context management so the dialog may be used in `with` statements - * Allows nesting multiple progress dialogs - - Example:: - - with ProgressDialog("Processing..", minVal, maxVal) as dlg: - # do stuff - dlg.setValue(i) ## could also use dlg += 1 - if dlg.wasCanceled(): - raise Exception("Processing canceled by user") - """ - - allDialogs = [] - - def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False, nested=False): - """ - ============== ================================================================ - **Arguments:** - labelText (required) - cancelText Text to display on cancel button, or None to disable it. - minimum - maximum - parent - wait Length of time (im ms) to wait before displaying dialog - busyCursor If True, show busy cursor until dialog finishes - disable If True, the progress dialog will not be displayed - and calls to wasCanceled() will always return False. - If ProgressDialog is entered from a non-gui thread, it will - always be disabled. - nested (bool) If True, then this progress bar will be displayed inside - any pre-existing progress dialogs that also allow nesting. - ============== ================================================================ - """ - # attributes used for nesting dialogs - self.nestedLayout = None - self._nestableWidgets = None - self._nestingReady = False - self._topDialog = None - self._subBars = [] - self.nested = nested - - # for rate-limiting Qt event processing during progress bar update - self._lastProcessEvents = None - - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - self.disabled = disable or (not isGuiThread) - if self.disabled: - return - - noCancel = False - if cancelText is None: - cancelText = '' - noCancel = True - - self.busyCursor = busyCursor - - QtGui.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) - - # If this will be a nested dialog, then we ignore the wait time - if nested is True and len(ProgressDialog.allDialogs) > 0: - self.setMinimumDuration(2**30) - else: - self.setMinimumDuration(wait) - - self.setWindowModality(QtCore.Qt.WindowModal) - self.setValue(self.minimum()) - if noCancel: - self.setCancelButton(None) - - def __enter__(self): - if self.disabled: - return self - if self.busyCursor: - QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) - - if self.nested and len(ProgressDialog.allDialogs) > 0: - topDialog = ProgressDialog.allDialogs[0] - topDialog._addSubDialog(self) - self._topDialog = topDialog - topDialog.canceled.connect(self.cancel) - - ProgressDialog.allDialogs.append(self) - - return self - - def __exit__(self, exType, exValue, exTrace): - if self.disabled: - return - if self.busyCursor: - QtGui.QApplication.restoreOverrideCursor() - - if self._topDialog is not None: - self._topDialog._removeSubDialog(self) - - ProgressDialog.allDialogs.pop(-1) - - self.setValue(self.maximum()) - - def __iadd__(self, val): - """Use inplace-addition operator for easy incrementing.""" - if self.disabled: - return self - self.setValue(self.value()+val) - return self - - def _addSubDialog(self, dlg): - # insert widgets from another dialog into this one. - - # set a new layout and arrange children into it (if needed). - self._prepareNesting() - - bar, btn = dlg._extractWidgets() - - # where should we insert this widget? Find the first slot with a - # "removed" widget (that was left as a placeholder) - inserted = False - for i,bar2 in enumerate(self._subBars): - if bar2.hidden: - self._subBars.pop(i) - bar2.hide() - bar2.setParent(None) - self._subBars.insert(i, bar) - inserted = True - break - if not inserted: - self._subBars.append(bar) - - # reset the layout - while self.nestedLayout.count() > 0: - self.nestedLayout.takeAt(0) - for b in self._subBars: - self.nestedLayout.addWidget(b) - - def _removeSubDialog(self, dlg): - # don't remove the widget just yet; instead we hide it and leave it in - # as a placeholder. - bar, btn = dlg._extractWidgets() - bar.hide() - - def _prepareNesting(self): - # extract all child widgets and place into a new layout that we can add to - if self._nestingReady is False: - # top layout contains progress bars + cancel button at the bottom - self._topLayout = QtGui.QGridLayout() - self.setLayout(self._topLayout) - self._topLayout.setContentsMargins(0, 0, 0, 0) - - # A vbox to contain all progress bars - self.nestedVBox = QtGui.QWidget() - self._topLayout.addWidget(self.nestedVBox, 0, 0, 1, 2) - self.nestedLayout = QtGui.QVBoxLayout() - self.nestedVBox.setLayout(self.nestedLayout) - - # re-insert all widgets - bar, btn = self._extractWidgets() - self.nestedLayout.addWidget(bar) - self._subBars.append(bar) - self._topLayout.addWidget(btn, 1, 1, 1, 1) - self._topLayout.setColumnStretch(0, 100) - self._topLayout.setColumnStretch(1, 1) - self._topLayout.setRowStretch(0, 100) - self._topLayout.setRowStretch(1, 1) - - self._nestingReady = True - - def _extractWidgets(self): - # return: - # 1. a single widget containing the label and progress bar - # 2. the cancel button - - if self._nestableWidgets is None: - widgets = [ch for ch in self.children() if isinstance(ch, QtGui.QWidget)] - label = [ch for ch in self.children() if isinstance(ch, QtGui.QLabel)][0] - bar = [ch for ch in self.children() if isinstance(ch, QtGui.QProgressBar)][0] - btn = [ch for ch in self.children() if isinstance(ch, QtGui.QPushButton)][0] - - sw = ProgressWidget(label, bar) - - self._nestableWidgets = (sw, btn) - - return self._nestableWidgets - - def resizeEvent(self, ev): - if self._nestingReady: - # don't let progress dialog manage widgets anymore. - return - return super().resizeEvent(ev) - - ## wrap all other functions to make sure they aren't being called from non-gui threads - - def setValue(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setValue(self, val) - - # Qt docs say this should happen automatically, but that doesn't seem - # to be the case. - if self.windowModality() == QtCore.Qt.WindowModal: - now = ptime.time() - if self._lastProcessEvents is None or (now - self._lastProcessEvents) > 0.2: - QtGui.QApplication.processEvents() - self._lastProcessEvents = now - - def setLabelText(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setLabelText(self, val) - - def setMaximum(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setMaximum(self, val) - - def setMinimum(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setMinimum(self, val) - - def wasCanceled(self): - if self.disabled: - return False - return QtGui.QProgressDialog.wasCanceled(self) - - def maximum(self): - if self.disabled: - return 0 - return QtGui.QProgressDialog.maximum(self) - - def minimum(self): - if self.disabled: - return 0 - return QtGui.QProgressDialog.minimum(self) - - -class ProgressWidget(QtGui.QWidget): - """Container for a label + progress bar that also allows its child widgets - to be hidden without changing size. - """ - def __init__(self, label, bar): - QtGui.QWidget.__init__(self) - self.hidden = False - self.layout = QtGui.QVBoxLayout() - self.setLayout(self.layout) - - self.label = label - self.bar = bar - self.layout.addWidget(label) - self.layout.addWidget(bar) - - def eventFilter(self, obj, ev): - return ev.type() == QtCore.QEvent.Paint - - def hide(self): - # hide label and bar, but continue occupying the same space in the layout - for widget in (self.label, self.bar): - widget.installEventFilter(self) - widget.update() - self.hidden = True diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py deleted file mode 100644 index 996f757..0000000 --- a/pyqtgraph/widgets/RawImageWidget.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -""" -RawImageWidget.py -Copyright 2010-2016 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from .. import getConfigOption, functions as fn, getCupy -from ..Qt import QtCore, QtGui, QtWidgets - -try: - QOpenGLWidget = QtWidgets.QOpenGLWidget - from OpenGL.GL import * - - HAVE_OPENGL = True -except (ImportError, AttributeError): - # Would prefer `except ImportError` here, but some versions of pyopengl generate - # AttributeError upon import - HAVE_OPENGL = False - - -class RawImageWidget(QtGui.QWidget): - """ - Widget optimized for very fast video display. - Generally using an ImageItem inside GraphicsView is fast enough. - On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. - """ - - def __init__(self, parent=None, scaled=False): - """ - Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. - This also greatly reduces the speed at which it will draw frames. - """ - QtGui.QWidget.__init__(self, parent) - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - self.scaled = scaled - self.opts = None - self.image = None - self._cp = getCupy() - - def setImage(self, img, *args, **kargs): - """ - img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). - Extra arguments are sent to functions.makeARGB - """ - if getConfigOption('imageAxisOrder') == 'col-major': - img = img.swapaxes(0, 1) - self.opts = (img, args, kargs) - self.image = None - self.update() - - def paintEvent(self, ev): - if self.opts is None: - return - if self.image is None: - argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) - if self._cp and self._cp.get_array_module(argb) == self._cp: - argb = argb.get() # transfer GPU data back to the CPU - self.image = fn.makeQImage(argb, alpha, copy=False, transpose=False) - self.opts = () - # if self.pixmap is None: - # self.pixmap = QtGui.QPixmap.fromImage(self.image) - p = QtGui.QPainter(self) - if self.scaled: - rect = self.rect() - ar = rect.width() / float(rect.height()) - imar = self.image.width() / float(self.image.height()) - if ar > imar: - rect.setWidth(int(rect.width() * imar / ar)) - else: - rect.setHeight(int(rect.height() * ar / imar)) - - p.drawImage(rect, self.image) - else: - p.drawImage(QtCore.QPointF(), self.image) - # p.drawPixmap(self.rect(), self.pixmap) - p.end() - - -if HAVE_OPENGL: - class RawImageGLWidget(QOpenGLWidget): - """ - Similar to RawImageWidget, but uses a GL widget to do all drawing. - Performance varies between platforms; see examples/VideoSpeedTest for benchmarking. - - Checks if setConfigOptions(imageAxisOrder='row-major') was set. - """ - - def __init__(self, parent=None, scaled=False): - QOpenGLWidget.__init__(self, parent) - self.scaled = scaled - self.image = None - self.uploaded = False - self.smooth = False - self.opts = None - - def setImage(self, img, *args, **kargs): - """ - img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). - Extra arguments are sent to functions.makeARGB - """ - if getConfigOption('imageAxisOrder') == 'col-major': - img = img.swapaxes(0, 1) - self.opts = (img, args, kargs) - self.image = None - self.uploaded = False - self.update() - - def initializeGL(self): - self.texture = glGenTextures(1) - - def uploadTexture(self): - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - # glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - - ## Test texture dimensions first - # shape = self.image.shape - # glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - # if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - # raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - - h, w = self.image.shape[:2] - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image) - glDisable(GL_TEXTURE_2D) - self.uploaded = True - - def paintGL(self): - glClear(GL_COLOR_BUFFER_BIT) - - if self.image is None: - if self.opts is None: - return - img, args, kwds = self.opts - kwds['useRGBA'] = True - self.image, alpha = fn.makeARGB(img, *args, **kwds) - - if not self.uploaded: - self.uploadTexture() - - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - glColor4f(1, 1, 1, 1) - - glBegin(GL_QUADS) - glTexCoord2f(0, 1) - glVertex3f(-1, -1, 0) - glTexCoord2f(1, 1) - glVertex3f(1, -1, 0) - glTexCoord2f(1, 0) - glVertex3f(1, 1, 0) - glTexCoord2f(0, 0) - glVertex3f(-1, 1, 0) - glEnd() - glDisable(GL_TEXTURE_3D) diff --git a/pyqtgraph/widgets/RemoteGraphicsView.py b/pyqtgraph/widgets/RemoteGraphicsView.py deleted file mode 100644 index c9549c8..0000000 --- a/pyqtgraph/widgets/RemoteGraphicsView.py +++ /dev/null @@ -1,291 +0,0 @@ -from ..Qt import QtGui, QtCore, QT_LIB -if QT_LIB.startswith('PyQt'): - from ..Qt import sip -from .. import multiprocess as mp -from .GraphicsView import GraphicsView -from .. import CONFIG_OPTIONS -import numpy as np -import mmap, tempfile, os, atexit, sys, random - -__all__ = ['RemoteGraphicsView'] - - -class RemoteGraphicsView(QtGui.QWidget): - """ - Replacement for GraphicsView that does all scene management and rendering on a remote process, - while displaying on the local widget. - - GraphicsItems must be created by proxy to the remote process. - - """ - def __init__(self, parent=None, *args, **kwds): - """ - The keyword arguments 'useOpenGL' and 'backgound', if specified, are passed to the remote - GraphicsView.__init__(). All other keyword arguments are passed to multiprocess.QtProcess.__init__(). - """ - self._img = None - self._imgReq = None - self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView. - ## without it, the widget will not compete for space against another GraphicsView. - QtGui.QWidget.__init__(self) - - # separate local keyword arguments from remote. - remoteKwds = {} - for kwd in ['useOpenGL', 'background']: - if kwd in kwds: - remoteKwds[kwd] = kwds.pop(kwd) - - self._proc = mp.QtProcess(**kwds) - self.pg = self._proc._import('pyqtgraph') - self.pg.setConfigOptions(**CONFIG_OPTIONS) - rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') - self._view = rpgRemote.Renderer(*args, **remoteKwds) - self._view._setProxyOptions(deferGetattr=True) - - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.setMouseTracking(True) - self.shm = None - shmFileName = self._view.shmFileName() - if sys.platform.startswith('win'): - self.shmtag = shmFileName - else: - self.shmFile = open(shmFileName, 'r') - - self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) - ## Note: we need synchronous signals - ## even though there is no return value-- - ## this informs the renderer that it is - ## safe to begin rendering again. - - for method in ['scene', 'setCentralItem']: - setattr(self, method, getattr(self._view, method)) - - def resizeEvent(self, ev): - ret = super().resizeEvent(ev) - self._view.resize(self.size(), _callSync='off') - return ret - - def sizeHint(self): - return QtCore.QSize(*self._sizeHint) - - def remoteSceneChanged(self, data): - w, h, size, newfile = data - #self._sizeHint = (whint, hhint) - if self.shm is None or self.shm.size != size: - if self.shm is not None: - self.shm.close() - if sys.platform.startswith('win'): - self.shmtag = newfile ## on windows, we create a new tag for every resize - self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once. - else: - self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ) - self.shm.seek(0) - data = self.shm.read(w*h*4) - self._img = QtGui.QImage(data, w, h, QtGui.QImage.Format_ARGB32) - self._img.data = data # data must be kept alive or PySide 1.2.1 (and probably earlier) will crash. - self.update() - - def paintEvent(self, ev): - if self._img is None: - return - p = QtGui.QPainter(self) - p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height())) - p.end() - - def serialize_mouse_enum(self, *args): - # PyQt6 can pickle enums and flags but cannot cast to int - # PyQt5 5.12, PyQt5 5.15, PySide2 5.15, PySide6 can pickle enums but not flags - # PySide2 5.12 cannot pickle enums nor flags - # MouseButtons and KeyboardModifiers are flags - if QT_LIB != 'PyQt6': - args = [int(x) for x in args] - return args - - def serialize_mouse_event(self, ev): - lpos, gpos = ev.localPos(), ev.screenPos() - typ, btn, btns, mods = self.serialize_mouse_enum( - ev.type(), ev.button(), ev.buttons(), ev.modifiers()) - return (typ, lpos, gpos, btn, btns, mods) - - def serialize_wheel_event(self, ev): - # {PyQt6, PySide6} have position() - # {PyQt5, PySide2} 5.15 have position() - # {PyQt5, PySide2} 5.15 have posF() (contrary to C++ docs) - # {PyQt5, PySide2} 5.12 have posF() - lpos = ev.position() if hasattr(ev, 'position') else ev.posF() - # gpos = ev.globalPosition() if hasattr(ev, 'globalPosition') else ev.globalPosF() - gpos = lpos # RemoteGraphicsView Renderer assumes to be at (0, 0) - btns, mods, phase = self.serialize_mouse_enum(ev.buttons(), ev.modifiers(), ev.phase()) - return (lpos, gpos, ev.pixelDelta(), ev.angleDelta(), btns, mods, phase, ev.inverted()) - - def mousePressEvent(self, ev): - self._view.mousePressEvent(self.serialize_mouse_event(ev), _callSync='off') - ev.accept() - return super().mousePressEvent(ev) - - def mouseReleaseEvent(self, ev): - self._view.mouseReleaseEvent(self.serialize_mouse_event(ev), _callSync='off') - ev.accept() - return super().mouseReleaseEvent(ev) - - def mouseMoveEvent(self, ev): - self._view.mouseMoveEvent(self.serialize_mouse_event(ev), _callSync='off') - ev.accept() - return super().mouseMoveEvent(ev) - - def wheelEvent(self, ev): - self._view.wheelEvent(self.serialize_wheel_event(ev), _callSync='off') - ev.accept() - return super().wheelEvent(ev) - - def enterEvent(self, ev): - lws = ev.localPos(), ev.windowPos(), ev.screenPos() - self._view.enterEvent(lws, _callSync='off') - return super().enterEvent(ev) - - def leaveEvent(self, ev): - typ, = self.serialize_mouse_enum(ev.type()) - self._view.leaveEvent(typ, _callSync='off') - return super().leaveEvent(ev) - - def remoteProcess(self): - """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" - return self._proc - - def close(self): - """Close the remote process. After this call, the widget will no longer be updated.""" - self._view.sceneRendered.disconnect() - self._proc.close() - - -class Renderer(GraphicsView): - ## Created by the remote process to handle render requests - - sceneRendered = QtCore.Signal(object) - - def __init__(self, *args, **kwds): - ## Create shared memory for rendered image - #pg.dbg(namespace={'r': self}) - if sys.platform.startswith('win'): - self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) - self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows - else: - self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_') - self.shmFile.write(b'\x00' * (mmap.PAGESIZE+1)) - self.shmFile.flush() - fd = self.shmFile.fileno() - self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) - atexit.register(self.close) - - GraphicsView.__init__(self, *args, **kwds) - self.scene().changed.connect(self.update) - self.img = None - self.renderTimer = QtCore.QTimer() - self.renderTimer.timeout.connect(self.renderView) - self.renderTimer.start(16) - - def close(self): - self.shm.close() - if not sys.platform.startswith('win'): - self.shmFile.close() - - def shmFileName(self): - if sys.platform.startswith('win'): - return self.shmtag - else: - return self.shmFile.name - - def update(self): - self.img = None - return super().update() - - def resize(self, size): - oldSize = self.size() - super().resize(size) - self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) - self.update() - - def renderView(self): - if self.img is None: - ## make sure shm is large enough and get its address - if self.width() == 0 or self.height() == 0: - return - size = self.width() * self.height() * 4 - if size > self.shm.size(): - if sys.platform.startswith('win'): - ## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap - self.shm.close() - ## it also says (sometimes) 'access is denied' if we try to reuse the tag. - self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) - self.shm = mmap.mmap(-1, size, self.shmtag) - elif sys.platform == 'darwin': - self.shm.close() - fd = self.shmFile.fileno() - os.ftruncate(fd, size + 1) - self.shm = mmap.mmap(fd, size, mmap.MAP_SHARED, mmap.PROT_WRITE) - else: - self.shm.resize(size) - - ## render the scene directly to shared memory - - # see functions.py::makeQImage() for rationale - if QT_LIB.startswith('PyQt'): - if QtCore.PYQT_VERSION == 0x60000: - img_ptr = sip.voidptr(self.shm) - else: - # PyQt5, PyQt6 >= 6.0.1 - img_ptr = int(sip.voidptr(self.shm)) - else: - # PySide2, PySide6 - img_ptr = self.shm - - self.img = QtGui.QImage(img_ptr, self.width(), self.height(), QtGui.QImage.Format_ARGB32) - - self.img.fill(0xffffffff) - p = QtGui.QPainter(self.img) - self.render(p, self.viewRect(), self.rect()) - p.end() - self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName())) - - def deserialize_mouse_event(self, mouse_event): - typ, pos, gpos, btn, btns, mods = mouse_event - typ = QtCore.QEvent.Type(typ) - if QT_LIB != 'PyQt6': - btn = QtCore.Qt.MouseButton(btn) - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods) - - def deserialize_wheel_event(self, wheel_event): - pos, gpos, pixelDelta, angleDelta, btns, mods, phase, inverted = wheel_event - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - phase = QtCore.Qt.ScrollPhase(phase) - return QtGui.QWheelEvent(pos, gpos, pixelDelta, angleDelta, btns, mods, phase, inverted) - - def mousePressEvent(self, mouse_event): - ev = self.deserialize_mouse_event(mouse_event) - return super().mousePressEvent(ev) - - def mouseMoveEvent(self, mouse_event): - ev = self.deserialize_mouse_event(mouse_event) - return super().mouseMoveEvent(ev) - - def mouseReleaseEvent(self, mouse_event): - ev = self.deserialize_mouse_event(mouse_event) - return super().mouseReleaseEvent(ev) - - def wheelEvent(self, wheel_event): - ev = self.deserialize_wheel_event(wheel_event) - return super().wheelEvent(ev) - - def enterEvent(self, lws): - ev = QtGui.QEnterEvent(*lws) - return super().enterEvent(ev) - - def leaveEvent(self, typ): - typ = QtCore.QEvent.Type(typ) - ev = QtCore.QEvent(typ) - return super().leaveEvent(ev) - diff --git a/pyqtgraph/widgets/ScatterPlotWidget.py b/pyqtgraph/widgets/ScatterPlotWidget.py deleted file mode 100644 index c81a702..0000000 --- a/pyqtgraph/widgets/ScatterPlotWidget.py +++ /dev/null @@ -1,291 +0,0 @@ -from ..Qt import QtGui, QtCore -from .PlotWidget import PlotWidget -from .DataFilterWidget import DataFilterParameter -from .ColorMapWidget import ColorMapParameter -from .. import parametertree as ptree -from .. import functions as fn -from .. import getConfigOption -from ..graphicsItems.TextItem import TextItem -import numpy as np -from collections import OrderedDict - -__all__ = ['ScatterPlotWidget'] - -class ScatterPlotWidget(QtGui.QSplitter): - """ - This is a high-level widget for exploring relationships in tabular data. - - Given a multi-column record array, the widget displays a scatter plot of a - specific subset of the data. Includes controls for selecting the columns to - plot, filtering data, and determining symbol color and shape. - - The widget consists of four components: - - 1) A list of column names from which the user may select 1 or 2 columns - to plot. If one column is selected, the data for that column will be - plotted in a histogram-like manner by using :func:`pseudoScatter() - `. If two columns are selected, then the - scatter plot will be generated with x determined by the first column - that was selected and y by the second. - 2) A DataFilter that allows the user to select a subset of the data by - specifying multiple selection criteria. - 3) A ColorMap that allows the user to determine how points are colored by - specifying multiple criteria. - 4) A PlotWidget for displaying the data. - """ - sigScatterPlotClicked = QtCore.Signal(object, object, object) - sigScatterPlotHovered = QtCore.Signal(object, object, object) - - def __init__(self, parent=None): - QtGui.QSplitter.__init__(self, QtCore.Qt.Horizontal) - self.ctrlPanel = QtGui.QSplitter(QtCore.Qt.Vertical) - self.addWidget(self.ctrlPanel) - self.fieldList = QtGui.QListWidget() - self.fieldList.setSelectionMode(self.fieldList.ExtendedSelection) - self.ptree = ptree.ParameterTree(showHeader=False) - self.filter = DataFilterParameter() - self.colorMap = ColorMapParameter() - self.params = ptree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap]) - self.ptree.setParameters(self.params, showTop=False) - - self.plot = PlotWidget() - self.ctrlPanel.addWidget(self.fieldList) - self.ctrlPanel.addWidget(self.ptree) - self.addWidget(self.plot) - - fg = fn.mkColor(getConfigOption('foreground')) - fg.setAlpha(150) - self.filterText = TextItem(border=getConfigOption('foreground'), color=fg) - self.filterText.setPos(60,20) - self.filterText.setParentItem(self.plot.plotItem) - - self.data = None - self.indices = None - self.mouseOverField = None - self.scatterPlot = None - self.selectionScatter = None - self.selectedIndices = [] - self.style = dict(pen=None, symbol='o') - self._visibleXY = None # currently plotted points - self._visibleData = None # currently plotted records - self._visibleIndices = None - self._indexMap = None - - self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) - self.filter.sigFilterChanged.connect(self.filterChanged) - self.colorMap.sigColorMapChanged.connect(self.updatePlot) - - def setFields(self, fields, mouseOverField=None): - """ - Set the list of field names/units to be processed. - - The format of *fields* is the same as used by - :func:`ColorMapWidget.setFields ` - """ - self.fields = OrderedDict(fields) - self.mouseOverField = mouseOverField - self.fieldList.clear() - for f,opts in fields: - item = QtGui.QListWidgetItem(f) - item.opts = opts - item = self.fieldList.addItem(item) - self.filter.setFields(fields) - self.colorMap.setFields(fields) - - def setSelectedFields(self, *fields): - self.fieldList.itemSelectionChanged.disconnect(self.fieldSelectionChanged) - try: - self.fieldList.clearSelection() - for f in fields: - i = list(self.fields.keys()).index(f) - item = self.fieldList.item(i) - item.setSelected(True) - finally: - self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) - self.fieldSelectionChanged() - - def setData(self, data): - """ - Set the data to be processed and displayed. - Argument must be a numpy record array. - """ - self.data = data - self.indices = np.arange(len(data)) - self.filtered = None - self.filteredIndices = None - self.updatePlot() - - def setSelectedIndices(self, inds): - """Mark the specified indices as selected. - - Must be a sequence of integers that index into the array given in setData(). - """ - self.selectedIndices = inds - self.updateSelected() - - def setSelectedPoints(self, points): - """Mark the specified points as selected. - - Must be a list of points as generated by the sigScatterPlotClicked signal. - """ - self.setSelectedIndices([pt.originalIndex for pt in points]) - - def fieldSelectionChanged(self): - sel = self.fieldList.selectedItems() - if len(sel) > 2: - self.fieldList.blockSignals(True) - try: - for item in sel[1:-1]: - item.setSelected(False) - finally: - self.fieldList.blockSignals(False) - - self.updatePlot() - - def filterChanged(self, f): - self.filtered = None - self.updatePlot() - desc = self.filter.describe() - if len(desc) == 0: - self.filterText.setVisible(False) - else: - self.filterText.setText('\n'.join(desc)) - self.filterText.setVisible(True) - - def updatePlot(self): - self.plot.clear() - if self.data is None or len(self.data) == 0: - return - - if self.filtered is None: - mask = self.filter.generateMask(self.data) - self.filtered = self.data[mask] - self.filteredIndices = self.indices[mask] - data = self.filtered - if len(data) == 0: - return - - colors = np.array([fn.mkBrush(*x) for x in self.colorMap.map(data)]) - - style = self.style.copy() - - ## Look up selected columns and units - sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) - units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) - if len(sel) == 0: - self.plot.setTitle('') - return - - - if len(sel) == 1: - self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') - if len(data) == 0: - return - #x = data[sel[0]] - #y = None - xy = [data[sel[0]], None] - elif len(sel) == 2: - self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) - if len(data) == 0: - return - - xy = [data[sel[0]], data[sel[1]]] - #xydata = [] - #for ax in [0,1]: - #d = data[sel[ax]] - ### scatter catecorical values just a bit so they show up better in the scatter plot. - ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: - ##d += np.random.normal(size=len(cells), scale=0.1) - - #xydata.append(d) - #x,y = xydata - - ## convert enum-type fields to float, set axis labels - enum = [False, False] - for i in [0,1]: - axis = self.plot.getAxis(['bottom', 'left'][i]) - if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): - vals = self.fields[sel[i]].get('values', list(set(xy[i]))) - xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) - axis.setTicks([list(enumerate(vals))]) - enum[i] = True - else: - axis.setTicks(None) # reset to automatic ticking - - ## mask out any nan values - mask = np.ones(len(xy[0]), dtype=bool) - if xy[0].dtype.kind == 'f': - mask &= np.isfinite(xy[0]) - if xy[1] is not None and xy[1].dtype.kind == 'f': - mask &= np.isfinite(xy[1]) - - xy[0] = xy[0][mask] - style['symbolBrush'] = colors[mask] - data = data[mask] - indices = self.filteredIndices[mask] - - ## Scatter y-values for a histogram-like appearance - if xy[1] is None: - ## column scatter plot - xy[1] = fn.pseudoScatter(xy[0]) - else: - ## beeswarm plots - xy[1] = xy[1][mask] - for ax in [0,1]: - if not enum[ax]: - continue - imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 - for i in range(imax+1): - keymask = xy[ax] == i - scatter = fn.pseudoScatter(xy[1-ax][keymask], bidir=True) - if len(scatter) == 0: - continue - smax = np.abs(scatter).max() - if smax != 0: - scatter *= 0.2 / smax - xy[ax][keymask] += scatter - - - if self.scatterPlot is not None: - try: - self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) - except: - pass - - self._visibleXY = xy - self._visibleData = data - self._visibleIndices = indices - self._indexMap = None - self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data, **style) - self.scatterPlot.sigPointsClicked.connect(self.plotClicked) - self.scatterPlot.sigPointsHovered.connect(self.plotHovered) - self.updateSelected() - - def updateSelected(self): - if self._visibleXY is None: - return - # map from global index to visible index - indMap = self._getIndexMap() - inds = [indMap[i] for i in self.selectedIndices if i in indMap] - x,y = self._visibleXY[0][inds], self._visibleXY[1][inds] - - if self.selectionScatter is not None: - self.plot.plotItem.removeItem(self.selectionScatter) - if len(x) == 0: - return - self.selectionScatter = self.plot.plot(x, y, pen=None, symbol='s', symbolSize=12, symbolBrush=None, symbolPen='y') - - def _getIndexMap(self): - # mapping from original data index to visible point index - if self._indexMap is None: - self._indexMap = {j:i for i,j in enumerate(self._visibleIndices)} - return self._indexMap - - def plotClicked(self, plot, points, ev): - # Tag each point with its index into the original dataset - for pt in points: - pt.originalIndex = self._visibleIndices[pt.index()] - self.sigScatterPlotClicked.emit(self, points, ev) - - def plotHovered(self, plot, points, ev): - self.sigScatterPlotHovered.emit(self, points, ev) diff --git a/pyqtgraph/widgets/SpinBox.py b/pyqtgraph/widgets/SpinBox.py deleted file mode 100644 index b1c7266..0000000 --- a/pyqtgraph/widgets/SpinBox.py +++ /dev/null @@ -1,615 +0,0 @@ -# -*- coding: utf-8 -*- -from math import isnan, isinf -from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors -import decimal -import weakref -import re - -from ..Qt import QtGui, QtCore -from ..python2_3 import asUnicode, basestring -from ..SignalProxy import SignalProxy -from .. import functions as fn - - -__all__ = ['SpinBox'] - - -class SpinBox(QtGui.QAbstractSpinBox): - """ - **Bases:** QtGui.QAbstractSpinBox - - Extension of QSpinBox widget for selection of a numerical value. - Adds many extra features: - - * SI prefix notation (eg, automatically display "300 mV" instead of "0.003 V") - * Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) - * Option for unbounded values - * Delayed signals (allows multiple rapid changes with only one change signal) - * Customizable text formatting - - ============================= ============================================== - **Signals:** - valueChanged(value) Same as QSpinBox; emitted every time the value - has changed. - sigValueChanged(self) Emitted when value has changed, but also combines - multiple rapid changes into one signal (eg, - when rolling the mouse wheel). - sigValueChanging(self, value) Emitted immediately for all value changes. - ============================= ============================================== - """ - - ## There's a PyQt bug that leaks a reference to the - ## QLineEdit returned from QAbstractSpinBox.lineEdit() - ## This makes it possible to crash the entire program - ## by making accesses to the LineEdit after the spinBox has been deleted. - ## I have no idea how to get around this.. - - - valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox - sigValueChanged = QtCore.Signal(object) # (self) - sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. - - def __init__(self, parent=None, value=0.0, **kwargs): - """ - ============== ======================================================================== - **Arguments:** - parent Sets the parent widget for this SpinBox (optional). Default is None. - value (float/int) initial value. Default is 0.0. - ============== ======================================================================== - - All keyword arguments are passed to :func:`setOpts`. - """ - QtGui.QAbstractSpinBox.__init__(self, parent) - self.lastValEmitted = None - self.lastText = '' - self.textValid = True ## If false, we draw a red border - self.setMinimumWidth(0) - self._lastFontHeight = None - - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) - self.errorBox = ErrorBox(self.lineEdit()) - - self.opts = { - 'bounds': [None, None], - 'wrapping': False, - - ## normal arithmetic step - 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time - ## if 'dec' is True, the step size is relative to the value - ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) - 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. - ## if true, minStep must be set in order to cross zero. - - 'int': False, ## Set True to force value to be integer - 'finite': True, - - 'suffix': '', - 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) - - 'delay': 0.3, ## delay sending wheel update signals for 300ms - - 'delayUntilEditFinished': True, ## do not send signals until text editing has finished - - 'decimals': 6, - - 'format': asUnicode("{scaledValue:.{decimals}g}{suffixGap}{siPrefix}{suffix}"), - 'regex': fn.FLOAT_REGEX, - 'evalFunc': D, - - 'compactHeight': True, # manually remove extra margin outside of text - } - - self.decOpts = ['step', 'minStep'] - - self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. - self.updateText() - self.skipValidate = False - self.setCorrectionMode(self.CorrectToPreviousValue) - self.setKeyboardTracking(False) - self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay']) - self.setOpts(**kwargs) - self._updateHeight() - - self.editingFinished.connect(self.editingFinishedEvent) - - def setOpts(self, **opts): - """Set options affecting the behavior of the SpinBox. - - ============== ======================================================================== - **Arguments:** - bounds (min,max) Minimum and maximum values allowed in the SpinBox. - Either may be None to leave the value unbounded. By default, values are - unbounded. - suffix (str) suffix (units) to display after the numerical value. By default, - suffix is an empty str. - siPrefix (bool) If True, then an SI prefix is automatically prepended - to the units and the value is scaled accordingly. For example, - if value=0.003 and suffix='V', then the SpinBox will display - "300 mV" (but a call to SpinBox.value will still return 0.003). In case - the value represents a dimensionless quantity that might span many - orders of magnitude, such as a Reynolds number, an SI - prefix is allowed with no suffix. Default is False. - step (float) The size of a single step. This is used when clicking the up/ - down arrows, when rolling the mouse wheel, or when pressing - keyboard arrows while the widget has keyboard focus. Note that - the interpretation of this value is different when specifying - the 'dec' argument. Default is 0.01. - dec (bool) If True, then the step value will be adjusted to match - the current size of the variable (for example, a value of 15 - might step in increments of 1 whereas a value of 1500 would - step in increments of 100). In this case, the 'step' argument - is interpreted *relative* to the current value. The most common - 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. Default is - False. - minStep (float) When dec=True, this specifies the minimum allowable step size. - int (bool) If True, the value is forced to integer type. Default is False - finite (bool) When False and int=False, infinite values (nan, inf, -inf) are - permitted. Default is True. - wrapping (bool) If True and both bounds are not None, spin box has circular behavior. - decimals (int) Number of decimal values to display. Default is 6. - format (str) Formatting string used to generate the text shown. Formatting is - done with ``str.format()`` and makes use of several arguments: - - * *value* - the unscaled value of the spin box - * *suffix* - the suffix string - * *scaledValue* - the scaled value to use when an SI prefix is present - * *siPrefix* - the SI prefix string (if any), or an empty string if - this feature has been disabled - * *suffixGap* - a single space if a suffix is present, or an empty - string otherwise. - regex (str or RegexObject) Regular expression used to parse the spinbox text. - May contain the following group names: - - * *number* - matches the numerical portion of the string (mandatory) - * *siPrefix* - matches the SI prefix string - * *suffix* - matches the suffix string - - Default is defined in ``pyqtgraph.functions.FLOAT_REGEX``. - evalFunc (callable) Fucntion that converts a numerical string to a number, - preferrably a Decimal instance. This function handles only the numerical - of the text; it does not have access to the suffix or SI prefix. - compactHeight (bool) if True, then set the maximum height of the spinbox based on the - height of its font. This allows more compact packing on platforms with - excessive widget decoration. Default is True. - ============== ======================================================================== - """ - #print opts - for k,v in opts.items(): - if k == 'bounds': - self.setMinimum(v[0], update=False) - self.setMaximum(v[1], update=False) - elif k == 'min': - self.setMinimum(v, update=False) - elif k == 'max': - self.setMaximum(v, update=False) - elif k in ['step', 'minStep']: - self.opts[k] = D(asUnicode(v)) - elif k == 'value': - pass ## don't set value until bounds have been set - elif k == 'format': - self.opts[k] = asUnicode(v) - elif k == 'regex' and isinstance(v, basestring): - self.opts[k] = re.compile(v) - elif k in self.opts: - self.opts[k] = v - else: - raise TypeError("Invalid keyword argument '%s'." % k) - if 'value' in opts: - self.setValue(opts['value']) - - ## If bounds have changed, update value to match - if 'bounds' in opts and 'value' not in opts: - self.setValue() - - ## sanity checks: - if self.opts['int']: - if 'step' in opts: - step = opts['step'] - ## not necessary.. - #if int(step) != step: - #raise Exception('Integer SpinBox must have integer step size.') - else: - self.opts['step'] = int(self.opts['step']) - - if 'minStep' in opts: - step = opts['minStep'] - if int(step) != step: - raise Exception('Integer SpinBox must have integer minStep size.') - else: - ms = int(self.opts.get('minStep', 1)) - if ms < 1: - ms = 1 - self.opts['minStep'] = ms - - if 'format' not in opts: - self.opts['format'] = asUnicode("{value:d}{suffixGap}{suffix}") - - if 'delay' in opts: - self.proxy.setDelay(opts['delay']) - - self.updateText() - - def setMaximum(self, m, update=True): - """Set the maximum allowed value (or None for no limit)""" - if m is not None: - m = D(asUnicode(m)) - self.opts['bounds'][1] = m - if update: - self.setValue() - - def setMinimum(self, m, update=True): - """Set the minimum allowed value (or None for no limit)""" - if m is not None: - m = D(asUnicode(m)) - self.opts['bounds'][0] = m - if update: - self.setValue() - - def wrapping(self): - """Return whether or not the spin box is circular.""" - return self.opts['wrapping'] - - def setWrapping(self, s): - """Set whether spin box is circular. - - Both bounds must be set for this to have an effect.""" - self.opts['wrapping'] = s - - def setPrefix(self, p): - """Set a string prefix. - """ - self.setOpts(prefix=p) - - def setRange(self, r0, r1): - """Set the upper and lower limits for values in the spinbox. - """ - self.setOpts(bounds = [r0,r1]) - - def setProperty(self, prop, val): - ## for QSpinBox compatibility - if prop == 'value': - #if type(val) is QtCore.QVariant: - #val = val.toDouble()[0] - self.setValue(val) - else: - print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) - - def setSuffix(self, suf): - """Set the string suffix appended to the spinbox text. - """ - self.setOpts(suffix=suf) - - def setSingleStep(self, step): - """Set the step size used when responding to the mouse wheel, arrow - buttons, or arrow keys. - """ - self.setOpts(step=step) - - def setDecimals(self, decimals): - """Set the number of decimals to be displayed when formatting numeric - values. - """ - self.setOpts(decimals=decimals) - - def selectNumber(self): - """ - Select the numerical portion of the text to allow quick editing by the user. - """ - le = self.lineEdit() - text = asUnicode(le.text()) - m = self.opts['regex'].match(text) - if m is None: - return - s,e = m.start('number'), m.end('number') - le.setSelection(s, e-s) - - def focusInEvent(self, ev): - super(SpinBox, self).focusInEvent(ev) - self.selectNumber() - - def value(self): - """ - Return the value of this SpinBox. - - """ - if self.opts['int']: - return int(self.val) - else: - return float(self.val) - - def setValue(self, value=None, update=True, delaySignal=False): - """Set the value of this SpinBox. - - If the value is out of bounds, it will be clipped to the nearest boundary - or wrapped if wrapping is enabled. - - If the spin is integer type, the value will be coerced to int. - Returns the actual value set. - - If value is None, then the current value is used (this is for resetting - the value after bounds, etc. have changed) - """ - if value is None: - value = self.value() - - bounded = True - if not isnan(value): - bounds = self.opts['bounds'] - if None not in bounds and self.opts['wrapping'] is True: - bounded = False - if isinf(value): - value = self.val - else: - # Casting of Decimals to floats required to avoid unexpected behavior of remainder operator - value = float(value) - l, u = float(bounds[0]), float(bounds[1]) - value = (value - l) % (u - l) + l - else: - if bounds[0] is not None and value < bounds[0]: - bounded = False - value = bounds[0] - if bounds[1] is not None and value > bounds[1]: - bounded = False - value = bounds[1] - - if self.opts['int']: - value = int(value) - - if not isinstance(value, D): - value = D(asUnicode(value)) - - prev, self.val = self.val, value - changed = not fn.eq(value, prev) # use fn.eq to handle nan - - if update and (changed or not bounded): - self.updateText(prev=prev) - - if changed: - self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. - if not delaySignal: - self.emitChanged() - - return value - - def emitChanged(self): - self.lastValEmitted = self.val - self.valueChanged.emit(float(self.val)) - self.sigValueChanged.emit(self) - - def delayedChange(self): - try: - if not fn.eq(self.val, self.lastValEmitted): # use fn.eq to handle nan - self.emitChanged() - except RuntimeError: - pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. - - def widgetGroupInterface(self): - return (self.valueChanged, SpinBox.value, SpinBox.setValue) - - def sizeHint(self): - return QtCore.QSize(120, 0) - - def stepEnabled(self): - return self.StepUpEnabled | self.StepDownEnabled - - def stepBy(self, n): - if isinf(self.val) or isnan(self.val): - return - - n = D(int(n)) ## n must be integral number of steps. - s = [D(-1), D(1)][n >= 0] ## determine sign of step - val = self.val - - for i in range(int(abs(n))): - if self.opts['dec']: - if val == 0: - step = self.opts['minStep'] - exp = None - else: - vs = [D(-1), D(1)][val >= 0] - #exp = D(int(abs(val*(D('1.01')**(s*vs))).log10())) - fudge = D('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. - exp = abs(val * fudge).log10().quantize(1, decimal.ROUND_FLOOR) - step = self.opts['step'] * D(10)**exp - if 'minStep' in self.opts: - step = max(step, self.opts['minStep']) - val += s * step - #print "Exp:", exp, "step", step, "val", val - else: - val += s*self.opts['step'] - - if 'minStep' in self.opts and abs(val) < self.opts['minStep']: - val = D(0) - self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. - - def valueInRange(self, value): - if not isnan(value): - bounds = self.opts['bounds'] - if bounds[0] is not None and value < bounds[0]: - return False - if bounds[1] is not None and value > bounds[1]: - return False - if self.opts.get('int', False): - if int(value) != value: - return False - return True - - def updateText(self, prev=None): - # temporarily disable validation - self.skipValidate = True - - txt = self.formatText(prev=prev) - - # actually set the text - self.lineEdit().setText(txt) - self.lastText = txt - - # re-enable the validation - self.skipValidate = False - - def formatText(self, prev=None): - # get the number of decimal places to print - decimals = self.opts['decimals'] - suffix = self.opts['suffix'] - - # format the string - val = self.value() - if self.opts['siPrefix'] is True: - # SI prefix was requested, so scale the value accordingly - - if self.val == 0 and prev is not None: - # special case: if it's zero use the previous prefix - (s, p) = fn.siScale(prev) - else: - (s, p) = fn.siScale(val) - parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': p, 'scaledValue': s*val} - - else: - # no SI prefix /suffix requested; scale is 1 - parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': '', 'scaledValue': val} - - parts['suffixGap'] = '' if (parts['suffix'] == '' and parts['siPrefix'] == '') else ' ' - - return self.opts['format'].format(**parts) - - def validate(self, strn, pos): - if self.skipValidate: - ret = QtGui.QValidator.Acceptable - else: - try: - val = self.interpret() - if val is False: - ret = QtGui.QValidator.Intermediate - else: - if self.valueInRange(val): - if not self.opts['delayUntilEditFinished']: - self.setValue(val, update=False) - ret = QtGui.QValidator.Acceptable - else: - ret = QtGui.QValidator.Intermediate - - except: - import sys - sys.excepthook(*sys.exc_info()) - ret = QtGui.QValidator.Intermediate - - ## draw / clear border - if ret == QtGui.QValidator.Intermediate: - self.textValid = False - elif ret == QtGui.QValidator.Acceptable: - self.textValid = True - ## note: if text is invalid, we don't change the textValid flag - ## since the text will be forced to its previous state anyway - self.update() - - self.errorBox.setVisible(not self.textValid) - - ## support 2 different pyqt APIs. Bleh. - if hasattr(QtCore, 'QString'): - return (ret, pos) - else: - return (ret, strn, pos) - - def fixup(self, strn): - # fixup is called when the spinbox loses focus with an invalid or intermediate string - self.updateText() - - # support both PyQt APIs (for Python 2 and 3 respectively) - # http://pyqt.sourceforge.net/Docs/PyQt4/python_v3.html#qvalidator - try: - strn.clear() - strn.append(self.lineEdit().text()) - except AttributeError: - return self.lineEdit().text() - - def interpret(self): - """Return value of text or False if text is invalid.""" - strn = self.lineEdit().text() - - # tokenize into numerical value, si prefix, and suffix - try: - val, siprefix, suffix = fn.siParse(strn, self.opts['regex'], suffix=self.opts['suffix']) - except Exception: - return False - - # check suffix - if suffix != self.opts['suffix']: - return False - - # generate value - val = self.opts['evalFunc'](val) - - if (self.opts['int'] or self.opts['finite']) and (isinf(val) or isnan(val)): - return False - - if self.opts['int']: - val = int(fn.siApply(val, siprefix)) - else: - try: - val = fn.siApply(val, siprefix) - except Exception: - import sys - sys.excepthook(*sys.exc_info()) - return False - - return val - - def editingFinishedEvent(self): - """Edit has finished; set value.""" - #print "Edit finished." - if asUnicode(self.lineEdit().text()) == self.lastText: - #print "no text change." - return - try: - val = self.interpret() - except Exception: - return - - if val is False: - #print "value invalid:", str(self.lineEdit().text()) - return - if val == self.val: - #print "no value change:", val, self.val - return - self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like - - def _updateHeight(self): - # SpinBox has very large margins on some platforms; this is a hack to remove those - # margins and allow more compact packing of controls. - if not self.opts['compactHeight']: - self.setMaximumHeight(1e6) - return - h = QtGui.QFontMetrics(self.font()).height() - if self._lastFontHeight != h: - self._lastFontHeight = h - self.setMaximumHeight(h) - - def paintEvent(self, ev): - self._updateHeight() - super().paintEvent(ev) - - -class ErrorBox(QtGui.QWidget): - """Red outline to draw around lineedit when value is invalid. - (for some reason, setting border from stylesheet does not work) - """ - def __init__(self, parent): - QtGui.QWidget.__init__(self, parent) - parent.installEventFilter(self) - self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) - self._resize() - self.setVisible(False) - - def eventFilter(self, obj, ev): - if ev.type() == QtCore.QEvent.Resize: - self._resize() - return False - - def _resize(self): - self.setGeometry(0, 0, self.parent().width(), self.parent().height()) - - def paintEvent(self, ev): - p = QtGui.QPainter(self) - p.setPen(fn.mkPen(color='r', width=2)) - p.drawRect(self.rect()) - p.end() diff --git a/pyqtgraph/widgets/TableWidget.py b/pyqtgraph/widgets/TableWidget.py deleted file mode 100644 index 2e91ec7..0000000 --- a/pyqtgraph/widgets/TableWidget.py +++ /dev/null @@ -1,513 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from ..Qt import QtGui, QtCore -from ..python2_3 import asUnicode, basestring -from .. import metaarray - -translate = QtCore.QCoreApplication.translate - -__all__ = ['TableWidget'] - - -def _defersort(fn): - def defersort(self, *args, **kwds): - # may be called recursively; only the first call needs to block sorting - setSorting = False - if self._sorting is None: - self._sorting = self.isSortingEnabled() - setSorting = True - self.setSortingEnabled(False) - try: - return fn(self, *args, **kwds) - finally: - if setSorting: - self.setSortingEnabled(self._sorting) - self._sorting = None - - return defersort - - -class TableWidget(QtGui.QTableWidget): - """Extends QTableWidget with some useful functions for automatic data handling - and copy / export context menu. Can automatically format and display a variety - of data types (see :func:`setData() ` for more - information. - """ - - def __init__(self, *args, **kwds): - """ - All positional arguments are passed to QTableWidget.__init__(). - - ===================== ================================================= - **Keyword Arguments** - editable (bool) If True, cells in the table can be edited - by the user. Default is False. - sortable (bool) If True, the table may be soted by - clicking on column headers. Note that this also - causes rows to appear initially shuffled until - a sort column is selected. Default is True. - *(added in version 0.9.9)* - ===================== ================================================= - """ - - QtGui.QTableWidget.__init__(self, *args) - - self.itemClass = TableWidgetItem - - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection) - self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - self.clear() - - kwds.setdefault('sortable', True) - kwds.setdefault('editable', False) - self.setEditable(kwds.pop('editable')) - self.setSortingEnabled(kwds.pop('sortable')) - - if len(kwds) > 0: - raise TypeError("Invalid keyword arguments '%s'" % list(kwds.keys())) - - self._sorting = None # used when temporarily disabling sorting - - self._formats = {None: None} # stores per-column formats and entire table format - self.sortModes = {} # stores per-column sort mode - - self.itemChanged.connect(self.handleItemChanged) - - self.contextMenu = QtGui.QMenu() - self.contextMenu.addAction(translate("TableWidget", 'Copy Selection')).triggered.connect(self.copySel) - self.contextMenu.addAction(translate("TableWidget", 'Copy All')).triggered.connect(self.copyAll) - self.contextMenu.addAction(translate("TableWidget", 'Save Selection')).triggered.connect(self.saveSel) - self.contextMenu.addAction(translate("TableWidget", 'Save All')).triggered.connect(self.saveAll) - - def clear(self): - """Clear all contents from the table.""" - QtGui.QTableWidget.clear(self) - self.verticalHeadersSet = False - self.horizontalHeadersSet = False - self.items = [] - self.setRowCount(0) - self.setColumnCount(0) - self.sortModes = {} - - def setData(self, data): - """Set the data displayed in the table. - Allowed formats are: - - * numpy arrays - * numpy record arrays - * metaarrays - * list-of-lists [[1,2,3], [4,5,6]] - * dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} - * list-of-dicts [{'x': 1, 'y': 4}, {'x': 2, 'y': 5}, ...] - """ - self.clear() - self.appendData(data) - self.resizeColumnsToContents() - - @_defersort - def appendData(self, data): - """ - Add new rows to the table. - - See :func:`setData() ` for accepted - data types. - """ - startRow = self.rowCount() - - fn0, header0 = self.iteratorFn(data) - if fn0 is None: - self.clear() - return - it0 = fn0(data) - try: - first = next(it0) - except StopIteration: - return - fn1, header1 = self.iteratorFn(first) - if fn1 is None: - self.clear() - return - - firstVals = [x for x in fn1(first)] - self.setColumnCount(len(firstVals)) - - if not self.verticalHeadersSet and header0 is not None: - labels = [self.verticalHeaderItem(i).text() for i in range(self.rowCount())] - self.setRowCount(startRow + len(header0)) - self.setVerticalHeaderLabels(labels + header0) - self.verticalHeadersSet = True - if not self.horizontalHeadersSet and header1 is not None: - self.setHorizontalHeaderLabels(header1) - self.horizontalHeadersSet = True - - i = startRow - self.setRow(i, firstVals) - for row in it0: - i += 1 - self.setRow(i, [x for x in fn1(row)]) - - if (self._sorting and self.horizontalHeadersSet and - self.horizontalHeader().sortIndicatorSection() >= self.columnCount()): - self.sortByColumn(0, QtCore.Qt.AscendingOrder) - - def setEditable(self, editable=True): - self.editable = editable - for item in self.items: - item.setEditable(editable) - - def setFormat(self, format, column=None): - """ - Specify the default text formatting for the entire table, or for a - single column if *column* is specified. - - If a string is specified, it is used as a format string for converting - float values (and all other types are converted using str). If a - function is specified, it will be called with the item as its only - argument and must return a string. Setting format = None causes the - default formatter to be used instead. - - Added in version 0.9.9. - - """ - if format is not None and not isinstance(format, basestring) and not callable(format): - raise ValueError("Format argument must string, callable, or None. (got %s)" % format) - - self._formats[column] = format - - - if column is None: - # update format of all items that do not have a column format - # specified - for c in range(self.columnCount()): - if self._formats.get(c, None) is None: - for r in range(self.rowCount()): - item = self.item(r, c) - if item is None: - continue - item.setFormat(format) - else: - # set all items in the column to use this format, or the default - # table format if None was specified. - if format is None: - format = self._formats[None] - for r in range(self.rowCount()): - item = self.item(r, column) - if item is None: - continue - item.setFormat(format) - - - def iteratorFn(self, data): - ## Return 1) a function that will provide an iterator for data and 2) a list of header strings - if isinstance(data, list) or isinstance(data, tuple): - return lambda d: d.__iter__(), None - elif isinstance(data, dict): - return lambda d: iter(d.values()), list(map(asUnicode, data.keys())) - elif (hasattr(data, 'implements') and data.implements('MetaArray')): - if data.axisHasColumns(0): - header = [asUnicode(data.columnName(0, i)) for i in range(data.shape[0])] - elif data.axisHasValues(0): - header = list(map(asUnicode, data.xvals(0))) - else: - header = None - return self.iterFirstAxis, header - elif isinstance(data, np.ndarray): - return self.iterFirstAxis, None - elif isinstance(data, np.void): - return self.iterate, list(map(asUnicode, data.dtype.names)) - elif data is None: - return (None,None) - elif np.isscalar(data): - return self.iterateScalar, None - else: - msg = "Don't know how to iterate over data type: {!s}".format(type(data)) - raise TypeError(msg) - - def iterFirstAxis(self, data): - for i in range(data.shape[0]): - yield data[i] - - def iterate(self, data): - # for numpy.void, which can be iterated but mysteriously - # has no __iter__ (??) - for x in data: - yield x - - def iterateScalar(self, data): - yield data - - def appendRow(self, data): - self.appendData([data]) - - @_defersort - def addRow(self, vals): - row = self.rowCount() - self.setRowCount(row + 1) - self.setRow(row, vals) - - @_defersort - def setRow(self, row, vals): - if row > self.rowCount() - 1: - self.setRowCount(row + 1) - for col in range(len(vals)): - val = vals[col] - item = self.itemClass(val, row) - item.setEditable(self.editable) - sortMode = self.sortModes.get(col, None) - if sortMode is not None: - item.setSortMode(sortMode) - format = self._formats.get(col, self._formats[None]) - item.setFormat(format) - self.items.append(item) - self.setItem(row, col, item) - item.setValue(val) # Required--the text-change callback is invoked - # when we call setItem. - - def setSortMode(self, column, mode): - """ - Set the mode used to sort *column*. - - ============== ======================================================== - **Sort Modes** - value Compares item.value if available; falls back to text - comparison. - text Compares item.text() - index Compares by the order in which items were inserted. - ============== ======================================================== - - Added in version 0.9.9 - """ - for r in range(self.rowCount()): - item = self.item(r, column) - if hasattr(item, 'setSortMode'): - item.setSortMode(mode) - self.sortModes[column] = mode - - def sizeHint(self): - # based on http://stackoverflow.com/a/7195443/54056 - width = sum(self.columnWidth(i) for i in range(self.columnCount())) - width += self.verticalHeader().sizeHint().width() - width += self.verticalScrollBar().sizeHint().width() - width += self.frameWidth() * 2 - height = sum(self.rowHeight(i) for i in range(self.rowCount())) - height += self.verticalHeader().sizeHint().height() - height += self.horizontalScrollBar().sizeHint().height() - return QtCore.QSize(width, height) - - def serialize(self, useSelection=False): - """Convert entire table (or just selected area) into tab-separated text values""" - if useSelection: - selection = self.selectedRanges()[0] - rows = list(range(selection.topRow(), - selection.bottomRow() + 1)) - columns = list(range(selection.leftColumn(), - selection.rightColumn() + 1)) - else: - rows = list(range(self.rowCount())) - columns = list(range(self.columnCount())) - - data = [] - if self.horizontalHeadersSet: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode('')) - - for c in columns: - row.append(asUnicode(self.horizontalHeaderItem(c).text())) - data.append(row) - - for r in rows: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode(self.verticalHeaderItem(r).text())) - for c in columns: - item = self.item(r, c) - if item is not None: - row.append(asUnicode(item.value)) - else: - row.append(asUnicode('')) - data.append(row) - - s = '' - for row in data: - s += ('\t'.join(row) + '\n') - return s - - def copySel(self): - """Copy selected data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) - - def copyAll(self): - """Copy all data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) - - def saveSel(self): - """Save selected data to file.""" - self.save(self.serialize(useSelection=True)) - - def saveAll(self): - """Save all data to file.""" - self.save(self.serialize(useSelection=False)) - - def save(self, data): - fileName = QtGui.QFileDialog.getSaveFileName( - self, - f"{translate('TableWidget', 'Save As')}...", - "", - f"{translate('TableWidget', 'Tab-separated values')} (*.tsv)" - ) - if isinstance(fileName, tuple): - fileName = fileName[0] # Qt4/5 API difference - if fileName == '': - return - with open(fileName, 'w') as fd: - fd.write(data) - - def contextMenuEvent(self, ev): - self.contextMenu.popup(ev.globalPos()) - - def keyPressEvent(self, ev): - if ev.matches(QtGui.QKeySequence.Copy): - ev.accept() - self.copySel() - else: - super().keyPressEvent(ev) - - def handleItemChanged(self, item): - item.itemChanged() - - -class TableWidgetItem(QtGui.QTableWidgetItem): - def __init__(self, val, index, format=None): - QtGui.QTableWidgetItem.__init__(self, '') - self._blockValueChange = False - self._format = None - self._defaultFormat = '%0.3g' - self.sortMode = 'value' - self.index = index - flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - self.setFlags(flags) - self.setValue(val) - self.setFormat(format) - - def setEditable(self, editable): - """ - Set whether this item is user-editable. - """ - if editable: - self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable) - else: - self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) - - def setSortMode(self, mode): - """ - Set the mode used to sort this item against others in its column. - - ============== ======================================================== - **Sort Modes** - value Compares item.value if available; falls back to text - comparison. - text Compares item.text() - index Compares by the order in which items were inserted. - ============== ======================================================== - """ - modes = ('value', 'text', 'index', None) - if mode not in modes: - raise ValueError('Sort mode must be one of %s' % str(modes)) - self.sortMode = mode - - def setFormat(self, fmt): - """Define the conversion from item value to displayed text. - - If a string is specified, it is used as a format string for converting - float values (and all other types are converted using str). If a - function is specified, it will be called with the item as its only - argument and must return a string. - - Added in version 0.9.9. - """ - if fmt is not None and not isinstance(fmt, basestring) and not callable(fmt): - raise ValueError("Format argument must string, callable, or None. (got %s)" % fmt) - self._format = fmt - self._updateText() - - def _updateText(self): - self._blockValueChange = True - try: - self._text = self.format() - self.setText(self._text) - finally: - self._blockValueChange = False - - def setValue(self, value): - self.value = value - self._updateText() - - def itemChanged(self): - """Called when the data of this item has changed.""" - if self.text() != self._text: - self.textChanged() - - def textChanged(self): - """Called when this item's text has changed for any reason.""" - self._text = self.text() - - if self._blockValueChange: - # text change was result of value or format change; do not - # propagate. - return - - try: - - self.value = type(self.value)(self.text()) - except ValueError: - self.value = str(self.text()) - - def format(self): - if callable(self._format): - return self._format(self) - if isinstance(self.value, (float, np.floating)): - if self._format is None: - return self._defaultFormat % self.value - else: - return self._format % self.value - else: - return asUnicode(self.value) - - def __lt__(self, other): - if self.sortMode == 'index' and hasattr(other, 'index'): - return self.index < other.index - if self.sortMode == 'value' and hasattr(other, 'value'): - return self.value < other.value - else: - return self.text() < other.text() - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - t = TableWidget() - win.setCentralWidget(t) - win.resize(800,600) - win.show() - - ll = [[1,2,3,4,5]] * 20 - ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 - dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} - - a = np.ones((20, 5)) - ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) - - t.setData(ll) - - ma = metaarray.MetaArray(np.ones((20, 3)), info=[ - {'values': np.linspace(1, 5, 20)}, - {'cols': [ - {'name': 'x'}, - {'name': 'y'}, - {'name': 'z'}, - ]} - ]) - t.setData(ma) - diff --git a/pyqtgraph/widgets/TreeWidget.py b/pyqtgraph/widgets/TreeWidget.py deleted file mode 100644 index b3e3f94..0000000 --- a/pyqtgraph/widgets/TreeWidget.py +++ /dev/null @@ -1,396 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore -from weakref import * - -__all__ = ['TreeWidget', 'TreeWidgetItem'] - - -class TreeWidget(QtGui.QTreeWidget): - """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. - Also maintains the expanded state of subtrees as they are moved. - This class demonstrates the absurd lengths one must go to to make drag/drop work.""" - - sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) - sigItemCheckStateChanged = QtCore.Signal(object, object) - sigItemTextChanged = QtCore.Signal(object, object) - sigColumnCountChanged = QtCore.Signal(object, object) # self, count - - def __init__(self, parent=None): - QtGui.QTreeWidget.__init__(self, parent) - - # wrap this item so that we can propagate tree change information - # to children. - self._invRootItem = InvisibleRootItem(QtGui.QTreeWidget.invisibleRootItem(self)) - - self.setAcceptDrops(True) - self.setDragEnabled(True) - self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked) - self.placeholders = [] - self.childNestingLimit = None - self.itemClicked.connect(self._itemClicked) - - def setItemWidget(self, item, col, wid): - """ - Overrides QTreeWidget.setItemWidget such that widgets are added inside an invisible wrapper widget. - This makes it possible to move the item in and out of the tree without its widgets being automatically deleted. - """ - w = QtGui.QWidget() ## foster parent / surrogate child widget - l = QtGui.QVBoxLayout() - l.setContentsMargins(0,0,0,0) - w.setLayout(l) - w.setSizePolicy(wid.sizePolicy()) - w.setMinimumHeight(wid.minimumHeight()) - w.setMinimumWidth(wid.minimumWidth()) - l.addWidget(wid) - w.realChild = wid - self.placeholders.append(w) - QtGui.QTreeWidget.setItemWidget(self, item, col, w) - - def itemWidget(self, item, col): - w = QtGui.QTreeWidget.itemWidget(self, item, col) - if w is not None and hasattr(w, 'realChild'): - w = w.realChild - return w - - def dropMimeData(self, parent, index, data, action): - item = self.currentItem() - p = parent - #print "drop", item, "->", parent, index - while True: - if p is None: - break - if p is item: - return False - #raise Exception("Can not move item into itself.") - p = p.parent() - - if not self.itemMoving(item, parent, index): - return False - - currentParent = item.parent() - if currentParent is None: - currentParent = self.invisibleRootItem() - if parent is None: - parent = self.invisibleRootItem() - - if currentParent is parent and index > parent.indexOfChild(item): - index -= 1 - - self.prepareMove(item) - - currentParent.removeChild(item) - #print " insert child to index", index - parent.insertChild(index, item) ## index will not be correct - self.setCurrentItem(item) - - self.recoverMove(item) - #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) - self.sigItemMoved.emit(item, parent, index) - return True - - def itemMoving(self, item, parent, index): - """Called when item has been dropped elsewhere in the tree. - Return True to accept the move, False to reject.""" - return True - - def prepareMove(self, item): - item.__widgets = [] - item.__expanded = item.isExpanded() - for i in range(self.columnCount()): - w = self.itemWidget(item, i) - item.__widgets.append(w) - if w is None: - continue - w.setParent(None) - for i in range(item.childCount()): - self.prepareMove(item.child(i)) - - def recoverMove(self, item): - for i in range(self.columnCount()): - w = item.__widgets[i] - if w is None: - continue - self.setItemWidget(item, i, w) - for i in range(item.childCount()): - self.recoverMove(item.child(i)) - - item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. - QtGui.QApplication.instance().processEvents() - item.setExpanded(item.__expanded) - - def collapseTree(self, item): - item.setExpanded(False) - for i in range(item.childCount()): - self.collapseTree(item.child(i)) - - def removeTopLevelItem(self, item): - for i in range(self.topLevelItemCount()): - if self.topLevelItem(i) is item: - self.takeTopLevelItem(i) - return - raise Exception("Item '%s' not in top-level items." % str(item)) - - def listAllItems(self, item=None): - items = [] - if item is not None: - items.append(item) - else: - item = self.invisibleRootItem() - - for cindex in range(item.childCount()): - foundItems = self.listAllItems(item=item.child(cindex)) - for f in foundItems: - items.append(f) - return items - - def dropEvent(self, ev): - super().dropEvent(ev) - self.updateDropFlags() - - def updateDropFlags(self): - ### intended to put a limit on how deep nests of children can go. - ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren - ### can end up over the childNestingLimit. - if self.childNestingLimit == None: - pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) - else: - items = self.listAllItems() - for item in items: - parentCount = 0 - p = item.parent() - while p is not None: - parentCount += 1 - p = p.parent() - if parentCount >= self.childNestingLimit: - item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled)) - else: - item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled) - - @staticmethod - def informTreeWidgetChange(item): - if hasattr(item, 'treeWidgetChanged'): - item.treeWidgetChanged() - for i in range(item.childCount()): - TreeWidget.informTreeWidgetChange(item.child(i)) - - def addTopLevelItem(self, item): - QtGui.QTreeWidget.addTopLevelItem(self, item) - self.informTreeWidgetChange(item) - - def addTopLevelItems(self, items): - QtGui.QTreeWidget.addTopLevelItems(self, items) - for item in items: - self.informTreeWidgetChange(item) - - def insertTopLevelItem(self, index, item): - QtGui.QTreeWidget.insertTopLevelItem(self, index, item) - self.informTreeWidgetChange(item) - - def insertTopLevelItems(self, index, items): - QtGui.QTreeWidget.insertTopLevelItems(self, index, items) - for item in items: - self.informTreeWidgetChange(item) - - def takeTopLevelItem(self, index): - item = self.topLevelItem(index) - if item is not None: - self.prepareMove(item) - item = QtGui.QTreeWidget.takeTopLevelItem(self, index) - self.prepareMove(item) - self.informTreeWidgetChange(item) - return item - - def topLevelItems(self): - return [self.topLevelItem(i) for i in range(self.topLevelItemCount())] - - def clear(self): - items = self.topLevelItems() - for item in items: - self.prepareMove(item) - QtGui.QTreeWidget.clear(self) - - ## Why do we want to do this? It causes RuntimeErrors. - #for item in items: - #self.informTreeWidgetChange(item) - - def invisibleRootItem(self): - return self._invRootItem - - def itemFromIndex(self, index): - """Return the item and column corresponding to a QModelIndex. - """ - col = index.column() - rows = [] - while index.row() >= 0: - rows.insert(0, index.row()) - index = index.parent() - item = self.topLevelItem(rows[0]) - for row in rows[1:]: - item = item.child(row) - return item, col - - def setColumnCount(self, c): - QtGui.QTreeWidget.setColumnCount(self, c) - self.sigColumnCountChanged.emit(self, c) - - def _itemClicked(self, item, col): - if hasattr(item, 'itemClicked'): - item.itemClicked(col) - - -class TreeWidgetItem(QtGui.QTreeWidgetItem): - """ - TreeWidgetItem that keeps track of its own widgets and expansion state. - - * Widgets may be added to columns before the item is added to a tree. - * Expanded state may be set before item is added to a tree. - * Adds setCheked and isChecked methods. - * Adds addChildren, insertChildren, and takeChildren methods. - """ - def __init__(self, *args): - QtGui.QTreeWidgetItem.__init__(self, *args) - self._widgets = {} # col: widget - self._tree = None - self._expanded = False - - def setChecked(self, column, checked): - self.setCheckState(column, QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) - - def isChecked(self, col): - return self.checkState(col) == QtCore.Qt.Checked - - def setExpanded(self, exp): - self._expanded = exp - QtGui.QTreeWidgetItem.setExpanded(self, exp) - - def isExpanded(self): - return self._expanded - - def setWidget(self, column, widget): - if column in self._widgets: - self.removeWidget(column) - self._widgets[column] = widget - tree = self.treeWidget() - if tree is None: - return - else: - tree.setItemWidget(self, column, widget) - - def removeWidget(self, column): - del self._widgets[column] - tree = self.treeWidget() - if tree is None: - return - tree.removeItemWidget(self, column) - - def treeWidgetChanged(self): - tree = self.treeWidget() - if self._tree is tree: - return - self._tree = self.treeWidget() - if tree is None: - return - for col, widget in self._widgets.items(): - tree.setItemWidget(self, col, widget) - QtGui.QTreeWidgetItem.setExpanded(self, self._expanded) - - def childItems(self): - return [self.child(i) for i in range(self.childCount())] - - def addChild(self, child): - QtGui.QTreeWidgetItem.addChild(self, child) - TreeWidget.informTreeWidgetChange(child) - - def addChildren(self, childs): - QtGui.QTreeWidgetItem.addChildren(self, childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def insertChild(self, index, child): - QtGui.QTreeWidgetItem.insertChild(self, index, child) - TreeWidget.informTreeWidgetChange(child) - - def insertChildren(self, index, childs): - QtGui.QTreeWidgetItem.addChildren(self, index, childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def removeChild(self, child): - QtGui.QTreeWidgetItem.removeChild(self, child) - TreeWidget.informTreeWidgetChange(child) - - def takeChild(self, index): - child = QtGui.QTreeWidgetItem.takeChild(self, index) - TreeWidget.informTreeWidgetChange(child) - return child - - def takeChildren(self): - childs = QtGui.QTreeWidgetItem.takeChildren(self) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - return childs - - def setData(self, column, role, value): - # credit: ekhumoro - # http://stackoverflow.com/questions/13662020/how-to-implement-itemchecked-and-itemunchecked-signals-for-qtreewidget-in-pyqt4 - checkstate = self.checkState(column) - text = self.text(column) - QtGui.QTreeWidgetItem.setData(self, column, role, value) - - treewidget = self.treeWidget() - if treewidget is None: - return - if (role == QtCore.Qt.CheckStateRole and checkstate != self.checkState(column)): - treewidget.sigItemCheckStateChanged.emit(self, column) - elif (role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) and text != self.text(column)): - treewidget.sigItemTextChanged.emit(self, column) - - def itemClicked(self, col): - """Called when this item is clicked on. - - Override this method to react to user clicks. - """ - - -class InvisibleRootItem(object): - """Wrapper around a TreeWidget's invisible root item that calls - TreeWidget.informTreeWidgetChange when child items are added/removed. - """ - def __init__(self, item): - self._real_item = item - - def addChild(self, child): - self._real_item.addChild(child) - TreeWidget.informTreeWidgetChange(child) - - def addChildren(self, childs): - self._real_item.addChildren(childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def insertChild(self, index, child): - self._real_item.insertChild(index, child) - TreeWidget.informTreeWidgetChange(child) - - def insertChildren(self, index, childs): - self._real_item.addChildren(index, childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def removeChild(self, child): - self._real_item.removeChild(child) - TreeWidget.informTreeWidgetChange(child) - - def takeChild(self, index): - child = self._real_item.takeChild(index) - TreeWidget.informTreeWidgetChange(child) - return child - - def takeChildren(self): - childs = self._real_item.takeChildren() - for child in childs: - TreeWidget.informTreeWidgetChange(child) - return childs - - def __getattr__(self, attr): - return getattr(self._real_item, attr) diff --git a/pyqtgraph/widgets/ValueLabel.py b/pyqtgraph/widgets/ValueLabel.py deleted file mode 100644 index 0714689..0000000 --- a/pyqtgraph/widgets/ValueLabel.py +++ /dev/null @@ -1,72 +0,0 @@ -from ..Qt import QtCore, QtGui -from ..ptime import time -from .. import functions as fn - -__all__ = ['ValueLabel'] - -class ValueLabel(QtGui.QLabel): - """ - QLabel specifically for displaying numerical values. - Extends QLabel adding some extra functionality: - - - displaying units with si prefix - - built-in exponential averaging - """ - - def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None): - """ - ============== ================================================================================== - **Arguments:** - suffix (str or None) The suffix to place after the value - siPrefix (bool) Whether to add an SI prefix to the units and display a scaled value - averageTime (float) The length of time in seconds to average values. If this value - is 0, then no averaging is performed. As this value increases - the display value will appear to change more slowly and smoothly. - formatStr (str) Optionally, provide a format string to use when displaying text. The text - will be generated by calling formatStr.format(value=, avgValue=, suffix=) - (see Python documentation on str.format) - This option is not compatible with siPrefix - ============== ================================================================================== - """ - QtGui.QLabel.__init__(self, parent) - self.values = [] - self.averageTime = averageTime ## no averaging by default - self.suffix = suffix - self.siPrefix = siPrefix - if formatStr is None: - formatStr = '{avgValue:0.2g} {suffix}' - self.formatStr = formatStr - - def setValue(self, value): - now = time() - self.values.append((now, value)) - cutoff = now - self.averageTime - while len(self.values) > 0 and self.values[0][0] < cutoff: - self.values.pop(0) - self.update() - - def setFormatStr(self, text): - self.formatStr = text - self.update() - - def setAverageTime(self, t): - self.averageTime = t - - def averageValue(self): - return sum(v[1] for v in self.values) / float(len(self.values)) - - - def paintEvent(self, ev): - self.setText(self.generateText()) - return super().paintEvent(ev) - - def generateText(self): - if len(self.values) == 0: - return '' - avg = self.averageValue() - val = self.values[-1][1] - if self.siPrefix: - return fn.siFormat(avg, suffix=self.suffix) - else: - return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix) - diff --git a/pyqtgraph/widgets/VerticalLabel.py b/pyqtgraph/widgets/VerticalLabel.py deleted file mode 100644 index c8cc80b..0000000 --- a/pyqtgraph/widgets/VerticalLabel.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore - -__all__ = ['VerticalLabel'] -#class VerticalLabel(QtGui.QLabel): - #def paintEvent(self, ev): - #p = QtGui.QPainter(self) - #p.rotate(-90) - #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, self.text()) - #p.end() - #self.setMinimumWidth(self.hint.height()) - #self.setMinimumHeight(self.hint.width()) - - #def sizeHint(self): - #if hasattr(self, 'hint'): - #return QtCore.QSize(self.hint.height(), self.hint.width()) - #else: - #return QtCore.QSize(16, 50) - -class VerticalLabel(QtGui.QLabel): - def __init__(self, text, orientation='vertical', forceWidth=True): - QtGui.QLabel.__init__(self, text) - self.forceWidth = forceWidth - self.orientation = None - self.setOrientation(orientation) - - def setOrientation(self, o): - if self.orientation == o: - return - self.orientation = o - self.update() - self.updateGeometry() - - def paintEvent(self, ev): - p = QtGui.QPainter(self) - #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) - #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) - #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) - - #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) - - if self.orientation == 'vertical': - p.rotate(-90) - rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) - else: - rgn = self.contentsRect() - align = self.alignment() - #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter - - self.hint = p.drawText(rgn, align, self.text()) - p.end() - - if self.orientation == 'vertical': - self.setMaximumWidth(self.hint.height()) - self.setMinimumWidth(0) - self.setMaximumHeight(16777215) - if self.forceWidth: - self.setMinimumHeight(self.hint.width()) - else: - self.setMinimumHeight(0) - else: - self.setMaximumHeight(self.hint.height()) - self.setMinimumHeight(0) - self.setMaximumWidth(16777215) - if self.forceWidth: - self.setMinimumWidth(self.hint.width()) - else: - self.setMinimumWidth(0) - - def sizeHint(self): - if self.orientation == 'vertical': - if hasattr(self, 'hint'): - return QtCore.QSize(self.hint.height(), self.hint.width()) - else: - return QtCore.QSize(19, 50) - else: - if hasattr(self, 'hint'): - return QtCore.QSize(self.hint.width(), self.hint.height()) - else: - return QtCore.QSize(50, 19) - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - w = QtGui.QWidget() - l = QtGui.QGridLayout() - w.setLayout(l) - - l1 = VerticalLabel("text 1", orientation='horizontal') - l2 = VerticalLabel("text 2") - l3 = VerticalLabel("text 3") - l4 = VerticalLabel("text 4", orientation='horizontal') - l.addWidget(l1, 0, 0) - l.addWidget(l2, 1, 1) - l.addWidget(l3, 2, 2) - l.addWidget(l4, 3, 3) - win.setCentralWidget(w) - win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/__init__.py b/pyqtgraph/widgets/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-36.pyc deleted file mode 100644 index b7e0b42..0000000 Binary files a/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-37.pyc deleted file mode 100644 index 50ffb51..0000000 Binary files a/pyqtgraph/widgets/__pycache__/BusyCursor.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/CheckTable.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/CheckTable.cpython-36.pyc deleted file mode 100644 index 583d373..0000000 Binary files a/pyqtgraph/widgets/__pycache__/CheckTable.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/CheckTable.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/CheckTable.cpython-37.pyc deleted file mode 100644 index 1325eeb..0000000 Binary files a/pyqtgraph/widgets/__pycache__/CheckTable.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ColorButton.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ColorButton.cpython-36.pyc deleted file mode 100644 index 8d6bdb8..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ColorButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ColorButton.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ColorButton.cpython-37.pyc deleted file mode 100644 index fd5e142..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ColorButton.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-36.pyc deleted file mode 100644 index 1eb2730..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-37.pyc deleted file mode 100644 index 502f389..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ColorMapWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ComboBox.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ComboBox.cpython-36.pyc deleted file mode 100644 index dd96fc0..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ComboBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ComboBox.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ComboBox.cpython-37.pyc deleted file mode 100644 index 925875e..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ComboBox.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-36.pyc deleted file mode 100644 index 8dc8a6f..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-37.pyc deleted file mode 100644 index 24028f3..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DataFilterWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-36.pyc deleted file mode 100644 index 756f85f..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-37.pyc deleted file mode 100644 index ad7af66..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DataTreeWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-36.pyc deleted file mode 100644 index 7e2515b..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-37.pyc deleted file mode 100644 index 27429e2..0000000 Binary files a/pyqtgraph/widgets/__pycache__/DiffTreeWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-36.pyc deleted file mode 100644 index be74848..0000000 Binary files a/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-37.pyc deleted file mode 100644 index 92af362..0000000 Binary files a/pyqtgraph/widgets/__pycache__/FeedbackButton.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/FileDialog.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/FileDialog.cpython-36.pyc deleted file mode 100644 index 80c64ca..0000000 Binary files a/pyqtgraph/widgets/__pycache__/FileDialog.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/FileDialog.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/FileDialog.cpython-37.pyc deleted file mode 100644 index ff802c2..0000000 Binary files a/pyqtgraph/widgets/__pycache__/FileDialog.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-36.pyc deleted file mode 100644 index 0971da5..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-37.pyc deleted file mode 100644 index 28cc4af..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GradientWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-36.pyc deleted file mode 100644 index beb2a17..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-37.pyc deleted file mode 100644 index 9acef67..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GraphicsLayoutWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-36.pyc deleted file mode 100644 index 035250f..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-37.pyc deleted file mode 100644 index 48cd89a..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GraphicsView.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GroupBox.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/GroupBox.cpython-36.pyc deleted file mode 100644 index 9f08a1a..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GroupBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/GroupBox.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/GroupBox.cpython-37.pyc deleted file mode 100644 index 14a8a28..0000000 Binary files a/pyqtgraph/widgets/__pycache__/GroupBox.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-36.pyc deleted file mode 100644 index a6096c5..0000000 Binary files a/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-37.pyc deleted file mode 100644 index 691825d..0000000 Binary files a/pyqtgraph/widgets/__pycache__/HistogramLUTWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-36.pyc deleted file mode 100644 index 13f1793..0000000 Binary files a/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-37.pyc deleted file mode 100644 index d4cbb95..0000000 Binary files a/pyqtgraph/widgets/__pycache__/JoystickButton.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-36.pyc deleted file mode 100644 index 2c7ef2d..0000000 Binary files a/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-37.pyc deleted file mode 100644 index 81d1916..0000000 Binary files a/pyqtgraph/widgets/__pycache__/LayoutWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/MatplotlibWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/MatplotlibWidget.cpython-36.pyc deleted file mode 100644 index 2c08ec8..0000000 Binary files a/pyqtgraph/widgets/__pycache__/MatplotlibWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-36.pyc deleted file mode 100644 index 7885c10..0000000 Binary files a/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-37.pyc deleted file mode 100644 index e061d9f..0000000 Binary files a/pyqtgraph/widgets/__pycache__/MultiPlotWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/PathButton.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/PathButton.cpython-36.pyc deleted file mode 100644 index 47f83a8..0000000 Binary files a/pyqtgraph/widgets/__pycache__/PathButton.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/PathButton.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/PathButton.cpython-37.pyc deleted file mode 100644 index 35de0f9..0000000 Binary files a/pyqtgraph/widgets/__pycache__/PathButton.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-36.pyc deleted file mode 100644 index f1d781e..0000000 Binary files a/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-37.pyc deleted file mode 100644 index a3ee7d4..0000000 Binary files a/pyqtgraph/widgets/__pycache__/PlotWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-36.pyc deleted file mode 100644 index 32363a8..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-37.pyc deleted file mode 100644 index 637f893..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ProgressDialog.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/RawImageWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/RawImageWidget.cpython-36.pyc deleted file mode 100644 index 19283ed..0000000 Binary files a/pyqtgraph/widgets/__pycache__/RawImageWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-36.pyc deleted file mode 100644 index 93c312a..0000000 Binary files a/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-37.pyc deleted file mode 100644 index cdb3f35..0000000 Binary files a/pyqtgraph/widgets/__pycache__/RemoteGraphicsView.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-36.pyc deleted file mode 100644 index d383c0c..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-37.pyc deleted file mode 100644 index bdce564..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ScatterPlotWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/SpinBox.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/SpinBox.cpython-36.pyc deleted file mode 100644 index ec4fbb1..0000000 Binary files a/pyqtgraph/widgets/__pycache__/SpinBox.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/SpinBox.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/SpinBox.cpython-37.pyc deleted file mode 100644 index fa37d6a..0000000 Binary files a/pyqtgraph/widgets/__pycache__/SpinBox.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/TableWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/TableWidget.cpython-36.pyc deleted file mode 100644 index 7e41f90..0000000 Binary files a/pyqtgraph/widgets/__pycache__/TableWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/TableWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/TableWidget.cpython-37.pyc deleted file mode 100644 index a780628..0000000 Binary files a/pyqtgraph/widgets/__pycache__/TableWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-36.pyc deleted file mode 100644 index 7a47c91..0000000 Binary files a/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-37.pyc deleted file mode 100644 index b15cc96..0000000 Binary files a/pyqtgraph/widgets/__pycache__/TreeWidget.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-36.pyc deleted file mode 100644 index 129671b..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-37.pyc deleted file mode 100644 index 4f9881e..0000000 Binary files a/pyqtgraph/widgets/__pycache__/ValueLabel.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-36.pyc deleted file mode 100644 index 93ace9f..0000000 Binary files a/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-37.pyc deleted file mode 100644 index 2d4923c..0000000 Binary files a/pyqtgraph/widgets/__pycache__/VerticalLabel.cpython-37.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/__init__.cpython-36.pyc b/pyqtgraph/widgets/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 2c482ec..0000000 Binary files a/pyqtgraph/widgets/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/pyqtgraph/widgets/__pycache__/__init__.cpython-37.pyc b/pyqtgraph/widgets/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 74ac2a6..0000000 Binary files a/pyqtgraph/widgets/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/setup_conda_env.bat b/setup_conda_env.bat index 6d3a742..89a5afe 100644 --- a/setup_conda_env.bat +++ b/setup_conda_env.bat @@ -2,6 +2,11 @@ SET mypath=%~d0 %mypath% SET mypath=%~p0 cd %mypath% + +echo Try to remove existing ACT_python3 environment. +call conda deactivate +call conda remove -n ACT_python3 --all -y +echo Creating new environment. call conda env create -f ACT_python3.yml call conda activate ACT_python3 echo Environment ACT_python3 is set. Please use this environment to run program. diff --git a/simulation/generate_DL.py b/simulation/ACT_DL_simulation.py similarity index 100% rename from simulation/generate_DL.py rename to simulation/ACT_DL_simulation.py diff --git a/simulation/ACT_SR_simulation_final.ipynb b/simulation/ACT_SR_simulation_final.ipynb new file mode 100644 index 0000000..6de6b8d --- /dev/null +++ b/simulation/ACT_SR_simulation_final.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a0bc1bd5", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from PIL import Image, ImageDraw\n", + "from scipy import ndimage\n", + "import tifffile as tiff\n", + "import matplotlib.pyplot as plt\n", + "from tqdm import tqdm\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6e0364c6", + "metadata": {}, + "outputs": [], + "source": [ + "import toolbox" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e300fdbf", + "metadata": {}, + "outputs": [], + "source": [ + "obj = toolbox.SR_image(4096, 8, 12.65)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e1ed57e0-2313-465e-8568-68e52675aac5", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = r\"D:\\Work\\ACT_simulation\\Simulated_imgs_straight_2\"\n", + "filenames = [\"X0Y0R0W0\",\"X0Y0R0W1\",\"X0Y0R0W2\",\"X0Y0R1W0\",\"X0Y0R1W1\",\"X0Y0R1W2\",\"X0Y0R2W0\",\"X0Y0R2W1\",\"X0Y0R2W2\" ]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1c907763-61fb-4a95-a2a4-d8120ffe6013", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████| 9/9 [11:04<00:00, 73.87s/it]\n" + ] + } + ], + "source": [ + "obj.set_particle_details(shape='straight', number=50, size='auto')\n", + "obj.set_intensity(mean=4000, sigma=0.2)\n", + "obj.set_noise(mean=1000, sigma=0.2)\n", + "obj.set_frame(number_of_frame=500)\n", + "obj.set_drift(apply_drift=1, max_drift_per_frame=3)\n", + "obj.generate_particle_list()\n", + "for i in tqdm(filenames):\n", + " obj.generate_particle_positions()\n", + " obj.set_path_name(data_path, i)\n", + " obj.draw_groundtruth()\n", + " obj.generate_blinks()\n", + " obj.generate_base_image()\n", + " obj.add_fiducials(number_of_fiducial=3, fiducial_brightness_rate=8)\n", + " obj.create_SR_stack()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "654e8e9e-cef1-48e0-8ace-dc92b7b2fd42", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|████████████████████████████████████████████████████████████████████████████████████| 9/9 [11:50<00:00, 78.99s/it]\n" + ] + } + ], + "source": [ + "data_path = r\"D:\\Work\\ACT_simulation\\Simulated_imgs_dot\"\n", + "filenames = [\"X0Y0R0W0\",\"X0Y0R0W1\",\"X0Y0R0W2\",\"X0Y0R1W0\",\"X0Y0R1W1\",\"X0Y0R1W2\",\"X0Y0R2W0\",\"X0Y0R2W1\",\"X0Y0R2W2\" ]\n", + "obj.set_particle_details(shape='dot', number=100, size=100)\n", + "obj.set_intensity(mean=4000, sigma=0.2)\n", + "obj.set_noise(mean=1000, sigma=0.2)\n", + "obj.set_frame(number_of_frame=500)\n", + "obj.set_drift(apply_drift=1, max_drift_per_frame=3)\n", + "obj.generate_particle_list()\n", + "for i in tqdm(filenames):\n", + " obj.generate_particle_positions()\n", + " obj.set_path_name(data_path, i)\n", + " obj.draw_groundtruth()\n", + " obj.generate_blinks()\n", + " obj.generate_base_image()\n", + " obj.add_fiducials(number_of_fiducial=3, fiducial_brightness_rate=8)\n", + " obj.create_SR_stack()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e8242c5-a71e-4200-adf1-4c73577bed53", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (py3venv)", + "language": "python", + "name": "py3venv" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.16" + }, + "toc-autonumbering": true, + "toc-showmarkdowntxt": true + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/simulation/generate_SR.py b/simulation/generate_SR.py deleted file mode 100644 index eb51860..0000000 --- a/simulation/generate_SR.py +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 23 13:34:18 2022 - -@author: Trevor Wu -""" -import numpy as np -from PIL import Image, ImageDraw -from scipy import ndimage - - -## parameters for images -DL_img_size = 500 -mag = 5 -SR_img_size = DL_img_size * mag -DL_pixel_size = 100 # nm -FWHM = 400 # nm -FWHM_px_SR = int(mag*FWHM/DL_pixel_size) -mean_intensity = 4000 -sigma_intensity = 0.2 -noise_intensity = 1000 -sigma_noise = 0.5 -num_frames = 1000 - -## parameters for aggregates -num_straight = 50 # number of straight fibrils -num_dot = 50 # number of dots -num_curly = 50 # number of curly fibrils - -total_num = num_straight + num_dot + num_curly - -len_straight = 20 # pixels in DL img -dia_dot = 1 # pixels in DL img -len_curly = 100 # pixels in DL img - -sigma = 0.1 # variation in size of all three types of aggregates - -# drift -min_move = 5 #nm, drift in x or y between frames - -# select blinking spots - some dots remains for more than one frame -num_blink = 100 # num of dots on one frame, 100 -num_lasting = 20 # num of dots blinking till the next frame, must be smaller than num_blink, 20 -num_fid = 2 # 3 fiducial markers in each frame -intensity_fid = 5*mean_intensity # fiducials are 5 times as bright as normal fluorophores - -# folder for DL images (blinkings) -path = r'I:\Data\20220714_fake_images\test\im4\raw' -# name of ground truth super-res image -SR_name = path+r"\SR.tif" - -SR_len_straight = len_straight * mag -SR_dia_dot = dia_dot * mag -SR_len_curly = len_curly * mag - -straight_len_list1 = np.random.normal(SR_len_straight, sigma*SR_len_straight, num_straight) -straight_len_list2 = np.random.normal(SR_len_straight, sigma*SR_len_straight, num_straight) -dot_dia_list1 = np.random.normal(SR_dia_dot, sigma*SR_dia_dot, num_dot) -dot_dia_list2 = np.random.normal(SR_dia_dot, sigma*SR_dia_dot, num_dot) -curly_len_list1 = np.random.normal(SR_len_curly, sigma*SR_len_curly, num_curly) -curly_len_list2 = np.random.normal(SR_len_curly, sigma*SR_len_curly, num_curly) - -straight_len_list1 = straight_len_list1.astype(np.int32) -straight_len_list2 = straight_len_list2.astype(np.int32) -dot_dia_list1 = dot_dia_list1.astype(np.int32) -dot_dia_list2 = dot_dia_list2.astype(np.int32) -curly_len_list1 = curly_len_list1.astype(np.int32) -curly_len_list2 = curly_len_list2.astype(np.int32) - -x_list = np.random.randint(0, SR_img_size, size = total_num) -y_list = np.random.randint(0, SR_img_size, size = total_num) - -rotate_list = np.random.randint(0, 360, size = num_straight) - -# for curly fibrils, additional info about its shape is needed -min_angle = 10 -max_angle = 30 -start_angle_list = np.random.randint(0, 360, size = num_curly) -angle_list = np.random.randint(min_angle, max_angle, size = num_curly) -end_angle_list = start_angle_list + angle_list - -# draw straight lines in an image -straight_list = [] -for i in range(0, num_straight): - w, h = straight_len_list1[i], straight_len_list2[i] - shape = [(0, 0), (w, h)] - img_straight = Image.new("I", (w, h)) - img1_straight = ImageDraw.Draw(img_straight) - img1_straight.line(shape, width = 2) - img_straight = img_straight.rotate(rotate_list[i]) - img_straight_arr = np.asarray(img_straight) - nz = np.nonzero(img_straight_arr) - straight_arr = img_straight_arr[nz[0].min():nz[0].max()+1, nz[1].min():nz[1].max()+1] - straight_list.append(straight_arr) - -# draw dots in an image -dot_list = [] -for i in range(0, num_dot): - w, h = dot_dia_list1[i], dot_dia_list2[i] - shape = [(1, 1), (w-1, h-1)] - img_dot = Image.new("I", (w, h)) - img1_dot = ImageDraw.Draw(img_dot) - img1_dot.ellipse(shape, fill = 1) - dot_arr = np.asarray(img_dot) - dot_list.append(dot_arr) - -# draw curly fibril in an image -curly_list = [] -for i in range(0, num_curly): - w, h = curly_len_list1[i], curly_len_list2[i] - shape = [(1, 1), (w, h)] - img_curly = Image.new("I", (w-1, h-1)) - img1_curly = ImageDraw.Draw(img_curly) - img1_curly.arc(shape, start = start_angle_list[i], end = end_angle_list[i]) - img_curly_arr = np.asarray(img_curly) - nz = np.nonzero(img_curly_arr) - curly_arr = img_curly_arr[nz[0].min():nz[0].max()+1, nz[1].min():nz[1].max()+1] - curly_arr = ndimage.binary_dilation(curly_arr) - curly_list.append(curly_arr) - -def crop_img(small_img, large_img, x, y): - small_size = np.shape(small_img) - large_size = np.shape(large_img) - if x + small_size[0] >= large_size[0]: - small_img = small_img[0:(large_size[0] - x -1), :] - if y + small_size[1] >= large_size[1]: - small_img = small_img[:, 0:large_size[1] - y -1] - return small_img - -SR_blank = np.zeros([SR_img_size, SR_img_size], dtype = np.int32) -for i in range(0, num_straight): - x, y = x_list[i], y_list[i] - straight_img = crop_img(straight_list[i], SR_blank, x, y) - SR_blank[x:x+np.shape(straight_img)[0], y:y+np.shape(straight_img)[1]] += straight_img - -for i in range(0, num_dot): - x, y = x_list[num_straight+i], y_list[num_straight+i] - dot_img = crop_img(dot_list[i], SR_blank, x, y) - SR_blank[x:x+np.shape(dot_img)[0], y:y+np.shape(dot_img)[1]] += dot_img - -for i in range(0, num_curly): - x, y = x_list[num_straight+num_dot+i], y_list[num_straight+num_dot+i] - curly_img = crop_img(curly_list[i], SR_blank, x, y) - SR_blank[x:x+np.shape(curly_img)[0], y:y+np.shape(curly_img)[1]] += curly_img - -SR_img = Image.fromarray(SR_blank) -SR_img.save(SR_name) - -# get coordinates of binding sites -index_list = list(np.stack(np.nonzero(SR_img), axis = -1)) - -# get coordinates of multiple binding sites, add into the index list -SR_array = np.copy(SR_blank) -multiple_sites = np.stack(np.where(SR_array>1), axis = -1) -for i in range(0, np.shape(multiple_sites)[0]): - num_sites = SR_array[tuple(multiple_sites[i])] - for j in range(0, num_sites-1): - index_list.append(multiple_sites[i]) - - - -# define fiducials -x_fid = np.random.randint(int(SR_img_size*0.1), int(SR_img_size*0.9), size = num_fid) -y_fid = np.random.randint(int(SR_img_size*0.1), int(SR_img_size*0.9), size = num_fid) - -# define drift trajectory - -x_steps = np.random.choice([-min_move, min_move], size = num_frames) + 0.1 * np.random.randn(num_frames) -y_steps = np.random.choice([-min_move, min_move], size = num_frames) + 0.1 * np.random.randn(num_frames) -x_drift = np.cumsum(x_steps) -y_drift = np.cumsum(y_steps) -x_drift_SR = np.round(x_drift/DL_pixel_size*mag).astype(np.int32) -y_drift_SR = np.round(y_drift/DL_pixel_size*mag).astype(np.int32) - -# for first frame -blink_index = list(np.random.randint(0, len(index_list), num_blink)) -blink = [] -blink.append(blink_index) - -for i in range(1, num_frames): - remain_index = np.random.randint(0, num_blink, num_lasting) #from 100 blinks on the previous frame, take out 50 indices - new_index = np.random.randint(0, len(index_list), num_blink-num_lasting) # from the whole list, take out 100-50 indices - blink_list = [] - for j in range(0, num_lasting): - blink_list.append(blink_index[remain_index[j]]) - for k in range(0, num_blink-num_lasting): - blink_list.append(new_index[k]) - blink.append(blink_list) - blink_index = blink_list.copy() - -def create_frame(blink, num_blink, frame, index_list, SR_img_size, FWHM_px_SR, path): - blank = np.zeros((SR_img_size, SR_img_size)) - list_intensity = np.random.normal(mean_intensity, mean_intensity*sigma_intensity, size = num_blink) - list_dia = np.random.normal(FWHM_px_SR*1.1, FWHM_px_SR*0.1, size = num_blink) - list_dia = list_dia.astype(np.int16) - for i in range(0, num_blink): - x, y = np.meshgrid(np.linspace(-1,1,list_dia[i]*2), np.linspace(-1,1,list_dia[i]*2)) - dst = np.sqrt(x*x+y*y) - # Initializing sigma and muu - sigma = 0.3 - muu = 0 - # Calculating Gaussian array - gauss = np.exp(-( (dst-muu)**2 / ( 2.0 * sigma**2 ) ) ) - gauss_intensity = gauss*list_intensity[i] - dot_size = int(list_dia[i]) - coord_x, coord_y = index_list[int(blink[frame][i])][0], index_list[int(blink[frame][i])][1] - if coord_x-dot_size < 0 or coord_y-dot_size<0 or coord_x+dot_size>SR_img_size or coord_y+dot_size>SR_img_size: - pass - else: - blank[coord_x-dot_size:coord_x+dot_size, coord_y-dot_size:coord_y+dot_size] += gauss_intensity - SR_frame = blank.copy() - return SR_frame - -def add_fiducial(SR_frame, num_fid, x_fid, y_fid, intensity_fid, FWHM_px_SR): - SR_frame_addfid = SR_frame.copy() - for i in range(0, num_fid): - x, y = np.meshgrid(np.linspace(-1,1,FWHM_px_SR*2), np.linspace(-1,1,FWHM_px_SR*2)) - dst = np.sqrt(x*x+y*y) - # Initializing sigma and muu - sigma = 0.3 - muu = 0 - # Calculating Gaussian array - gauss = np.exp(-( (dst-muu)**2 / ( 2.0 * sigma**2 ) ) ) - gauss_intensity = gauss*intensity_fid - SR_frame_addfid[int(x_fid[i]-FWHM_px_SR) : int(x_fid[i]+FWHM_px_SR), int(y_fid[i]-FWHM_px_SR):int(y_fid[i]+FWHM_px_SR)] += gauss_intensity - SR_frame_fid = SR_frame_addfid.copy() - return SR_frame_fid - -def add_drift(SR_frame_fid, x_drift_SR, y_drift_SR, frame): - x_d = x_drift_SR[frame] - y_d = y_drift_SR[frame] - x_size = np.shape(SR_frame_fid)[0] - y_size = np.shape(SR_frame_fid)[1] - if x_d == 0: - frame_x = SR_frame_fid.copy() - elif x_d > 0: - frame_x = SR_frame_fid.copy() - padding_x = np.zeros((x_d, y_size)) - frame_drift_x = np.concatenate((padding_x, frame_x), axis = 0) - frame_x = frame_drift_x[:x_size, :] - elif x_d < 0: - frame_x = SR_frame_fid.copy() - padding_x = np.zeros((-x_d, y_size)) - frame_drift_x = np.concatenate((frame_x, padding_x), axis = 0) - frame_x = frame_drift_x[-x_d:, :] - if y_d == 0: - frame_y = frame_x.copy() - elif y_d > 0: - frame_y = frame_x.copy() - padding_y = np.zeros((x_size, y_d)) - frame_drift_y = np.concatenate((padding_y, frame_y), axis = 1) - frame_y = frame_drift_y[:, :y_size] - elif y_d < 0: - frame_y = frame_x.copy() - padding_y = np.zeros((x_size,-y_d)) - frame_drift_y = np.concatenate((frame_y, padding_y), axis = 1) - frame_y = frame_drift_y[:, -y_d:] - return frame_y - -def add_noise(DL_frame, noise_intensity, sigma_noise): - noise = np.random.normal(noise_intensity, sigma_noise*noise_intensity, size = np.shape(DL_frame)) - noisy_frame = DL_frame + noise - return noisy_frame - -for frame in range(0, num_frames): - SR_frame = create_frame(blink, num_blink, frame, index_list, SR_img_size, FWHM_px_SR, path) - SR_frame_fid = add_fiducial(SR_frame, num_fid, x_fid, y_fid, intensity_fid, FWHM_px_SR) - frame_drift = add_drift(SR_frame_fid, x_drift_SR, y_drift_SR, frame) - DL_frame = frame_drift.copy() - DL_frame = DL_frame.reshape(int(DL_frame.shape[0]/mag), mag, int(DL_frame.shape[1]/mag), mag).mean(-1).mean(1) - noisy_frame = add_noise(DL_frame, noise_intensity, sigma_noise) - final_frame = Image.fromarray(noisy_frame) - frame_name = path + '\\'+ str(frame) + '.tif' - final_frame.save(frame_name) \ No newline at end of file diff --git a/simulation/toolbox.py b/simulation/toolbox.py new file mode 100644 index 0000000..be9c87d --- /dev/null +++ b/simulation/toolbox.py @@ -0,0 +1,311 @@ +import os +import numpy as np +import pandas as pd +from PIL import Image, ImageDraw +from scipy import ndimage +import tifffile as tiff + +class SR_image: + def __init__(self, SR_image_size, magnification, SR_pixel_size): + # Image parameters + self.SR_image_size = SR_image_size + self.magnification = magnification + self.SR_pixel_size = SR_pixel_size + self.DL_image_size = self.SR_image_size/self.magnification + self.DL_pixel_size = self.SR_pixel_size*self.magnification + + self.FWHM_DL_nm = 400 + self.FWHM_SR_pixel = int(self.magnification*self.FWHM_DL_nm/self.DL_pixel_size) + + # parameters for particles + self.particle_shape = 'straight' + self.number_of_particles = 50 + self.particle_size = 'auto' + self.intensity_mean = 4000 + self.intensity_sigma = 0.2 + + # parameters for noise + self.noise_mean = 1000 + self.noise_sigma = 0.5 + + # parameters for SR simulation generation + self.number_of_frame = 1000 + self.max_blink_per_image = 2*self.number_of_particles + self.percentage_leftover = 0.2 + + # parameters for drift + self.apply_drift = 1 + self.max_drift_per_frame = 5 # nm + + + def set_particle_details(self, shape='straight', number=50, size='auto'): + self.particle_shape = shape + self.number_of_particles = number + self.particle_size = size + + + def set_intensity(self, mean, sigma): + self.intensity_mean = mean + self.intensity_sigma = sigma + + + def set_noise(self, mean, sigma): + self.noise_mean = mean + self.noise_sigma = sigma + + + def set_frame(self, number_of_frame): + self.number_of_frame = number_of_frame + + + def set_path_name(self, path, name): + self.ground_truth_path = path + r'\ground_truth_images' + if not os.path.isdir(self.ground_truth_path): + os.mkdir(self.ground_truth_path) + + self.simulated_image_path = path + r'\simulated_SR_data' + if not os.path.isdir(self.simulated_image_path): + os.mkdir(self.simulated_image_path) + + self.filename = name + + + def set_drift(self, apply_drift=1, max_drift_per_frame=5): + self.apply_drift=apply_drift + self.max_drift_per_frame=max_drift_per_frame + + + def gaussian_dist(self, x, muu, sigma, amp=0): + if amp == 0: + amp = (1/(sigma*((2*np.pi)**(1/2)))) + return amp*np.exp(-0.5*((x-muu)/sigma)**2) + + + def generate_particle_list(self): + # Generate particle list formed by image of particle (in array format) + # Generate position list for these particles + def error_return(): + try: + return self.particle_list + except NameError: + print('No previous list found') + return 0 + + if (self.particle_shape != 'straight') & (self.particle_shape != 'dot'): # prevent unknown shape + print('Unknown shape, try to return the previous list') + error_return() + + rotate_list = np.random.randint(0, 360, size = self.number_of_particles) + + # prepare self.size_nm_list + if self.particle_size == 'auto': + if self.particle_shape == 'straight': + number_of_groups = np.random.randint(1, 6) + particle_per_group = int(self.number_of_particles/number_of_groups) + group_mean_size = np.random.randint(self.SR_image_size*self.SR_pixel_size*0.01,self.SR_image_size*self.SR_pixel_size*0.1, size=number_of_groups) + self.size_nm_list = np.array([]) # size in nm + for g in range(0, number_of_groups): + self.size_nm_list = np.concatenate((self.size_nm_list, np.random.normal(group_mean_size[g], group_mean_size[g]*0.10, size=particle_per_group))) + elif self.particle_shape == 'dot': + number_of_groups = np.random.randint(1, 6) + particle_per_group = int(self.number_of_particles/number_of_groups) + group_mean_size = np.random.randint(50, 200, size=number_of_groups) + self.size_nm_list = np.array([]) # size in nm + for g in range(0, number_of_groups): + self.size_nm_list = np.concatenate((self.size_nm_list, np.random.normal(group_mean_size[g], group_mean_size[g]*0.10, size=particle_per_group))) + else: # fixed size + try: + self.size_nm_list = np.random.normal(self.particle_size, self.particle_size*0.10, size=self.number_of_particles) + except TypeError: + print('Unknown size, try to return the previous list') + error_return() + + size_pixel_list = self.size_nm_list/self.SR_pixel_size + self.size_pixel_sr_list = np.round(np.sqrt(0.5*(size_pixel_list**2))) # calculate the image for line by trigonometry + self.size_pixel_sr_list = self.size_pixel_sr_list.astype(np.int32) # Then round and convert to int32 + self.number_of_particles = len(self.size_pixel_sr_list) # set number of particles to actual number of particles generated + + # prepare particle_list + self.particle_list = [] + if self.particle_shape == 'straight': + for i in range(0, self.number_of_particles): + side = self.size_pixel_sr_list[i] + shape = [(0, 0), (side, side)] + canvas = Image.new("I", (side, side)) + img = ImageDraw.Draw(canvas) + img.line(shape, width = 2) + canvas = canvas.rotate(angle=rotate_list[i], expand=True, resample=Image.Resampling.NEAREST) + img_arr = np.asarray(canvas) + nz = np.nonzero(img_arr) + img_arr = img_arr[nz[0].min():nz[0].max()+1, nz[1].min():nz[1].max()+1] + self.particle_list.append(img_arr) + elif self.particle_shape == 'dot': + for i in range(0, self.number_of_particles): + side = self.size_pixel_sr_list[i] + shape = [(0, 0), (side-1, side-1)] + canvas = Image.new("I", (side, side)) + img = ImageDraw.Draw(canvas) + img.ellipse(shape, fill=1) + img_arr = np.asarray(canvas) + self.particle_list.append(img_arr) + return self.particle_list + + + def generate_particle_positions(self): + # generate position list for particles (left top as 0,0) + self.x_list = np.random.randint(0, self.SR_image_size, size=self.number_of_particles) + self.y_list = np.random.randint(0, self.SR_image_size, size=self.number_of_particles) + return self.x_list, self.y_list + + + def draw_groundtruth(self): + # save the ground truth data for reference + simulation_details = pd.DataFrame({ + 'x_pixel': self.x_list, + 'y_pixel': self.y_list, + 'length': self.size_nm_list, + 'sr_pixel_size': self.size_pixel_sr_list + }) + simulation_details.to_csv(self.ground_truth_path + '\\' + self.filename + '.csv') + + # Create a empty SR image canvas with set size + # Plot all the particle generated onto the canvas + self.SR_groundtruth_image = np.zeros([self.SR_image_size, self.SR_image_size], dtype = np.int32) + canvas_shape = self.SR_groundtruth_image.shape + for i in range(0, self.number_of_particles): + x, y = self.x_list[i], self.y_list[i] + particle = self.particle_list[i] + particle_shape = particle.shape + if x + particle_shape[0] >= canvas_shape[0]: + x -= particle_shape[0] + if y + particle_shape[1] >= canvas_shape[1]: + y -= particle_shape[1] + self.SR_groundtruth_image[x:x+particle_shape[0], y:y+particle_shape[1]] += particle + img = Image.fromarray(self.SR_groundtruth_image) + img.save(self.ground_truth_path + '\\' + self.filename + '.tif') + return self.SR_groundtruth_image + + + def generate_blinks(self): + # Generate blink list + # Find the non-zero indices and values in the array + nonzero_indices = np.nonzero(self.SR_groundtruth_image) + values = self.SR_groundtruth_image[nonzero_indices] + # Create a list of coordinates based on the values + self.blink_list = np.array([coord for coord, value in zip(zip(*nonzero_indices), values) for _ in range(value)]) + + return self.blink_list + + + def generate_base_image(self): + self.base_simulated_image = np.zeros([self.SR_image_size, self.SR_image_size], dtype = np.int32) + return self.base_simulated_image + + + def add_fiducials(self, number_of_fiducial=3, fiducial_brightness_rate=5): + """ + paras: + number_of_fiducial: int; + fiducial_brightness_rate: float, in terms of x times the intensity of signal. + """ + fid_x_pos = np.random.randint(int(self.SR_image_size*0.1), int(self.SR_image_size*0.9), size=number_of_fiducial) + fid_y_pos = np.random.randint(int(self.SR_image_size*0.1), int(self.SR_image_size*0.9), size=number_of_fiducial) + + # define fiducial + x, y = np.meshgrid(np.linspace(-1,1,self.FWHM_SR_pixel*2), np.linspace(-1,1,self.FWHM_SR_pixel*2)) + dst = np.sqrt(x*x+y*y) + gauss_muu = 0 + gauss_sigma = 0.3 + fiducial = self.gaussian_dist(dst, gauss_muu, gauss_sigma, amp=1) + fiducial *= self.intensity_mean*fiducial_brightness_rate + fiducial = fiducial.astype('int32') + + # add fiducial + for i in range(0, number_of_fiducial): + self.base_simulated_image[int(fid_x_pos[i]-self.FWHM_SR_pixel):int(fid_x_pos[i]+self.FWHM_SR_pixel), int(fid_y_pos[i]-self.FWHM_SR_pixel):int(fid_y_pos[i]+self.FWHM_SR_pixel)] += fiducial + return self.base_simulated_image + + + def draw_blinks(self, leftover=[]): + blinked_image = self.base_simulated_image.copy() + number_of_blinks = np.random.randint(1, 3*self.number_of_particles) # randomly pick the number of blink in a frame + blink_positions = self.blink_list[np.random.choice(np.shape(self.blink_list)[0], size=number_of_blinks, replace=False)]# get blinks from blink list + if len(leftover) != 0: # addtion of leftover blinks from previous frame + blink_positions = np.concatenate((blink_positions, leftover), axis=0) + number_of_blinks = len(blink_positions) + + intensities = np.random.normal(self.intensity_mean, self.intensity_mean*self.intensity_sigma, size=number_of_blinks) + sizes = np.random.normal(1.1*self.FWHM_SR_pixel, 0.1*self.FWHM_SR_pixel, size=number_of_blinks).astype('int32') + + for i in range(0, number_of_blinks): + x, y = np.meshgrid(np.linspace(-1,1,sizes[i]*2), np.linspace(-1,1,sizes[i]*2)) + dst = np.sqrt(x*x+y*y) + gauss_muu = 0 + gauss_sigma = 0.3 + particle = self.gaussian_dist(dst, gauss_muu, gauss_sigma, amp=1) * intensities[i] # light up the particle with gaussian blur + particle = particle.astype('int32') + + # plot particle onto base image + pos_x, pos_y = blink_positions[i] + if pos_x-sizes[i] < 0 or pos_y-sizes[i]<0 or pos_x+sizes[i]>self.SR_image_size or pos_y+sizes[i]>self.SR_image_size: + pass + else: + blinked_image[pos_x-sizes[i]:pos_x+sizes[i], pos_y-sizes[i]:pos_y+sizes[i]] += particle + if self.percentage_leftover != 0: + leftover = blink_positions[np.random.choice(np.shape(blink_positions)[0], size=int(self.percentage_leftover*np.shape(blink_positions)[0]), replace=False)] + return blinked_image, leftover + + + def generate_drift(self): + # define cummulative drift + x_trajectory_nm = np.cumsum(np.random.choice([-self.max_drift_per_frame, self.max_drift_per_frame], size=self.number_of_frame)) + y_trajectory_nm = np.cumsum(np.random.choice([-self.max_drift_per_frame, self.max_drift_per_frame], size=self.number_of_frame)) + self.x_drift_pixel = np.round(x_trajectory_nm/self.SR_pixel_size).astype(np.int32) + self.y_drift_pixel = np.round(y_trajectory_nm/self.SR_pixel_size).astype(np.int32) + return self.x_drift_pixel, self.y_drift_pixel + + + def draw_drift(self, blinked_image, frame_count): + x_drift = self.x_drift_pixel[frame_count] + y_drift = self.y_drift_pixel[frame_count] + + x_padding = np.zeros((abs(x_drift), self.SR_image_size)) + y_padding = np.zeros((self.SR_image_size, abs(y_drift))) + + drifted_image = blinked_image.copy() + if x_drift > 0: + drifted_image = np.concatenate((x_padding, drifted_image), axis=0) + drifted_image = drifted_image[:self.SR_image_size, :] + elif x_drift < 0: + drifted_image = np.concatenate((drifted_image, x_padding), axis=0) + drifted_image = drifted_image[-x_drift:, :] + else: + pass + + if y_drift > 0: + drifted_image = np.concatenate((y_padding, drifted_image), axis=1) + drifted_image = drifted_image[:, :self.SR_image_size] + elif y_drift < 0: + drifted_image = np.concatenate((drifted_image, y_padding), axis=1) + drifted_image = drifted_image[:, -y_drift:] + else: + pass + + return drifted_image + + def create_SR_stack(self): + SR_stack = [] + if self.apply_drift == 1: + self.generate_drift() + leftover = [] + for f in range(0, self.number_of_frame): + frame, leftover = self.draw_blinks(leftover=leftover) + if self.apply_drift == 1: + frame = self.draw_drift(blinked_image=frame, frame_count=f) + # reshape SR image to DL size + DL_frame = frame.reshape(int(self.DL_image_size), self.magnification, int(self.DL_image_size), self.magnification).mean(-1).mean(1) + noise = np.random.normal(self.noise_mean, self.noise_mean*self.noise_sigma, size=np.shape(DL_frame)) + DL_frame = DL_frame + noise + SR_stack.append(DL_frame) + tiff.imwrite(self.simulated_image_path+'\\'+self.filename+'.tif', SR_stack) + return SR_stack \ No newline at end of file diff --git a/toolbox.py b/toolbox.py index a293657..7d3220a 100644 --- a/toolbox.py +++ b/toolbox.py @@ -147,7 +147,6 @@ def run(self): if self.job == 'Reconstruction': try: self.project.superRes_reconstruction(progress_signal=self.progress, IJ=self.IJ) - print('returned from Reconstruction') except: print(sys.exc_info()) elif self.job == 'FiducialCorrection':