wolfhece 2.1.118__py3-none-any.whl → 2.1.119__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wolfhece/PyDraw.py +186 -48
- wolfhece/PyVertexvectors.py +5 -0
- wolfhece/apps/version.py +1 -1
- wolfhece/lazviewer/laz_viewer.py +352 -117
- wolfhece/lazviewer/viewer/viewer.py +12 -2
- wolfhece/opengl/py3d.py +10 -4
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.119.dist-info}/METADATA +1 -1
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.119.dist-info}/RECORD +11 -11
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.119.dist-info}/WHEEL +0 -0
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.119.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.119.dist-info}/top_level.txt +0 -0
wolfhece/lazviewer/laz_viewer.py
CHANGED
@@ -23,9 +23,10 @@ from ..PyWMS import getWalonmap
|
|
23
23
|
from ..PyTranslate import _
|
24
24
|
from ..color_constants import Colors
|
25
25
|
from ..PyParams import Wolf_Param, Type_Param
|
26
|
-
from ..matplotlib_fig import Matplotlib_Figure as
|
26
|
+
from ..matplotlib_fig import Matplotlib_Figure as MplFig
|
27
27
|
from ..drawing_obj import Element_To_Draw
|
28
28
|
from ..CpGrid import CpGrid
|
29
|
+
from ..PyVertexvectors import vector, Zones, zone, wolfvertex
|
29
30
|
|
30
31
|
"""
|
31
32
|
Importation et visualisation de données LAS et LAZ
|
@@ -636,7 +637,7 @@ class xyz_laz_grids():
|
|
636
637
|
|
637
638
|
(up_s, up_z, up_color), (down_s, down_z, down_color) = self.scan_around(xy, length_buffer)
|
638
639
|
|
639
|
-
figmpl =
|
640
|
+
figmpl = MplFig()
|
640
641
|
figmpl.presets()
|
641
642
|
fig = figmpl.fig
|
642
643
|
ax = figmpl.cur_ax
|
@@ -687,49 +688,116 @@ class xyz_laz_grids():
|
|
687
688
|
force_format=force_format))
|
688
689
|
|
689
690
|
|
690
|
-
class
|
691
|
+
class Wolf_LAZ_Data(Element_To_Draw):
|
692
|
+
""" Base class for LAZ data which can be imported in Pydraw.Mapviewer.
|
693
|
+
"""
|
691
694
|
|
692
695
|
def __init__(self, idx:str = '', plotted:bool = False, mapviewer = None, need_for_wx:bool = False) -> None:
|
693
696
|
|
697
|
+
self._filename:Path = Path('') # filename TODO : improve serialization
|
698
|
+
self._lazfile:Path = None # Not used
|
699
|
+
|
700
|
+
# Bounds must be set before calling super().__init__() because
|
701
|
+
# xmin, xmax, ymin, ymax properties are used and depend on _bounds
|
694
702
|
self._bounds = [[0.,0.],[0.,0.]] # [[xmin,xmax],[ymin,ymax]]
|
703
|
+
|
695
704
|
super().__init__(idx, plotted, mapviewer, need_for_wx)
|
696
705
|
|
697
|
-
self.
|
698
|
-
self.
|
699
|
-
self.classification = Classification_LAZ()
|
706
|
+
self._data:np.ndarray = None # Numpy data array -- to be plotted
|
707
|
+
self._colors:np.ndarray = None # NumPy array of colors for each point --> see viewer attributes for details
|
708
|
+
self.classification = Classification_LAZ() # Classification of LAZ data --> defining colors if codification is used
|
709
|
+
|
710
|
+
self._associated_color:int = Colors_Lazviewer.CODE_2023.value # Associated color type for LAZ data
|
711
|
+
|
712
|
+
self.viewer:viewer = None # PPTK viewer
|
713
|
+
|
714
|
+
self._point_size = .05 # Point size in viewer -- in user units
|
715
|
+
|
716
|
+
self._flight_memory = [] # Flight position
|
717
|
+
|
718
|
+
self._myprops = None # Properties based on Wolf_Param class
|
719
|
+
|
720
|
+
self._bg_color = [int(0.23*255), int(0.23*255), int(.44*255), 255] # Background color
|
721
|
+
self._bg_color_top = [0, 0, 0, 255] # Top background color
|
722
|
+
self._bg_color_bottom = [int(0.23*255), int(0.23*255), int(.44*255), 255] # Bottom background color
|
723
|
+
|
724
|
+
self._floor_level = 0. # Floor level -- user units
|
725
|
+
self._floor_color = [int(0.3*255), int(0.3*255), int(.3*255), 127] # Floor color
|
726
|
+
|
727
|
+
self._show_grid = True # Show grid in viewer
|
728
|
+
self._show_axis = True # Show axis in viewer
|
729
|
+
self._show_info = True # Show info in viewer
|
730
|
+
|
731
|
+
self._select_only_codes = [] # Codes to be selected -- Defined by user
|
732
|
+
|
733
|
+
self._xls:CpGrid = None # Excel grid for selected data
|
734
|
+
self._xlsFrame:wx.Frame = None # Frame containing the xls grid
|
735
|
+
|
736
|
+
def serialize(self):
|
737
|
+
""" Serialize class : data and attributes """
|
738
|
+
return {'bounds':self._bounds, 'data':str(self._filename) + '.npz', 'associated_color':self._associated_color,
|
739
|
+
'point_size':self._point_size, 'bg_color':self._bg_color, 'bg_color_top':self._bg_color_top,
|
740
|
+
'bg_color_bottom':self._bg_color_bottom, 'floor_level':self._floor_level, 'floor_color':self._floor_color,
|
741
|
+
'show_grid':self._show_grid, 'show_axis':self._show_axis, 'show_info':self._show_info}
|
742
|
+
|
743
|
+
def deserialize(self, data:dict):
|
744
|
+
""" Deserialize class : data and attributes """
|
745
|
+
self._bounds = data['bounds']
|
746
|
+
self._filename = Path(data['data'])
|
747
|
+
self._associated_color = data['associated_color']
|
748
|
+
self._point_size = data['point_size']
|
749
|
+
self._bg_color = data['bg_color']
|
750
|
+
self._bg_color_top = data['bg_color_top']
|
751
|
+
self._bg_color_bottom = data['bg_color_bottom']
|
752
|
+
self._floor_level = data['floor_level']
|
753
|
+
self._floor_color = data['floor_color']
|
754
|
+
self._show_grid = data['show_grid']
|
755
|
+
self._show_axis = data['show_axis']
|
756
|
+
self._show_info = data['show_info']
|
757
|
+
|
758
|
+
self.data = np.load(self._filename)['data']
|
700
759
|
|
701
|
-
|
760
|
+
def saveas(self, fn:str):
|
761
|
+
""" Save class : data and attributes """
|
762
|
+
import pickle
|
702
763
|
|
703
|
-
self.
|
704
|
-
self._colors:np.ndarray = None
|
764
|
+
self._filename = Path(fn)
|
705
765
|
|
706
|
-
|
766
|
+
with open(fn,'wb') as f:
|
767
|
+
pickle.dump(self.serialize(),f)
|
707
768
|
|
708
|
-
|
769
|
+
# save data by numpy
|
770
|
+
np.savez(str(fn) + '.npz', data=self._data)
|
709
771
|
|
710
|
-
|
772
|
+
def load(self, fn:str):
|
773
|
+
""" Load class : data and attributes """
|
774
|
+
import pickle
|
711
775
|
|
712
|
-
|
713
|
-
|
714
|
-
self._bg_color_bottom = [0,0,0,255]
|
776
|
+
with open(fn,'rb') as f:
|
777
|
+
self.deserialize(pickle.load(f))
|
715
778
|
|
716
|
-
|
717
|
-
|
779
|
+
@property
|
780
|
+
def associated_color(self):
|
781
|
+
return self._associated_color
|
718
782
|
|
719
|
-
|
720
|
-
|
721
|
-
self.
|
783
|
+
@associated_color.setter
|
784
|
+
def associated_color(self, value:int):
|
785
|
+
self._associated_color = value
|
786
|
+
self.set_colors()
|
722
787
|
|
723
|
-
|
788
|
+
def merge(self, other:"Wolf_LAZ_Data"):
|
789
|
+
""" Merge two Wolf_LAZ_Data objects """
|
724
790
|
|
725
|
-
self.
|
726
|
-
|
791
|
+
if self._data is None:
|
792
|
+
self._data = other._data
|
793
|
+
else:
|
794
|
+
self._data = np.concatenate((self._data, other._data))
|
727
795
|
|
728
|
-
|
729
|
-
|
730
|
-
# self._timer = None
|
796
|
+
self.bounds = [[min(self.bounds[0][0],other.bounds[0][0]),max(self.bounds[0][1],other.bounds[0][1])],
|
797
|
+
[min(self.bounds[1][0],other.bounds[1][0]),max(self.bounds[1][1],other.bounds[1][1])]]
|
731
798
|
|
732
799
|
def filter_data(self, codes:list[int]):
|
800
|
+
""" Filter data by codes """
|
733
801
|
self._data = self._data[np.isin(self._data[:,3],codes)]
|
734
802
|
|
735
803
|
def bg_color(self, value):
|
@@ -746,7 +814,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
746
814
|
|
747
815
|
def floor_level(self, value):
|
748
816
|
if self.viewer is not None:
|
749
|
-
return self.viewer.set(floor_level = value)
|
817
|
+
return self.viewer.set(floor_level = float(value))
|
750
818
|
|
751
819
|
def floor_color(self, value):
|
752
820
|
if self.viewer is not None:
|
@@ -754,15 +822,24 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
754
822
|
|
755
823
|
def show_grid(self, value:bool):
|
756
824
|
if self.viewer is not None:
|
757
|
-
return self.viewer.set(show_grid = value)
|
825
|
+
return self.viewer.set(show_grid = bool(value))
|
758
826
|
|
759
827
|
def show_axis(self, value:bool):
|
760
828
|
if self.viewer is not None:
|
761
|
-
return self.viewer.set(show_axis = value)
|
829
|
+
return self.viewer.set(show_axis = bool(value))
|
762
830
|
|
763
831
|
def show_info(self, value:bool):
|
764
832
|
if self.viewer is not None:
|
765
|
-
return self.viewer.set(show_info = value)
|
833
|
+
return self.viewer.set(show_info = bool(value))
|
834
|
+
|
835
|
+
def force_view(self, x, y, z = -1):
|
836
|
+
""" Force lookat position """
|
837
|
+
if z == -1:
|
838
|
+
curx,cury,curz = self.lookat
|
839
|
+
self.lookat = [x,y,curz]
|
840
|
+
else:
|
841
|
+
self.lookat = [x,y,z]
|
842
|
+
# self.eye = self._eye_pos()
|
766
843
|
|
767
844
|
@property
|
768
845
|
def selected(self):
|
@@ -772,6 +849,10 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
772
849
|
|
773
850
|
@property
|
774
851
|
def xyz_selected(self) -> np.ndarray:
|
852
|
+
""" Extract the selected points from the viewer.
|
853
|
+
|
854
|
+
Filter the selected points by codes if _select_only_codes is not empty."""
|
855
|
+
|
775
856
|
if self.viewer is None:
|
776
857
|
return None
|
777
858
|
|
@@ -799,6 +880,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
799
880
|
@property
|
800
881
|
def num_points(self):
|
801
882
|
""" Number of points """
|
883
|
+
|
802
884
|
nb1 = self.data.shape[0]
|
803
885
|
if self.viewer is not None:
|
804
886
|
nb2 = self.viewer.get('num_points')[0]
|
@@ -808,12 +890,12 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
808
890
|
|
809
891
|
@property
|
810
892
|
def nb_points(self):
|
811
|
-
""" Number of points """
|
893
|
+
""" Number of points - alias of num_points """
|
812
894
|
return self.num_points
|
813
895
|
|
814
896
|
@property
|
815
897
|
def nb(self):
|
816
|
-
""" Number of points """
|
898
|
+
""" Number of points - alias of num_points """
|
817
899
|
return self.num_points
|
818
900
|
|
819
901
|
@property
|
@@ -823,36 +905,46 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
823
905
|
|
824
906
|
@property
|
825
907
|
def mvp(self):
|
908
|
+
""" Model View Projection matrix
|
909
|
+
|
910
|
+
See https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93projection_matrix for details
|
911
|
+
"""
|
912
|
+
|
826
913
|
if self.viewer is None:
|
827
914
|
return None
|
828
915
|
return self.viewer.get('mvp')
|
829
916
|
|
830
917
|
@property
|
831
918
|
def eye(self):
|
919
|
+
""" eye/camera position """
|
832
920
|
if self.viewer is None:
|
833
921
|
return None
|
834
922
|
return self.viewer.get('eye')
|
835
923
|
|
836
924
|
@property
|
837
925
|
def lookat(self):
|
926
|
+
""" Lookat position """
|
838
927
|
if self.viewer is None:
|
839
928
|
return None
|
840
929
|
return self.viewer.get('lookat')
|
841
930
|
|
842
931
|
@property
|
843
932
|
def phi(self):
|
933
|
+
""" Azimuth angle (radians) """
|
844
934
|
if self.viewer is None:
|
845
935
|
return None
|
846
936
|
return self.viewer.get('phi')[0]
|
847
937
|
|
848
938
|
@property
|
849
939
|
def theta(self):
|
940
|
+
""" Elevation angle (radians) """
|
850
941
|
if self.viewer is None:
|
851
942
|
return None
|
852
943
|
return self.viewer.get('theta')[0]
|
853
944
|
|
854
945
|
@property
|
855
946
|
def r(self):
|
947
|
+
""" Distance from lookat """
|
856
948
|
if self.viewer is None:
|
857
949
|
return None
|
858
950
|
return self.viewer.get('r')[0]
|
@@ -862,24 +954,48 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
862
954
|
if self.viewer is None:
|
863
955
|
return None
|
864
956
|
self.viewer.set(phi=value)
|
957
|
+
self._fill_props()
|
865
958
|
|
866
959
|
@theta.setter
|
867
960
|
def theta(self, value):
|
868
961
|
if self.viewer is None:
|
869
962
|
return None
|
870
963
|
self.viewer.set(theta=value)
|
964
|
+
self._fill_props()
|
871
965
|
|
872
966
|
@r.setter
|
873
967
|
def r(self, value):
|
874
968
|
if self.viewer is None:
|
875
969
|
return None
|
876
970
|
self.viewer.set(r=value)
|
971
|
+
self._fill_props()
|
877
972
|
|
878
973
|
@lookat.setter
|
879
974
|
def lookat(self, value):
|
880
975
|
if self.viewer is None:
|
881
976
|
return None
|
882
977
|
self.viewer.set(lookat=value)
|
978
|
+
# self.viewer.set(lookat=value, phi=self.phi, theta=self.theta, r=self.r)
|
979
|
+
self._fill_props()
|
980
|
+
|
981
|
+
def _eye_pos(self):
|
982
|
+
""" Compute eye pos from lookat and r, phi, theta.
|
983
|
+
|
984
|
+
phi is the azimuth angle (radians)
|
985
|
+
theta is the elevation angle (radians)
|
986
|
+
r is the distance from lookat
|
987
|
+
"""
|
988
|
+
|
989
|
+
lx, ly, lz = self.lookat
|
990
|
+
r = self.r
|
991
|
+
phi = self.phi
|
992
|
+
theta = self.theta
|
993
|
+
|
994
|
+
x = lx + r*np.sin(phi)*np.cos(theta)
|
995
|
+
y = ly + r*np.cos(phi)*np.cos(theta)
|
996
|
+
z = lz + r*np.sin(theta)
|
997
|
+
|
998
|
+
return [x,y,z]
|
883
999
|
|
884
1000
|
@eye.setter
|
885
1001
|
def eye(self, value):
|
@@ -892,11 +1008,13 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
892
1008
|
# Compute phi, theta, r based on eye and lookat and right vector
|
893
1009
|
r = np.sqrt((x-lx)**2 + (y-ly)**2 + (z-lz)**2)
|
894
1010
|
self.r =r
|
895
|
-
self.
|
896
|
-
self.
|
1011
|
+
self.theta = np.arcsin((z-lz)/r)
|
1012
|
+
self.phi = -np.arctan2((x-lx),(y-ly))
|
1013
|
+
self._fill_props()
|
897
1014
|
|
898
1015
|
@property
|
899
1016
|
def point_size(self):
|
1017
|
+
""" Point size in viewer -- user units """
|
900
1018
|
return self._point_size
|
901
1019
|
|
902
1020
|
@point_size.setter
|
@@ -904,6 +1022,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
904
1022
|
self._point_size = value
|
905
1023
|
if self.viewer is not None:
|
906
1024
|
self.viewer.set(point_size=value)
|
1025
|
+
self._fill_props()
|
907
1026
|
|
908
1027
|
@property
|
909
1028
|
def xyz(self):
|
@@ -914,17 +1033,18 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
914
1033
|
return self.data[:,3]
|
915
1034
|
|
916
1035
|
def codes_unique(self):
|
1036
|
+
""" Only unique codes """
|
917
1037
|
return list(np.unique(self.codes).astype(int))
|
918
1038
|
|
919
1039
|
def create_viewer(self, color_code:Colors_Lazviewer = None, classification:Classification_LAZ = None):
|
920
1040
|
""" Create a viewer for las data """
|
921
1041
|
|
922
|
-
if color_code is not None:
|
923
|
-
self.associated_color = color_code
|
924
|
-
|
925
1042
|
if classification is not None:
|
926
1043
|
self.classification = classification
|
927
1044
|
|
1045
|
+
if color_code is not None:
|
1046
|
+
self.associated_color = color_code
|
1047
|
+
|
928
1048
|
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
929
1049
|
|
930
1050
|
self.viewer = viewer(self.xyz, self._colors)
|
@@ -933,6 +1053,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
933
1053
|
return self.viewer
|
934
1054
|
|
935
1055
|
def interactive_update_colors(self):
|
1056
|
+
""" Create a frame to interactively update colors """
|
936
1057
|
|
937
1058
|
self.classification.interactive_update_colors()
|
938
1059
|
|
@@ -945,12 +1066,17 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
945
1066
|
self.classification._choose_colors.callback = new_callback_colors
|
946
1067
|
|
947
1068
|
def set_colors(self):
|
1069
|
+
""" Set colors in viewer --> using attributes method (not colormap) """
|
1070
|
+
|
948
1071
|
if self.viewer is not None:
|
949
1072
|
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
950
1073
|
self.viewer.attributes(self._colors)
|
951
1074
|
|
952
1075
|
def set_classification(self, classification:str = None):
|
1076
|
+
""" Set classification of LAZ data
|
953
1077
|
|
1078
|
+
TODO : Check if 2020-2022 SPW campaign is the same classification as 2013
|
1079
|
+
"""
|
954
1080
|
if classification is None:
|
955
1081
|
logging.warning(_('No classification chosen - Abort !'))
|
956
1082
|
elif classification == 'SPW 2013-2014':
|
@@ -959,6 +1085,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
959
1085
|
self.classification.init_2023()
|
960
1086
|
|
961
1087
|
def find_minmax(self, update=False):
|
1088
|
+
""" Find min and max of data """
|
962
1089
|
if self.data is not None:
|
963
1090
|
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
964
1091
|
|
@@ -996,6 +1123,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
996
1123
|
|
997
1124
|
@property
|
998
1125
|
def data(self):
|
1126
|
+
""" Full data array (x,y,z,code) """
|
999
1127
|
return self._data
|
1000
1128
|
|
1001
1129
|
@data.setter
|
@@ -1011,86 +1139,105 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1011
1139
|
self._bounds = value
|
1012
1140
|
|
1013
1141
|
def from_grid(self, grid:xyz_laz_grid, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]]):
|
1014
|
-
|
1142
|
+
""" Create data from grid LAZ """
|
1015
1143
|
self.bounds = bounds
|
1016
1144
|
self.data = grid.scan(bounds)
|
1017
1145
|
|
1018
1146
|
def from_file(self, fn:str):
|
1019
|
-
|
1147
|
+
""" Create data from LAZ file """
|
1020
1148
|
self.data = read_laz(fn)
|
1021
1149
|
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
1022
1150
|
|
1023
|
-
def
|
1151
|
+
def descimate(self, step:int):
|
1152
|
+
""" Descimate data.
|
1153
|
+
|
1154
|
+
Conserve only one point every 'step' points.
|
1155
|
+
|
1156
|
+
:param step: step of descimation
|
1157
|
+
"""
|
1024
1158
|
self.data = self.data[::step]
|
1025
1159
|
|
1026
1160
|
def get_data_class(self, key:int):
|
1161
|
+
""" Get data with a specific code """
|
1162
|
+
|
1163
|
+
assert isinstance(key, int), _('Key must be an integer')
|
1164
|
+
|
1027
1165
|
return self.data[self.data[:,3] == key]
|
1028
1166
|
|
1029
1167
|
def add_pose_in_memory(self, key_time:float = 1.):
|
1168
|
+
""" Add current pose in flight memory """
|
1169
|
+
|
1030
1170
|
if self.viewer is not None:
|
1031
1171
|
lookat = self.lookat
|
1032
1172
|
new_pose = (lookat[0], lookat[1], lookat[2], self.phi, self.theta, self.r)
|
1033
|
-
self.
|
1173
|
+
self._flight_memory.append((new_pose, key_time))
|
1034
1174
|
|
1035
1175
|
def play_flight(self, tlim=[-np.inf, np.inf], repeat=False, interp='cubic_natural'):
|
1036
|
-
|
1176
|
+
""" Play flight memory """
|
1037
1177
|
if self.viewer is not None:
|
1038
|
-
if len(self.
|
1039
|
-
poses = [cur[0] for cur in self.
|
1178
|
+
if len(self._flight_memory)>0:
|
1179
|
+
poses = [cur[0] for cur in self._flight_memory]
|
1040
1180
|
times = [0.]
|
1041
|
-
for i in range(1,len(self.
|
1042
|
-
times.append(times[-1]+self.
|
1181
|
+
for i in range(1,len(self._flight_memory)):
|
1182
|
+
times.append(times[-1]+self._flight_memory[i][1])
|
1043
1183
|
self.viewer.play(poses, times, tlim, repeat, interp)
|
1044
1184
|
|
1045
1185
|
def set_times(self, times:np.ndarray):
|
1046
|
-
|
1047
|
-
|
1186
|
+
""" Set times for flight memory """
|
1187
|
+
if len(self._flight_memory)>0:
|
1188
|
+
self._flight_memory = [(self._flight_memory[i][0], times[i]) for i in range(len(self._flight_memory))]
|
1048
1189
|
|
1049
1190
|
def set_times_increment(self, increment:float):
|
1050
|
-
if len(self.
|
1051
|
-
self.
|
1191
|
+
if len(self._flight_memory)>0:
|
1192
|
+
self._flight_memory = [(self._flight_memory[i][0], increment*i) for i in range(len(self._flight_memory))]
|
1052
1193
|
|
1053
1194
|
def get_times(self):
|
1054
|
-
return np.asarray([cur[1] for cur in self.
|
1195
|
+
return np.asarray([cur[1] for cur in self._flight_memory])
|
1196
|
+
|
1197
|
+
def record_flight(self, dirout:str, tlim=[-np.inf, np.inf], interp='cubic_natural', fps=24, prefix:str = 'laz_', ext:str = 'png'):
|
1198
|
+
""" Record flight memory in multiple images
|
1055
1199
|
|
1056
|
-
|
1200
|
+
FIXME : FREEZE the app --> to debug
|
1201
|
+
"""
|
1057
1202
|
if self.viewer is not None:
|
1058
|
-
if len(self.
|
1059
|
-
poses = [cur[0] for cur in self.
|
1203
|
+
if len(self._flight_memory)>0:
|
1204
|
+
poses = [cur[0] for cur in self._flight_memory]
|
1060
1205
|
times = [0.]
|
1061
|
-
for i in range(1,len(self.
|
1062
|
-
times.append(times[-1]+self.
|
1206
|
+
for i in range(1,len(self._flight_memory)):
|
1207
|
+
times.append(times[-1]+self._flight_memory[i][1])
|
1063
1208
|
self.viewer.record(dirout, poses, times, tlim, interp, fps=fps, prefix=prefix, ext=ext)
|
1064
1209
|
|
1065
1210
|
def save_flight(self, fn:str):
|
1066
|
-
""" Write memory to file JSON """
|
1211
|
+
""" Write flight memory to file JSON """
|
1212
|
+
|
1067
1213
|
import json
|
1068
1214
|
|
1069
|
-
if len(self.
|
1215
|
+
if len(self._flight_memory)>0:
|
1070
1216
|
with open(fn,'w') as f:
|
1071
|
-
json.dump(self.
|
1217
|
+
json.dump(self._flight_memory, f, indent=2)
|
1072
1218
|
|
1073
1219
|
def load_flight(self, fn:str):
|
1074
|
-
""" Load memory from file JSON """
|
1220
|
+
""" Load flight memory from file JSON """
|
1075
1221
|
import json
|
1076
1222
|
|
1077
1223
|
if exists(fn):
|
1078
1224
|
with open(fn,'r') as f:
|
1079
|
-
self.
|
1225
|
+
self._flight_memory = json.load(f)
|
1080
1226
|
|
1081
1227
|
|
1082
1228
|
def _callback_props(self):
|
1083
1229
|
|
1084
|
-
self.
|
1230
|
+
self._update_viewer()
|
1085
1231
|
|
1086
1232
|
def _callback_destroy_props(self):
|
1087
1233
|
|
1088
1234
|
if self._myprops is not None:
|
1089
|
-
self._callback_props()
|
1235
|
+
# self._callback_props()
|
1090
1236
|
self._myprops.Destroy()
|
1091
1237
|
self._myprops = None
|
1092
1238
|
|
1093
1239
|
def _create_props(self):
|
1240
|
+
""" Create properties Wolf_Param for LAZ data """
|
1094
1241
|
|
1095
1242
|
if self._myprops is not None:
|
1096
1243
|
return
|
@@ -1134,22 +1281,123 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1134
1281
|
codes_sel += str(curcode) + ','
|
1135
1282
|
ret = props.addparam('Selection', 'Codes', codes_sel, Type_Param.String, 'Codes to select')
|
1136
1283
|
|
1137
|
-
# ret = props.addparam('Auto Update', 'Active', self._auto_update_properties, Type_Param.Logical, 'Auto update properties')
|
1138
|
-
# ret = props.addparam('Auto Update', 'Interval', self._auto_update_interval, Type_Param.Integer, 'Auto update interval (ms)')
|
1139
|
-
|
1140
1284
|
props.Populate()
|
1141
1285
|
|
1142
|
-
updatebutton = wx.Button(props, label=_('
|
1286
|
+
updatebutton = wx.Button(props, label=_('Get from viewer'))
|
1143
1287
|
props.sizerbut.Add(updatebutton,1,wx.EXPAND)
|
1144
|
-
|
1288
|
+
updatebutton.Bind(wx.EVT_BUTTON, self._fill_props)
|
1145
1289
|
|
1146
|
-
getselection = wx.Button(props, label=_('
|
1290
|
+
getselection = wx.Button(props, label=_('Edit selection'))
|
1147
1291
|
props.sizerbut.Add(getselection,1,wx.EXPAND)
|
1148
|
-
|
1292
|
+
getselection.Bind(wx.EVT_BUTTON, self._OnEdit_Selection)
|
1149
1293
|
|
1150
1294
|
props.Layout()
|
1151
1295
|
|
1152
|
-
def
|
1296
|
+
def _set_new_xls(self):
|
1297
|
+
""" Create a new Excel grid for selected data """
|
1298
|
+
|
1299
|
+
self._xlsFrame = wx.Frame(None, wx.ID_ANY, _('Selected points - ') + self.idx)
|
1300
|
+
self._xls = CpGrid(self._xlsFrame, wx.ID_ANY, style = wx.WANTS_CHARS)
|
1301
|
+
|
1302
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
1303
|
+
sizer.Add(self._xls, 1, wx.EXPAND)
|
1304
|
+
|
1305
|
+
nbclass = len(self.classification.classification)
|
1306
|
+
self._xls.CreateGrid(10, 4)
|
1307
|
+
|
1308
|
+
# Add a button to plot a histogram
|
1309
|
+
but_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
1310
|
+
plotbutton = wx.Button(self._xlsFrame, label=_('Plot histogram (All data)'))
|
1311
|
+
but_sizer.Add(plotbutton, 1, wx.EXPAND)
|
1312
|
+
plotbutton.Bind(wx.EVT_BUTTON, self.OnPlot_histogram)
|
1313
|
+
|
1314
|
+
plotbutton2 = wx.Button(self._xlsFrame, label=_('Plot histogram (Grid data)'))
|
1315
|
+
but_sizer.Add(plotbutton2, 1, wx.EXPAND)
|
1316
|
+
plotbutton2.Bind(wx.EVT_BUTTON, self.OnPlot_histogram_grid)
|
1317
|
+
|
1318
|
+
sizer.Add(but_sizer, 0, wx.EXPAND)
|
1319
|
+
|
1320
|
+
self._xlsFrame.SetSizer(sizer)
|
1321
|
+
self._xlsFrame.Layout()
|
1322
|
+
self._xlsFrame.Show()
|
1323
|
+
|
1324
|
+
icon = wx.Icon()
|
1325
|
+
icon_path = Path(__file__).parent.parent / "apps/wolf_logo2.bmp"
|
1326
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
1327
|
+
self._xlsFrame.SetIcon(icon)
|
1328
|
+
|
1329
|
+
def OnPlot_histogram(self, event:wx.MouseEvent):
|
1330
|
+
""" Plot histogram of selected data """
|
1331
|
+
self._plot_histogram()
|
1332
|
+
|
1333
|
+
def OnPlot_histogram_grid(self, event:wx.MouseEvent):
|
1334
|
+
""" Plot histogram of selected data """
|
1335
|
+
self._plot_histogram_grid()
|
1336
|
+
|
1337
|
+
def _plot_histogram_grid(self):
|
1338
|
+
""" Histogram ONLY of selected data in grid.
|
1339
|
+
|
1340
|
+
The data are extracted based on the first column of the grid
|
1341
|
+
untile an empty cell is found.
|
1342
|
+
"""
|
1343
|
+
|
1344
|
+
xls = self._xls
|
1345
|
+
|
1346
|
+
if xls is None:
|
1347
|
+
logging.warning(_('No Excel grid'))
|
1348
|
+
return
|
1349
|
+
|
1350
|
+
# Find not null cells
|
1351
|
+
nbrows = 1
|
1352
|
+
|
1353
|
+
while xls.GetCellValue(nbrows,0) != '' and nbrows < xls.NumberRows:
|
1354
|
+
nbrows += 1
|
1355
|
+
|
1356
|
+
if nbrows == 1:
|
1357
|
+
logging.warning(_('Nt enough points selected'))
|
1358
|
+
return
|
1359
|
+
|
1360
|
+
xyz = np.zeros((nbrows,3))
|
1361
|
+
codes = np.zeros(nbrows)
|
1362
|
+
|
1363
|
+
try:
|
1364
|
+
for i in range(nbrows):
|
1365
|
+
xyz[i,0] = float(xls.GetCellValue(i,0))
|
1366
|
+
xyz[i,1] = float(xls.GetCellValue(i,1))
|
1367
|
+
xyz[i,2] = float(xls.GetCellValue(i,2))
|
1368
|
+
codes[i] = int(xls.GetCellValue(i,3))
|
1369
|
+
except Exception as e:
|
1370
|
+
logging.error(e)
|
1371
|
+
logging.warning(_('Bad values in grid - Check your input'))
|
1372
|
+
return
|
1373
|
+
|
1374
|
+
fig = plt.figure()
|
1375
|
+
|
1376
|
+
ax = fig.add_subplot(111)
|
1377
|
+
|
1378
|
+
ax.hist(xyz[:,2], bins=256)
|
1379
|
+
|
1380
|
+
fig.show()
|
1381
|
+
|
1382
|
+
def _plot_histogram(self):
|
1383
|
+
""" """
|
1384
|
+
|
1385
|
+
xyz = self.xyz_selected
|
1386
|
+
|
1387
|
+
if xyz.shape[0]==0:
|
1388
|
+
logging.warning(_('No points selected'))
|
1389
|
+
return
|
1390
|
+
|
1391
|
+
fig = plt.figure()
|
1392
|
+
|
1393
|
+
ax = fig.add_subplot(111)
|
1394
|
+
|
1395
|
+
ax.hist(xyz[:,2], bins=256)
|
1396
|
+
|
1397
|
+
fig.show()
|
1398
|
+
|
1399
|
+
def _selection2vector(self):
|
1400
|
+
""" FIXME: must use RANSAC to compute a segment from the selected points """
|
1153
1401
|
|
1154
1402
|
if self.viewer is None:
|
1155
1403
|
logging.warning(_('No viewer'))
|
@@ -1161,24 +1409,40 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1161
1409
|
logging.warning(_('No points selected'))
|
1162
1410
|
return
|
1163
1411
|
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1412
|
+
vect = vector(name = self.idx + '_selection', fromnumpy=xyz)
|
1413
|
+
|
1414
|
+
return vect
|
1415
|
+
|
1167
1416
|
|
1168
|
-
|
1169
|
-
|
1417
|
+
def _OnEdit_Selection(self, event:wx.MouseEvent):
|
1418
|
+
""" Get selection from viewer and create a XLS grid """
|
1419
|
+
self._edit_selection()
|
1170
1420
|
|
1171
|
-
|
1172
|
-
self._xls.CreateGrid(xyz.shape[0] + nbclass +1, 4)
|
1421
|
+
def _edit_selection(self):
|
1173
1422
|
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1423
|
+
if self.viewer is None:
|
1424
|
+
logging.warning(_('No viewer'))
|
1425
|
+
return
|
1426
|
+
|
1427
|
+
xyz = self.xyz_selected
|
1177
1428
|
|
1429
|
+
if xyz.shape[0]==0:
|
1430
|
+
logging.warning(_('No points selected'))
|
1431
|
+
return
|
1432
|
+
|
1433
|
+
if self._xls is None:
|
1434
|
+
self._set_new_xls()
|
1178
1435
|
else:
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1436
|
+
try:
|
1437
|
+
self._xls.ClearGrid()
|
1438
|
+
except:
|
1439
|
+
#Useful if the grid is already destroyed
|
1440
|
+
self._set_new_xls()
|
1441
|
+
|
1442
|
+
nbclass = len(self.classification.classification)
|
1443
|
+
min_rows = xyz.shape[0] + nbclass +1
|
1444
|
+
if self._xls.NumberRows < min_rows:
|
1445
|
+
self._xls.AppendRows(min_rows - self._xls.NumberRows)
|
1182
1446
|
|
1183
1447
|
self._xls.SetColLabelValue(0, 'X')
|
1184
1448
|
self._xls.SetColLabelValue(1, 'Y')
|
@@ -1206,7 +1470,8 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1206
1470
|
self._xlsFrame.Raise()
|
1207
1471
|
self._xlsFrame.Center()
|
1208
1472
|
|
1209
|
-
def
|
1473
|
+
def _update_viewer(self):
|
1474
|
+
""" Update the viewer with properties """
|
1210
1475
|
|
1211
1476
|
if self._myprops is None:
|
1212
1477
|
return
|
@@ -1245,17 +1510,8 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1245
1510
|
logging.warning(_('Nullify selection filter - Check your input'))
|
1246
1511
|
self._select_only_codes = []
|
1247
1512
|
|
1248
|
-
# self._auto_update_interval = props[('Auto Update', 'Interval')]
|
1249
|
-
# self._auto_update_properties = props[('Auto Update', 'Active')]
|
1250
|
-
|
1251
|
-
# if self._auto_update_properties:
|
1252
|
-
# self.SetTimer(self._auto_update_interval)
|
1253
|
-
# else:
|
1254
|
-
# if self._timer is not None:
|
1255
|
-
# self._timer.Stop()
|
1256
|
-
|
1257
1513
|
def _fill_props(self, full:bool = False):
|
1258
|
-
|
1514
|
+
""" Fill properties from attributes """
|
1259
1515
|
if self._myprops is None:
|
1260
1516
|
return
|
1261
1517
|
|
@@ -1288,31 +1544,10 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1288
1544
|
|
1289
1545
|
props[('Points', 'Size')] = self._point_size
|
1290
1546
|
|
1291
|
-
# props[('Auto Update', 'Interval')] = self._auto_update_interval
|
1292
|
-
# props[('Auto Update', 'Active')] = self._auto_update_properties
|
1293
|
-
|
1294
1547
|
props.Populate()
|
1295
1548
|
|
1296
|
-
# def _fill_handler(self, event):
|
1297
|
-
|
1298
|
-
# if self.viewer is None:
|
1299
|
-
# return
|
1300
|
-
|
1301
|
-
# self._fill_props(full = False)
|
1302
|
-
|
1303
|
-
# def SetTimer(self, interval:int):
|
1304
|
-
|
1305
|
-
# if self.viewer is None:
|
1306
|
-
# return
|
1307
|
-
|
1308
|
-
# if self._timer is None:
|
1309
|
-
# self._timer = wx.Timer(self._myprops)
|
1310
|
-
# self._myprops.Bind(wx.EVT_TIMER, self._fill_handler, self._timer)
|
1311
|
-
|
1312
|
-
# self._timer.Start(interval)
|
1313
|
-
|
1314
1549
|
def show_properties(self):
|
1315
|
-
|
1550
|
+
""" Surcharged method (see Element_To_Draw) to show properties from MapViewer"""
|
1316
1551
|
if self.viewer is None:
|
1317
1552
|
return
|
1318
1553
|
|