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.
@@ -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 mplfig
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 = mplfig()
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 Base_LAZ_Data(Element_To_Draw):
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.lazfile:Path = None
698
- self._data:np.ndarray = None
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
- self.associated_color:int = Colors_Lazviewer.CODE_2023.value
760
+ def saveas(self, fn:str):
761
+ """ Save class : data and attributes """
762
+ import pickle
702
763
 
703
- self.viewer:viewer = None
704
- self._colors:np.ndarray = None
764
+ self._filename = Path(fn)
705
765
 
706
- self._point_size = .05
766
+ with open(fn,'wb') as f:
767
+ pickle.dump(self.serialize(),f)
707
768
 
708
- self._memory = []
769
+ # save data by numpy
770
+ np.savez(str(fn) + '.npz', data=self._data)
709
771
 
710
- self._myprops = None
772
+ def load(self, fn:str):
773
+ """ Load class : data and attributes """
774
+ import pickle
711
775
 
712
- self._bg_color = [0,0,0,255]
713
- self._bg_color_top = [0,0,0,255]
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
- self._floor_level = 0.
717
- self._floor_color = [0,0,0,255]
779
+ @property
780
+ def associated_color(self):
781
+ return self._associated_color
718
782
 
719
- self._show_grid = True
720
- self._show_axis = True
721
- self._show_info = True
783
+ @associated_color.setter
784
+ def associated_color(self, value:int):
785
+ self._associated_color = value
786
+ self.set_colors()
722
787
 
723
- self._select_only_codes = []
788
+ def merge(self, other:"Wolf_LAZ_Data"):
789
+ """ Merge two Wolf_LAZ_Data objects """
724
790
 
725
- self._xls:CpGrid = None
726
- self._xlsFrame:wx.Frame = None
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
- # self._auto_update_properties = False
729
- # self._auto_update_interval = 100 # ms
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.phi = np.arccos((z-lz)/r)
896
- self.theta = np.arctan2((y-ly),(x-lx))
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 decimate(self, step:int):
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._memory.append((new_pose, key_time))
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._memory)>0:
1039
- poses = [cur[0] for cur in self._memory]
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._memory)):
1042
- times.append(times[-1]+self._memory[i][1])
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
- if len(self._memory)>0:
1047
- self._memory = [(self._memory[i][0], times[i]) for i in range(len(self._memory))]
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._memory)>0:
1051
- self._memory = [(self._memory[i][0], increment*i) for i in range(len(self._memory))]
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._memory])
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
- def record_flight(self, dirout:str, tlim=[-np.inf, np.inf], interp='cubic_natural', fps=24, prefix:str = 'laz_', ext:str = '.png'):
1200
+ FIXME : FREEZE the app --> to debug
1201
+ """
1057
1202
  if self.viewer is not None:
1058
- if len(self._memory)>0:
1059
- poses = [cur[0] for cur in self._memory]
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._memory)):
1062
- times.append(times[-1]+self._memory[i][1])
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._memory)>0:
1215
+ if len(self._flight_memory)>0:
1070
1216
  with open(fn,'w') as f:
1071
- json.dump(self._memory, f, indent=2)
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._memory = json.load(f)
1225
+ self._flight_memory = json.load(f)
1080
1226
 
1081
1227
 
1082
1228
  def _callback_props(self):
1083
1229
 
1084
- self._update_props()
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=_('Update from viewer'))
1286
+ updatebutton = wx.Button(props, label=_('Get from viewer'))
1143
1287
  props.sizerbut.Add(updatebutton,1,wx.EXPAND)
1144
- props.Bind(wx.EVT_BUTTON, self._fill_props, updatebutton)
1288
+ updatebutton.Bind(wx.EVT_BUTTON, self._fill_props)
1145
1289
 
1146
- getselection = wx.Button(props, label=_('Get selection'))
1290
+ getselection = wx.Button(props, label=_('Edit selection'))
1147
1291
  props.sizerbut.Add(getselection,1,wx.EXPAND)
1148
- props.Bind(wx.EVT_BUTTON, self._get_selection, getselection)
1292
+ getselection.Bind(wx.EVT_BUTTON, self._OnEdit_Selection)
1149
1293
 
1150
1294
  props.Layout()
1151
1295
 
1152
- def _get_selection(self, event):
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
- if self._xls is None:
1165
- self._xlsFrame = wx.Frame(None, wx.ID_ANY, _('Selected points - ') + self.idx)
1166
- self._xls = CpGrid(self._xlsFrame, wx.ID_ANY, style = wx.WANTS_CHARS)
1412
+ vect = vector(name = self.idx + '_selection', fromnumpy=xyz)
1413
+
1414
+ return vect
1415
+
1167
1416
 
1168
- sizer = wx.BoxSizer(wx.VERTICAL)
1169
- sizer.Add(self._xls, 1, wx.EXPAND)
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
- nbclass = len(self.classification.classification)
1172
- self._xls.CreateGrid(xyz.shape[0] + nbclass +1, 4)
1421
+ def _edit_selection(self):
1173
1422
 
1174
- self._xlsFrame.SetSizer(sizer)
1175
- self._xlsFrame.Layout()
1176
- self._xlsFrame.Show()
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
- self._xls.ClearGrid()
1180
- if self._xls.NumberRows < xyz.shape[0]:
1181
- self._xls.AppendRows(xyz.shape[0]-self._xls.NumberRows)
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 _update_props(self):
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