wolfhece 2.1.118__py3-none-any.whl → 2.1.120__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 +893 -177
- wolfhece/PyVertexvectors.py +304 -0
- wolfhece/apps/check_install.py +39 -0
- wolfhece/apps/version.py +1 -1
- wolfhece/lazviewer/_add_path.py +17 -17
- wolfhece/lazviewer/laz_viewer.py +422 -128
- wolfhece/lazviewer/libs/qt_plugins/platforms/qwindows.dll +0 -0
- wolfhece/lazviewer/viewer/qt.conf +2 -0
- wolfhece/lazviewer/viewer/viewer.py +28 -8
- wolfhece/matplotlib_fig.py +83 -1
- wolfhece/opengl/py3d.py +10 -4
- wolfhece/wolfresults_2D.py +484 -21
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.120.dist-info}/METADATA +1 -1
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.120.dist-info}/RECORD +17 -15
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.120.dist-info}/WHEEL +0 -0
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.120.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.118.dist-info → wolfhece-2.1.120.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, PRESET_LAYOUTS
|
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
|
@@ -374,6 +375,35 @@ class xyz_laz_grid():
|
|
374
375
|
|
375
376
|
return retdata
|
376
377
|
|
378
|
+
def find_files_in_bounds(self, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]]):
|
379
|
+
""" Find all files in bounds """
|
380
|
+
|
381
|
+
x1=bounds[0][0]
|
382
|
+
x2=bounds[0][1]
|
383
|
+
y1=bounds[1][0]
|
384
|
+
y2=bounds[1][1]
|
385
|
+
|
386
|
+
file1= self.genfile.split('x1')[0]
|
387
|
+
file2= self.genfile.split('y1')[-1]
|
388
|
+
|
389
|
+
files=[]
|
390
|
+
|
391
|
+
for x in range(int(self.origx), int(self.endx), int(self.dx)):
|
392
|
+
for y in range(int(self.origy), int(self.endy), int(self.dy)):
|
393
|
+
|
394
|
+
locbounds=np.float64(((x,x+self.dx),(y,y+self.dy)))
|
395
|
+
|
396
|
+
test = not(x2 < locbounds[0][0] or x1 > locbounds[0][1] or y2 < locbounds[1][0] or y1 > locbounds[1][1])
|
397
|
+
|
398
|
+
if test:
|
399
|
+
fxyz = file1+str(int(x))+'_'+str(int(y))+file2
|
400
|
+
|
401
|
+
fn = join(self.mydir,fxyz)
|
402
|
+
if exists(fn):
|
403
|
+
files.append(fn)
|
404
|
+
|
405
|
+
return files
|
406
|
+
|
377
407
|
def _split_xyz(self, dirout:str, nbparts:int = 10):
|
378
408
|
""" Split XYZ file into 'nb' parts along X and Y """
|
379
409
|
for entry in scandir(self.mydir):
|
@@ -493,7 +523,7 @@ class xyz_laz_grids():
|
|
493
523
|
""" Ensemble de grids """
|
494
524
|
|
495
525
|
def __init__(self, dir_grids:str, create:bool=False) -> None:
|
496
|
-
self.grids = []
|
526
|
+
self.grids:list[xyz_laz_grid] = []
|
497
527
|
self.colors = Classification_LAZ()
|
498
528
|
self.colors.init_2023()
|
499
529
|
|
@@ -526,6 +556,36 @@ class xyz_laz_grids():
|
|
526
556
|
logging.info(_('Data found -- {} points'.format(ret.shape[0])))
|
527
557
|
return ret
|
528
558
|
|
559
|
+
def find_files_in_bounds(self, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]]):
|
560
|
+
|
561
|
+
ret = [(cur.mydir, cur.find_files_in_bounds(bounds)) for cur in self.grids]
|
562
|
+
ret = [cur for cur in ret if len(cur[1])>0]
|
563
|
+
|
564
|
+
if len(ret)==0:
|
565
|
+
logging.info(_('No data found'))
|
566
|
+
return []
|
567
|
+
else:
|
568
|
+
logging.info(_('Data found -- {} files'.format(len(ret))))
|
569
|
+
return ret
|
570
|
+
|
571
|
+
def copy_files_in_bounds(self, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]], dirout:str):
|
572
|
+
|
573
|
+
import shutil
|
574
|
+
|
575
|
+
out = Path(dirout)
|
576
|
+
out.mkdir(exist_ok=True)
|
577
|
+
|
578
|
+
files = self.find_files_in_bounds(bounds)
|
579
|
+
|
580
|
+
for curdir, curfiles in files:
|
581
|
+
locdir = dirout / Path(curdir).parent.name
|
582
|
+
locdir.mkdir(exist_ok=True)
|
583
|
+
for curfile in curfiles:
|
584
|
+
shutil.copy(curfile, locdir / Path(curfile).name)
|
585
|
+
|
586
|
+
# copy gridinfo.txt
|
587
|
+
shutil.copy(join(curdir,'gridinfo.txt'), locdir / 'gridinfo.txt')
|
588
|
+
|
529
589
|
def read_dir(self, dir_grids):
|
530
590
|
dirs = listdir(dir_grids)
|
531
591
|
|
@@ -636,14 +696,11 @@ class xyz_laz_grids():
|
|
636
696
|
|
637
697
|
(up_s, up_z, up_color), (down_s, down_z, down_color) = self.scan_around(xy, length_buffer)
|
638
698
|
|
639
|
-
figmpl =
|
640
|
-
figmpl.presets()
|
641
|
-
fig = figmpl.fig
|
642
|
-
ax = figmpl.cur_ax
|
699
|
+
figmpl = MplFig(PRESET_LAYOUTS.DEFAULT)
|
643
700
|
|
644
701
|
logging.info(_('Plotting'))
|
645
|
-
|
646
|
-
|
702
|
+
figmpl.plot(up_s, up_z, c=up_color ,marker='.')
|
703
|
+
figmpl.plot(down_s, down_z,c=down_color,marker='+')
|
647
704
|
|
648
705
|
if show:
|
649
706
|
figmpl.Show()
|
@@ -687,49 +744,116 @@ class xyz_laz_grids():
|
|
687
744
|
force_format=force_format))
|
688
745
|
|
689
746
|
|
690
|
-
class
|
747
|
+
class Wolf_LAZ_Data(Element_To_Draw):
|
748
|
+
""" Base class for LAZ data which can be imported in Pydraw.Mapviewer.
|
749
|
+
"""
|
691
750
|
|
692
751
|
def __init__(self, idx:str = '', plotted:bool = False, mapviewer = None, need_for_wx:bool = False) -> None:
|
693
752
|
|
753
|
+
self._filename:Path = Path('') # filename TODO : improve serialization
|
754
|
+
self._lazfile:Path = None # Not used
|
755
|
+
|
756
|
+
# Bounds must be set before calling super().__init__() because
|
757
|
+
# xmin, xmax, ymin, ymax properties are used and depend on _bounds
|
694
758
|
self._bounds = [[0.,0.],[0.,0.]] # [[xmin,xmax],[ymin,ymax]]
|
759
|
+
|
695
760
|
super().__init__(idx, plotted, mapviewer, need_for_wx)
|
696
761
|
|
697
|
-
self.
|
698
|
-
self.
|
699
|
-
self.classification = Classification_LAZ()
|
762
|
+
self._data:np.ndarray = None # Numpy data array -- to be plotted
|
763
|
+
self._colors:np.ndarray = None # NumPy array of colors for each point --> see viewer attributes for details
|
764
|
+
self.classification = Classification_LAZ() # Classification of LAZ data --> defining colors if codification is used
|
700
765
|
|
701
|
-
self.
|
766
|
+
self._associated_color:int = Colors_Lazviewer.CODE_2023.value # Associated color type for LAZ data
|
702
767
|
|
703
|
-
self.viewer:viewer = None
|
704
|
-
self._colors:np.ndarray = None
|
768
|
+
self.viewer:viewer = None # PPTK viewer
|
705
769
|
|
706
|
-
self._point_size = .05
|
770
|
+
self._point_size = .05 # Point size in viewer -- in user units
|
707
771
|
|
708
|
-
self.
|
772
|
+
self._flight_memory = [] # Flight position
|
709
773
|
|
710
|
-
self._myprops = None
|
774
|
+
self._myprops = None # Properties based on Wolf_Param class
|
711
775
|
|
712
|
-
self._bg_color = [0,0,
|
713
|
-
self._bg_color_top = [0,0,0,255]
|
714
|
-
self._bg_color_bottom = [0,0,
|
776
|
+
self._bg_color = [int(0.23*255), int(0.23*255), int(.44*255), 255] # Background color
|
777
|
+
self._bg_color_top = [0, 0, 0, 255] # Top background color
|
778
|
+
self._bg_color_bottom = [int(0.23*255), int(0.23*255), int(.44*255), 255] # Bottom background color
|
715
779
|
|
716
|
-
self._floor_level = 0.
|
717
|
-
self._floor_color = [0,0,
|
780
|
+
self._floor_level = 0. # Floor level -- user units
|
781
|
+
self._floor_color = [int(0.3*255), int(0.3*255), int(.3*255), 127] # Floor color
|
718
782
|
|
719
|
-
self._show_grid = True
|
720
|
-
self._show_axis = True
|
721
|
-
self._show_info = True
|
783
|
+
self._show_grid = True # Show grid in viewer
|
784
|
+
self._show_axis = True # Show axis in viewer
|
785
|
+
self._show_info = True # Show info in viewer
|
722
786
|
|
723
|
-
self._select_only_codes = []
|
787
|
+
self._select_only_codes = [] # Codes to be selected -- Defined by user
|
724
788
|
|
725
|
-
self._xls:CpGrid = None
|
726
|
-
self._xlsFrame:wx.Frame = None
|
789
|
+
self._xls:CpGrid = None # Excel grid for selected data
|
790
|
+
self._xlsFrame:wx.Frame = None # Frame containing the xls grid
|
727
791
|
|
728
|
-
|
729
|
-
|
730
|
-
|
792
|
+
def serialize(self):
|
793
|
+
""" Serialize class : data and attributes """
|
794
|
+
return {'bounds':self._bounds, 'data':str(self._filename) + '.npz', 'associated_color':self._associated_color,
|
795
|
+
'point_size':self._point_size, 'bg_color':self._bg_color, 'bg_color_top':self._bg_color_top,
|
796
|
+
'bg_color_bottom':self._bg_color_bottom, 'floor_level':self._floor_level, 'floor_color':self._floor_color,
|
797
|
+
'show_grid':self._show_grid, 'show_axis':self._show_axis, 'show_info':self._show_info}
|
798
|
+
|
799
|
+
def deserialize(self, data:dict):
|
800
|
+
""" Deserialize class : data and attributes """
|
801
|
+
self._bounds = data['bounds']
|
802
|
+
self._filename = Path(data['data'])
|
803
|
+
self._associated_color = data['associated_color']
|
804
|
+
self._point_size = data['point_size']
|
805
|
+
self._bg_color = data['bg_color']
|
806
|
+
self._bg_color_top = data['bg_color_top']
|
807
|
+
self._bg_color_bottom = data['bg_color_bottom']
|
808
|
+
self._floor_level = data['floor_level']
|
809
|
+
self._floor_color = data['floor_color']
|
810
|
+
self._show_grid = data['show_grid']
|
811
|
+
self._show_axis = data['show_axis']
|
812
|
+
self._show_info = data['show_info']
|
813
|
+
|
814
|
+
self.data = np.load(self._filename)['data']
|
815
|
+
|
816
|
+
def saveas(self, fn:str):
|
817
|
+
""" Save class : data and attributes """
|
818
|
+
import pickle
|
819
|
+
|
820
|
+
self._filename = Path(fn)
|
821
|
+
|
822
|
+
with open(fn,'wb') as f:
|
823
|
+
pickle.dump(self.serialize(),f)
|
824
|
+
|
825
|
+
# save data by numpy
|
826
|
+
np.savez(str(fn) + '.npz', data=self._data)
|
827
|
+
|
828
|
+
def load(self, fn:str):
|
829
|
+
""" Load class : data and attributes """
|
830
|
+
import pickle
|
831
|
+
|
832
|
+
with open(fn,'rb') as f:
|
833
|
+
self.deserialize(pickle.load(f))
|
834
|
+
|
835
|
+
@property
|
836
|
+
def associated_color(self):
|
837
|
+
return self._associated_color
|
838
|
+
|
839
|
+
@associated_color.setter
|
840
|
+
def associated_color(self, value:int):
|
841
|
+
self._associated_color = value
|
842
|
+
self.set_colors()
|
843
|
+
|
844
|
+
def merge(self, other:"Wolf_LAZ_Data"):
|
845
|
+
""" Merge two Wolf_LAZ_Data objects """
|
846
|
+
|
847
|
+
if self._data is None:
|
848
|
+
self._data = other._data
|
849
|
+
else:
|
850
|
+
self._data = np.concatenate((self._data, other._data))
|
851
|
+
|
852
|
+
self.bounds = [[min(self.bounds[0][0],other.bounds[0][0]),max(self.bounds[0][1],other.bounds[0][1])],
|
853
|
+
[min(self.bounds[1][0],other.bounds[1][0]),max(self.bounds[1][1],other.bounds[1][1])]]
|
731
854
|
|
732
855
|
def filter_data(self, codes:list[int]):
|
856
|
+
""" Filter data by codes """
|
733
857
|
self._data = self._data[np.isin(self._data[:,3],codes)]
|
734
858
|
|
735
859
|
def bg_color(self, value):
|
@@ -746,7 +870,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
746
870
|
|
747
871
|
def floor_level(self, value):
|
748
872
|
if self.viewer is not None:
|
749
|
-
return self.viewer.set(floor_level = value)
|
873
|
+
return self.viewer.set(floor_level = float(value))
|
750
874
|
|
751
875
|
def floor_color(self, value):
|
752
876
|
if self.viewer is not None:
|
@@ -754,15 +878,24 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
754
878
|
|
755
879
|
def show_grid(self, value:bool):
|
756
880
|
if self.viewer is not None:
|
757
|
-
return self.viewer.set(show_grid = value)
|
881
|
+
return self.viewer.set(show_grid = bool(value))
|
758
882
|
|
759
883
|
def show_axis(self, value:bool):
|
760
884
|
if self.viewer is not None:
|
761
|
-
return self.viewer.set(show_axis = value)
|
885
|
+
return self.viewer.set(show_axis = bool(value))
|
762
886
|
|
763
887
|
def show_info(self, value:bool):
|
764
888
|
if self.viewer is not None:
|
765
|
-
return self.viewer.set(show_info = value)
|
889
|
+
return self.viewer.set(show_info = bool(value))
|
890
|
+
|
891
|
+
def force_view(self, x, y, z = -1):
|
892
|
+
""" Force lookat position """
|
893
|
+
if z == -1:
|
894
|
+
curx,cury,curz = self.lookat
|
895
|
+
self.lookat = [x,y,curz]
|
896
|
+
else:
|
897
|
+
self.lookat = [x,y,z]
|
898
|
+
# self.eye = self._eye_pos()
|
766
899
|
|
767
900
|
@property
|
768
901
|
def selected(self):
|
@@ -772,6 +905,10 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
772
905
|
|
773
906
|
@property
|
774
907
|
def xyz_selected(self) -> np.ndarray:
|
908
|
+
""" Extract the selected points from the viewer.
|
909
|
+
|
910
|
+
Filter the selected points by codes if _select_only_codes is not empty."""
|
911
|
+
|
775
912
|
if self.viewer is None:
|
776
913
|
return None
|
777
914
|
|
@@ -799,21 +936,25 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
799
936
|
@property
|
800
937
|
def num_points(self):
|
801
938
|
""" Number of points """
|
939
|
+
|
802
940
|
nb1 = self.data.shape[0]
|
803
941
|
if self.viewer is not None:
|
804
|
-
|
805
|
-
|
806
|
-
|
942
|
+
try:
|
943
|
+
nb2 = self.viewer.get('num_points')[0]
|
944
|
+
assert nb1 == nb2, _('Incoherent number of points')
|
945
|
+
except:
|
946
|
+
# viewer is not initialized or Destroyed
|
947
|
+
self.viewer = None
|
807
948
|
return nb1
|
808
949
|
|
809
950
|
@property
|
810
951
|
def nb_points(self):
|
811
|
-
""" Number of points """
|
952
|
+
""" Number of points - alias of num_points """
|
812
953
|
return self.num_points
|
813
954
|
|
814
955
|
@property
|
815
956
|
def nb(self):
|
816
|
-
""" Number of points """
|
957
|
+
""" Number of points - alias of num_points """
|
817
958
|
return self.num_points
|
818
959
|
|
819
960
|
@property
|
@@ -823,36 +964,46 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
823
964
|
|
824
965
|
@property
|
825
966
|
def mvp(self):
|
967
|
+
""" Model View Projection matrix
|
968
|
+
|
969
|
+
See https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93projection_matrix for details
|
970
|
+
"""
|
971
|
+
|
826
972
|
if self.viewer is None:
|
827
973
|
return None
|
828
974
|
return self.viewer.get('mvp')
|
829
975
|
|
830
976
|
@property
|
831
977
|
def eye(self):
|
978
|
+
""" eye/camera position """
|
832
979
|
if self.viewer is None:
|
833
980
|
return None
|
834
981
|
return self.viewer.get('eye')
|
835
982
|
|
836
983
|
@property
|
837
984
|
def lookat(self):
|
985
|
+
""" Lookat position """
|
838
986
|
if self.viewer is None:
|
839
987
|
return None
|
840
988
|
return self.viewer.get('lookat')
|
841
989
|
|
842
990
|
@property
|
843
991
|
def phi(self):
|
992
|
+
""" Azimuth angle (radians) """
|
844
993
|
if self.viewer is None:
|
845
994
|
return None
|
846
995
|
return self.viewer.get('phi')[0]
|
847
996
|
|
848
997
|
@property
|
849
998
|
def theta(self):
|
999
|
+
""" Elevation angle (radians) """
|
850
1000
|
if self.viewer is None:
|
851
1001
|
return None
|
852
1002
|
return self.viewer.get('theta')[0]
|
853
1003
|
|
854
1004
|
@property
|
855
1005
|
def r(self):
|
1006
|
+
""" Distance from lookat """
|
856
1007
|
if self.viewer is None:
|
857
1008
|
return None
|
858
1009
|
return self.viewer.get('r')[0]
|
@@ -862,24 +1013,48 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
862
1013
|
if self.viewer is None:
|
863
1014
|
return None
|
864
1015
|
self.viewer.set(phi=value)
|
1016
|
+
self._fill_props()
|
865
1017
|
|
866
1018
|
@theta.setter
|
867
1019
|
def theta(self, value):
|
868
1020
|
if self.viewer is None:
|
869
1021
|
return None
|
870
1022
|
self.viewer.set(theta=value)
|
1023
|
+
self._fill_props()
|
871
1024
|
|
872
1025
|
@r.setter
|
873
1026
|
def r(self, value):
|
874
1027
|
if self.viewer is None:
|
875
1028
|
return None
|
876
1029
|
self.viewer.set(r=value)
|
1030
|
+
self._fill_props()
|
877
1031
|
|
878
1032
|
@lookat.setter
|
879
1033
|
def lookat(self, value):
|
880
1034
|
if self.viewer is None:
|
881
1035
|
return None
|
882
1036
|
self.viewer.set(lookat=value)
|
1037
|
+
# self.viewer.set(lookat=value, phi=self.phi, theta=self.theta, r=self.r)
|
1038
|
+
self._fill_props()
|
1039
|
+
|
1040
|
+
def _eye_pos(self):
|
1041
|
+
""" Compute eye pos from lookat and r, phi, theta.
|
1042
|
+
|
1043
|
+
phi is the azimuth angle (radians)
|
1044
|
+
theta is the elevation angle (radians)
|
1045
|
+
r is the distance from lookat
|
1046
|
+
"""
|
1047
|
+
|
1048
|
+
lx, ly, lz = self.lookat
|
1049
|
+
r = self.r
|
1050
|
+
phi = self.phi
|
1051
|
+
theta = self.theta
|
1052
|
+
|
1053
|
+
x = lx + r*np.sin(phi)*np.cos(theta)
|
1054
|
+
y = ly + r*np.cos(phi)*np.cos(theta)
|
1055
|
+
z = lz + r*np.sin(theta)
|
1056
|
+
|
1057
|
+
return [x,y,z]
|
883
1058
|
|
884
1059
|
@eye.setter
|
885
1060
|
def eye(self, value):
|
@@ -892,11 +1067,13 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
892
1067
|
# Compute phi, theta, r based on eye and lookat and right vector
|
893
1068
|
r = np.sqrt((x-lx)**2 + (y-ly)**2 + (z-lz)**2)
|
894
1069
|
self.r =r
|
895
|
-
self.
|
896
|
-
self.
|
1070
|
+
self.theta = np.arcsin((z-lz)/r)
|
1071
|
+
self.phi = -np.arctan2((x-lx),(y-ly))
|
1072
|
+
self._fill_props()
|
897
1073
|
|
898
1074
|
@property
|
899
1075
|
def point_size(self):
|
1076
|
+
""" Point size in viewer -- user units """
|
900
1077
|
return self._point_size
|
901
1078
|
|
902
1079
|
@point_size.setter
|
@@ -904,6 +1081,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
904
1081
|
self._point_size = value
|
905
1082
|
if self.viewer is not None:
|
906
1083
|
self.viewer.set(point_size=value)
|
1084
|
+
self._fill_props()
|
907
1085
|
|
908
1086
|
@property
|
909
1087
|
def xyz(self):
|
@@ -914,17 +1092,18 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
914
1092
|
return self.data[:,3]
|
915
1093
|
|
916
1094
|
def codes_unique(self):
|
1095
|
+
""" Only unique codes """
|
917
1096
|
return list(np.unique(self.codes).astype(int))
|
918
1097
|
|
919
1098
|
def create_viewer(self, color_code:Colors_Lazviewer = None, classification:Classification_LAZ = None):
|
920
1099
|
""" Create a viewer for las data """
|
921
1100
|
|
922
|
-
if color_code is not None:
|
923
|
-
self.associated_color = color_code
|
924
|
-
|
925
1101
|
if classification is not None:
|
926
1102
|
self.classification = classification
|
927
1103
|
|
1104
|
+
if color_code is not None:
|
1105
|
+
self.associated_color = color_code
|
1106
|
+
|
928
1107
|
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
929
1108
|
|
930
1109
|
self.viewer = viewer(self.xyz, self._colors)
|
@@ -933,6 +1112,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
933
1112
|
return self.viewer
|
934
1113
|
|
935
1114
|
def interactive_update_colors(self):
|
1115
|
+
""" Create a frame to interactively update colors """
|
936
1116
|
|
937
1117
|
self.classification.interactive_update_colors()
|
938
1118
|
|
@@ -945,12 +1125,17 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
945
1125
|
self.classification._choose_colors.callback = new_callback_colors
|
946
1126
|
|
947
1127
|
def set_colors(self):
|
1128
|
+
""" Set colors in viewer --> using attributes method (not colormap) """
|
1129
|
+
|
948
1130
|
if self.viewer is not None:
|
949
1131
|
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
950
1132
|
self.viewer.attributes(self._colors)
|
951
1133
|
|
952
1134
|
def set_classification(self, classification:str = None):
|
1135
|
+
""" Set classification of LAZ data
|
953
1136
|
|
1137
|
+
TODO : Check if 2020-2022 SPW campaign is the same classification as 2013
|
1138
|
+
"""
|
954
1139
|
if classification is None:
|
955
1140
|
logging.warning(_('No classification chosen - Abort !'))
|
956
1141
|
elif classification == 'SPW 2013-2014':
|
@@ -959,6 +1144,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
959
1144
|
self.classification.init_2023()
|
960
1145
|
|
961
1146
|
def find_minmax(self, update=False):
|
1147
|
+
""" Find min and max of data """
|
962
1148
|
if self.data is not None:
|
963
1149
|
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
964
1150
|
|
@@ -996,6 +1182,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
996
1182
|
|
997
1183
|
@property
|
998
1184
|
def data(self):
|
1185
|
+
""" Full data array (x,y,z,code) """
|
999
1186
|
return self._data
|
1000
1187
|
|
1001
1188
|
@data.setter
|
@@ -1010,87 +1197,106 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1010
1197
|
def bounds(self, value):
|
1011
1198
|
self._bounds = value
|
1012
1199
|
|
1013
|
-
def from_grid(self, grid:
|
1014
|
-
|
1200
|
+
def from_grid(self, grid:xyz_laz_grids, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]]):
|
1201
|
+
""" Create data from grid LAZ """
|
1015
1202
|
self.bounds = bounds
|
1016
1203
|
self.data = grid.scan(bounds)
|
1017
1204
|
|
1018
1205
|
def from_file(self, fn:str):
|
1019
|
-
|
1206
|
+
""" Create data from LAZ file """
|
1020
1207
|
self.data = read_laz(fn)
|
1021
1208
|
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
1022
1209
|
|
1023
|
-
def
|
1210
|
+
def descimate(self, step:int):
|
1211
|
+
""" Descimate data.
|
1212
|
+
|
1213
|
+
Conserve only one point every 'step' points.
|
1214
|
+
|
1215
|
+
:param step: step of descimation
|
1216
|
+
"""
|
1024
1217
|
self.data = self.data[::step]
|
1025
1218
|
|
1026
1219
|
def get_data_class(self, key:int):
|
1220
|
+
""" Get data with a specific code """
|
1221
|
+
|
1222
|
+
assert isinstance(key, int), _('Key must be an integer')
|
1223
|
+
|
1027
1224
|
return self.data[self.data[:,3] == key]
|
1028
1225
|
|
1029
1226
|
def add_pose_in_memory(self, key_time:float = 1.):
|
1227
|
+
""" Add current pose in flight memory """
|
1228
|
+
|
1030
1229
|
if self.viewer is not None:
|
1031
1230
|
lookat = self.lookat
|
1032
1231
|
new_pose = (lookat[0], lookat[1], lookat[2], self.phi, self.theta, self.r)
|
1033
|
-
self.
|
1232
|
+
self._flight_memory.append((new_pose, key_time))
|
1034
1233
|
|
1035
1234
|
def play_flight(self, tlim=[-np.inf, np.inf], repeat=False, interp='cubic_natural'):
|
1036
|
-
|
1235
|
+
""" Play flight memory """
|
1037
1236
|
if self.viewer is not None:
|
1038
|
-
if len(self.
|
1039
|
-
poses = [cur[0] for cur in self.
|
1237
|
+
if len(self._flight_memory)>0:
|
1238
|
+
poses = [cur[0] for cur in self._flight_memory]
|
1040
1239
|
times = [0.]
|
1041
|
-
for i in range(1,len(self.
|
1042
|
-
times.append(times[-1]+self.
|
1240
|
+
for i in range(1,len(self._flight_memory)):
|
1241
|
+
times.append(times[-1]+self._flight_memory[i][1])
|
1043
1242
|
self.viewer.play(poses, times, tlim, repeat, interp)
|
1044
1243
|
|
1045
1244
|
def set_times(self, times:np.ndarray):
|
1046
|
-
|
1047
|
-
|
1245
|
+
""" Set times for flight memory """
|
1246
|
+
if len(self._flight_memory)>0:
|
1247
|
+
self._flight_memory = [(self._flight_memory[i][0], times[i]) for i in range(len(self._flight_memory))]
|
1048
1248
|
|
1049
1249
|
def set_times_increment(self, increment:float):
|
1050
|
-
if len(self.
|
1051
|
-
self.
|
1250
|
+
if len(self._flight_memory)>0:
|
1251
|
+
self._flight_memory = [(self._flight_memory[i][0], increment*i) for i in range(len(self._flight_memory))]
|
1052
1252
|
|
1053
1253
|
def get_times(self):
|
1054
|
-
return np.asarray([cur[1] for cur in self.
|
1254
|
+
return np.asarray([cur[1] for cur in self._flight_memory])
|
1255
|
+
|
1256
|
+
def record_flight(self, dirout:str, tlim=[-np.inf, np.inf], interp='cubic_natural', fps=24, prefix:str = 'laz_', ext:str = 'png'):
|
1257
|
+
""" Record flight memory in multiple images
|
1055
1258
|
|
1056
|
-
|
1259
|
+
FIXME : FREEZE the app --> to debug
|
1260
|
+
"""
|
1057
1261
|
if self.viewer is not None:
|
1058
|
-
if len(self.
|
1059
|
-
poses = [cur[0] for cur in self.
|
1262
|
+
if len(self._flight_memory)>0:
|
1263
|
+
poses = [cur[0] for cur in self._flight_memory]
|
1060
1264
|
times = [0.]
|
1061
|
-
for i in range(1,len(self.
|
1062
|
-
times.append(times[-1]+self.
|
1265
|
+
for i in range(1,len(self._flight_memory)):
|
1266
|
+
times.append(times[-1]+self._flight_memory[i][1])
|
1063
1267
|
self.viewer.record(dirout, poses, times, tlim, interp, fps=fps, prefix=prefix, ext=ext)
|
1064
1268
|
|
1065
1269
|
def save_flight(self, fn:str):
|
1066
|
-
""" Write memory to file JSON """
|
1270
|
+
""" Write flight memory to file JSON """
|
1271
|
+
|
1067
1272
|
import json
|
1068
1273
|
|
1069
|
-
if len(self.
|
1274
|
+
if len(self._flight_memory)>0:
|
1070
1275
|
with open(fn,'w') as f:
|
1071
|
-
json.dump(self.
|
1276
|
+
json.dump(self._flight_memory, f, indent=2)
|
1072
1277
|
|
1073
1278
|
def load_flight(self, fn:str):
|
1074
|
-
""" Load memory from file JSON """
|
1279
|
+
""" Load flight memory from file JSON """
|
1075
1280
|
import json
|
1076
1281
|
|
1077
1282
|
if exists(fn):
|
1078
1283
|
with open(fn,'r') as f:
|
1079
|
-
self.
|
1284
|
+
self._flight_memory = json.load(f)
|
1080
1285
|
|
1081
1286
|
|
1082
1287
|
def _callback_props(self):
|
1083
1288
|
|
1084
|
-
self.
|
1289
|
+
self._update_viewer()
|
1085
1290
|
|
1086
1291
|
def _callback_destroy_props(self):
|
1087
1292
|
|
1088
1293
|
if self._myprops is not None:
|
1089
|
-
self._callback_props()
|
1294
|
+
# self._callback_props()
|
1090
1295
|
self._myprops.Destroy()
|
1091
1296
|
self._myprops = None
|
1092
1297
|
|
1093
1298
|
def _create_props(self):
|
1299
|
+
""" Create properties Wolf_Param for LAZ data """
|
1094
1300
|
|
1095
1301
|
if self._myprops is not None:
|
1096
1302
|
return
|
@@ -1134,22 +1340,123 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1134
1340
|
codes_sel += str(curcode) + ','
|
1135
1341
|
ret = props.addparam('Selection', 'Codes', codes_sel, Type_Param.String, 'Codes to select')
|
1136
1342
|
|
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
1343
|
props.Populate()
|
1141
1344
|
|
1142
|
-
updatebutton = wx.Button(props, label=_('
|
1345
|
+
updatebutton = wx.Button(props, label=_('Get from viewer'))
|
1143
1346
|
props.sizerbut.Add(updatebutton,1,wx.EXPAND)
|
1144
|
-
|
1347
|
+
updatebutton.Bind(wx.EVT_BUTTON, self._fill_props)
|
1145
1348
|
|
1146
|
-
getselection = wx.Button(props, label=_('
|
1349
|
+
getselection = wx.Button(props, label=_('Edit selection'))
|
1147
1350
|
props.sizerbut.Add(getselection,1,wx.EXPAND)
|
1148
|
-
|
1351
|
+
getselection.Bind(wx.EVT_BUTTON, self._OnEdit_Selection)
|
1149
1352
|
|
1150
1353
|
props.Layout()
|
1151
1354
|
|
1152
|
-
def
|
1355
|
+
def _set_new_xls(self):
|
1356
|
+
""" Create a new Excel grid for selected data """
|
1357
|
+
|
1358
|
+
self._xlsFrame = wx.Frame(None, wx.ID_ANY, _('Selected points - ') + self.idx)
|
1359
|
+
self._xls = CpGrid(self._xlsFrame, wx.ID_ANY, style = wx.WANTS_CHARS)
|
1360
|
+
|
1361
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
1362
|
+
sizer.Add(self._xls, 1, wx.EXPAND)
|
1363
|
+
|
1364
|
+
nbclass = len(self.classification.classification)
|
1365
|
+
self._xls.CreateGrid(10, 4)
|
1366
|
+
|
1367
|
+
# Add a button to plot a histogram
|
1368
|
+
but_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
1369
|
+
plotbutton = wx.Button(self._xlsFrame, label=_('Plot histogram (All data)'))
|
1370
|
+
but_sizer.Add(plotbutton, 1, wx.EXPAND)
|
1371
|
+
plotbutton.Bind(wx.EVT_BUTTON, self.OnPlot_histogram)
|
1372
|
+
|
1373
|
+
plotbutton2 = wx.Button(self._xlsFrame, label=_('Plot histogram (Grid data)'))
|
1374
|
+
but_sizer.Add(plotbutton2, 1, wx.EXPAND)
|
1375
|
+
plotbutton2.Bind(wx.EVT_BUTTON, self.OnPlot_histogram_grid)
|
1376
|
+
|
1377
|
+
sizer.Add(but_sizer, 0, wx.EXPAND)
|
1378
|
+
|
1379
|
+
self._xlsFrame.SetSizer(sizer)
|
1380
|
+
self._xlsFrame.Layout()
|
1381
|
+
self._xlsFrame.Show()
|
1382
|
+
|
1383
|
+
icon = wx.Icon()
|
1384
|
+
icon_path = Path(__file__).parent.parent / "apps/wolf_logo2.bmp"
|
1385
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
1386
|
+
self._xlsFrame.SetIcon(icon)
|
1387
|
+
|
1388
|
+
def OnPlot_histogram(self, event:wx.MouseEvent):
|
1389
|
+
""" Plot histogram of selected data """
|
1390
|
+
self._plot_histogram()
|
1391
|
+
|
1392
|
+
def OnPlot_histogram_grid(self, event:wx.MouseEvent):
|
1393
|
+
""" Plot histogram of selected data """
|
1394
|
+
self._plot_histogram_grid()
|
1395
|
+
|
1396
|
+
def _plot_histogram_grid(self):
|
1397
|
+
""" Histogram ONLY of selected data in grid.
|
1398
|
+
|
1399
|
+
The data are extracted based on the first column of the grid
|
1400
|
+
untile an empty cell is found.
|
1401
|
+
"""
|
1402
|
+
|
1403
|
+
xls = self._xls
|
1404
|
+
|
1405
|
+
if xls is None:
|
1406
|
+
logging.warning(_('No Excel grid'))
|
1407
|
+
return
|
1408
|
+
|
1409
|
+
# Find not null cells
|
1410
|
+
nbrows = 1
|
1411
|
+
|
1412
|
+
while xls.GetCellValue(nbrows,0) != '' and nbrows < xls.NumberRows:
|
1413
|
+
nbrows += 1
|
1414
|
+
|
1415
|
+
if nbrows == 1:
|
1416
|
+
logging.warning(_('Nt enough points selected'))
|
1417
|
+
return
|
1418
|
+
|
1419
|
+
xyz = np.zeros((nbrows,3))
|
1420
|
+
codes = np.zeros(nbrows)
|
1421
|
+
|
1422
|
+
try:
|
1423
|
+
for i in range(nbrows):
|
1424
|
+
xyz[i,0] = float(xls.GetCellValue(i,0))
|
1425
|
+
xyz[i,1] = float(xls.GetCellValue(i,1))
|
1426
|
+
xyz[i,2] = float(xls.GetCellValue(i,2))
|
1427
|
+
codes[i] = int(xls.GetCellValue(i,3))
|
1428
|
+
except Exception as e:
|
1429
|
+
logging.error(e)
|
1430
|
+
logging.warning(_('Bad values in grid - Check your input'))
|
1431
|
+
return
|
1432
|
+
|
1433
|
+
fig = plt.figure()
|
1434
|
+
|
1435
|
+
ax = fig.add_subplot(111)
|
1436
|
+
|
1437
|
+
ax.hist(xyz[:,2], bins=256)
|
1438
|
+
|
1439
|
+
fig.show()
|
1440
|
+
|
1441
|
+
def _plot_histogram(self):
|
1442
|
+
""" """
|
1443
|
+
|
1444
|
+
xyz = self.xyz_selected
|
1445
|
+
|
1446
|
+
if xyz.shape[0]==0:
|
1447
|
+
logging.warning(_('No points selected'))
|
1448
|
+
return
|
1449
|
+
|
1450
|
+
fig = plt.figure()
|
1451
|
+
|
1452
|
+
ax = fig.add_subplot(111)
|
1453
|
+
|
1454
|
+
ax.hist(xyz[:,2], bins=256)
|
1455
|
+
|
1456
|
+
fig.show()
|
1457
|
+
|
1458
|
+
def _selection2vector(self):
|
1459
|
+
""" FIXME: must use RANSAC to compute a segment from the selected points """
|
1153
1460
|
|
1154
1461
|
if self.viewer is None:
|
1155
1462
|
logging.warning(_('No viewer'))
|
@@ -1161,24 +1468,40 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1161
1468
|
logging.warning(_('No points selected'))
|
1162
1469
|
return
|
1163
1470
|
|
1164
|
-
|
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)
|
1471
|
+
vect = vector(name = self.idx + '_selection', fromnumpy=xyz)
|
1167
1472
|
|
1168
|
-
|
1169
|
-
sizer.Add(self._xls, 1, wx.EXPAND)
|
1473
|
+
return vect
|
1170
1474
|
|
1171
|
-
nbclass = len(self.classification.classification)
|
1172
|
-
self._xls.CreateGrid(xyz.shape[0] + nbclass +1, 4)
|
1173
1475
|
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1476
|
+
def _OnEdit_Selection(self, event:wx.MouseEvent):
|
1477
|
+
""" Get selection from viewer and create a XLS grid """
|
1478
|
+
self._edit_selection()
|
1479
|
+
|
1480
|
+
def _edit_selection(self):
|
1481
|
+
|
1482
|
+
if self.viewer is None:
|
1483
|
+
logging.warning(_('No viewer'))
|
1484
|
+
return
|
1177
1485
|
|
1486
|
+
xyz = self.xyz_selected
|
1487
|
+
|
1488
|
+
if xyz.shape[0]==0:
|
1489
|
+
logging.warning(_('No points selected'))
|
1490
|
+
return
|
1491
|
+
|
1492
|
+
if self._xls is None:
|
1493
|
+
self._set_new_xls()
|
1178
1494
|
else:
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1495
|
+
try:
|
1496
|
+
self._xls.ClearGrid()
|
1497
|
+
except:
|
1498
|
+
#Useful if the grid is already destroyed
|
1499
|
+
self._set_new_xls()
|
1500
|
+
|
1501
|
+
nbclass = len(self.classification.classification)
|
1502
|
+
min_rows = xyz.shape[0] + nbclass +1
|
1503
|
+
if self._xls.NumberRows < min_rows:
|
1504
|
+
self._xls.AppendRows(min_rows - self._xls.NumberRows)
|
1182
1505
|
|
1183
1506
|
self._xls.SetColLabelValue(0, 'X')
|
1184
1507
|
self._xls.SetColLabelValue(1, 'Y')
|
@@ -1206,7 +1529,8 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1206
1529
|
self._xlsFrame.Raise()
|
1207
1530
|
self._xlsFrame.Center()
|
1208
1531
|
|
1209
|
-
def
|
1532
|
+
def _update_viewer(self):
|
1533
|
+
""" Update the viewer with properties """
|
1210
1534
|
|
1211
1535
|
if self._myprops is None:
|
1212
1536
|
return
|
@@ -1217,7 +1541,7 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1217
1541
|
props = self._myprops
|
1218
1542
|
|
1219
1543
|
self.lookat = (props[('Look at', 'X')], props[('Look at', 'Y')], props[('Look at', 'Z')])
|
1220
|
-
self.eye = (props[('Camera', 'X')], props[('Camera', 'Y')], props[('Camera', 'Z')])
|
1544
|
+
# self.eye = (props[('Camera', 'X')], props[('Camera', 'Y')], props[('Camera', 'Z')])
|
1221
1545
|
|
1222
1546
|
color = np.asarray(props[('Background', 'Color')])
|
1223
1547
|
self.bg_color(color / 255.)
|
@@ -1245,17 +1569,8 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1245
1569
|
logging.warning(_('Nullify selection filter - Check your input'))
|
1246
1570
|
self._select_only_codes = []
|
1247
1571
|
|
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
1572
|
def _fill_props(self, full:bool = False):
|
1258
|
-
|
1573
|
+
""" Fill properties from attributes """
|
1259
1574
|
if self._myprops is None:
|
1260
1575
|
return
|
1261
1576
|
|
@@ -1288,31 +1603,10 @@ class Base_LAZ_Data(Element_To_Draw):
|
|
1288
1603
|
|
1289
1604
|
props[('Points', 'Size')] = self._point_size
|
1290
1605
|
|
1291
|
-
# props[('Auto Update', 'Interval')] = self._auto_update_interval
|
1292
|
-
# props[('Auto Update', 'Active')] = self._auto_update_properties
|
1293
|
-
|
1294
1606
|
props.Populate()
|
1295
1607
|
|
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
1608
|
def show_properties(self):
|
1315
|
-
|
1609
|
+
""" Surcharged method (see Element_To_Draw) to show properties from MapViewer"""
|
1316
1610
|
if self.viewer is None:
|
1317
1611
|
return
|
1318
1612
|
|