wolfhece 2.1.116__py3-none-any.whl → 2.1.118__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/CpGrid.py +1 -1
- wolfhece/PyDraw.py +964 -360
- wolfhece/PyParams.py +27 -2
- wolfhece/apps/version.py +1 -1
- wolfhece/lazviewer/laz_viewer.py +651 -10
- wolfhece/lazviewer/viewer/viewer.py +2 -2
- wolfhece/wolf_array.py +278 -5
- wolfhece/wolf_tiles.py +2 -2
- wolfhece/wolf_vrt.py +18 -17
- {wolfhece-2.1.116.dist-info → wolfhece-2.1.118.dist-info}/METADATA +1 -1
- {wolfhece-2.1.116.dist-info → wolfhece-2.1.118.dist-info}/RECORD +14 -14
- {wolfhece-2.1.116.dist-info → wolfhece-2.1.118.dist-info}/WHEEL +0 -0
- {wolfhece-2.1.116.dist-info → wolfhece-2.1.118.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.116.dist-info → wolfhece-2.1.118.dist-info}/top_level.txt +0 -0
wolfhece/lazviewer/laz_viewer.py
CHANGED
@@ -5,7 +5,7 @@ from PIL import Image
|
|
5
5
|
from os.path import exists,join
|
6
6
|
import matplotlib.pyplot as plt
|
7
7
|
from os import stat,remove,listdir,scandir, makedirs
|
8
|
-
import
|
8
|
+
from pathlib import Path
|
9
9
|
import math
|
10
10
|
from time import sleep
|
11
11
|
from typing import Literal, Union
|
@@ -22,8 +22,10 @@ from . import viewer
|
|
22
22
|
from ..PyWMS import getWalonmap
|
23
23
|
from ..PyTranslate import _
|
24
24
|
from ..color_constants import Colors
|
25
|
-
from ..PyParams import Wolf_Param
|
25
|
+
from ..PyParams import Wolf_Param, Type_Param
|
26
26
|
from ..matplotlib_fig import Matplotlib_Figure as mplfig
|
27
|
+
from ..drawing_obj import Element_To_Draw
|
28
|
+
from ..CpGrid import CpGrid
|
27
29
|
|
28
30
|
"""
|
29
31
|
Importation et visualisation de données LAS et LAZ
|
@@ -43,16 +45,20 @@ class Colors_Lazviewer(Enum):
|
|
43
45
|
class Classification_LAZ():
|
44
46
|
|
45
47
|
def __init__(self) -> None:
|
48
|
+
|
49
|
+
self.class_name = 'SPW-Geofit 2023'
|
46
50
|
self.classification:dict[str,str,list[float,float,float,float]] = {}
|
47
51
|
self.test_wx()
|
48
52
|
|
49
53
|
self._choose_colors = None
|
54
|
+
self._viewer = None
|
50
55
|
|
51
56
|
def test_wx(self):
|
52
57
|
self.wx_exists = wx.App.Get() is not None
|
53
58
|
|
54
59
|
def init_2013(self):
|
55
60
|
|
61
|
+
self.class_name = 'SPW 2013-2014'
|
56
62
|
self.classification={
|
57
63
|
0 : ['0', 'Pas de classification', Colors.rgb_withalpha_float('black',.2),],
|
58
64
|
1 : ['Hors-sol', 'building, toits et autres', Colors.rgb_withalpha_float('white',.2),],
|
@@ -63,6 +69,7 @@ class Classification_LAZ():
|
|
63
69
|
|
64
70
|
def init_2023(self):
|
65
71
|
|
72
|
+
self.class_name = 'SPW-Geofit 2023'
|
66
73
|
self.classification={
|
67
74
|
0 : ['0', 'Pas de classification', Colors.rgb_withalpha_float('black',.2),],
|
68
75
|
1 : ['Défaut', 'Voiture, câbles électrique, points de végétation diffus, Sursol non utile', Colors.rgb_withalpha_float('white',.2),],
|
@@ -72,6 +79,7 @@ class Classification_LAZ():
|
|
72
79
|
9 : ['Eau', 'Points de la surface d\’eau brute mesurés par le scanner 2',Colors.rgb_withalpha_float('royalblue',.3)],
|
73
80
|
10 : ['Ponts', 'Les ponts ont été classés à part pour améliorer la définition du MNT. Ils ont été ouverts grâce',Colors.rgb_withalpha_float('lightyellow1',1.)],
|
74
81
|
11 : ['Mur de berges', 'Mur et muret en berge de la Vesdre dépassant le sol à des vocation de réaliser une modélisation 3D hydraulique avec ces obstacles.',Colors.rgb_withalpha_float('red1',1.)],
|
82
|
+
13 : ['Inconnu', 'A vérifier auSPW', Colors.rgb_withalpha_float('lightslategray',.2)],
|
75
83
|
15 : ['Tranche d\'eau', 'Echo intermédiaire dans l\’eau n\’appartenant ni à la surface d\’eau ni au fond du lit', Colors.rgb_withalpha_float('lightblue',.2)],
|
76
84
|
16 : ['Surface bathymétrique', 'Fond du lit de la Vesdre et de ses affluents et des autres surfaces d\’eau mesurées à partir du scanner 3 FWF discrétisé',Colors.rgb_withalpha_float('sandybrown',1.)],
|
77
85
|
17 : ['Surface bathymétrique incertaine', 'Surface bathymétrique sur les zones peu profondes principalement sous végétation où les intensités des échos sont parfois trop faibles pour avoir la certitude qu\’ils représentent le fond de rivière. La classe 17 est néanmoins plus certaine que la classe 18. Elle est utilisée dans la génération des MNT par défaut.',Colors.rgb_withalpha_float('rosybrown',.5)],
|
@@ -142,7 +150,7 @@ class xyz_laz():
|
|
142
150
|
self.data = []
|
143
151
|
return
|
144
152
|
|
145
|
-
last_part =
|
153
|
+
last_part = Path(fn).name
|
146
154
|
parts = last_part.split('_')
|
147
155
|
|
148
156
|
# L'origine est codée dans le nom de fichier
|
@@ -153,7 +161,7 @@ class xyz_laz():
|
|
153
161
|
dy = 3500.
|
154
162
|
|
155
163
|
# Récupération du lien vers le fichier GridInfo.txt
|
156
|
-
gridinfo=join(
|
164
|
+
gridinfo=join(Path(fn).parent,'gridinfo.txt')
|
157
165
|
|
158
166
|
if exists(gridinfo):
|
159
167
|
with open(gridinfo,'r') as f:
|
@@ -240,8 +248,8 @@ class xyz_laz():
|
|
240
248
|
if exists(fn+'.zip'):
|
241
249
|
with zipfile.ZipFile(fn+'.zip','r') as zip_file:
|
242
250
|
fn = zip_file.namelist()[0]
|
243
|
-
zip_file.extract(fn, path=
|
244
|
-
fn = join(
|
251
|
+
zip_file.extract(fn, path=Path(fn).parent)
|
252
|
+
fn = join(Path(fn).parent, fn)
|
245
253
|
elif not exists(fn):
|
246
254
|
return
|
247
255
|
|
@@ -391,7 +399,7 @@ class xyz_laz_grid():
|
|
391
399
|
xloc = np.linspace(xbounds[0], xbounds[1], gridsize[0]+1)
|
392
400
|
yloc = np.linspace(ybounds[0], ybounds[1], gridsize[1]+1)
|
393
401
|
|
394
|
-
dirout =
|
402
|
+
dirout = Path(fn_out).parent
|
395
403
|
fn=join(dirout,'gridinfo.txt')
|
396
404
|
|
397
405
|
if exists(fn):
|
@@ -401,7 +409,7 @@ class xyz_laz_grid():
|
|
401
409
|
f.write(str(int(xbounds[0]))+','+str(int(xbounds[1]))+'\n')
|
402
410
|
f.write(str(int(ybounds[0]))+','+str(int(ybounds[1]))+'\n')
|
403
411
|
f.write(str(int(gridsize[0]))+','+str(int(gridsize[1]))+'\n')
|
404
|
-
f.write(
|
412
|
+
f.write(Path(fn_out).name+'_'+'x1'+'_'+'y1'+'_xyz.bin'+'\n')
|
405
413
|
if force_format == np.float64:
|
406
414
|
f.write('xy_float64')
|
407
415
|
else:
|
@@ -477,7 +485,7 @@ class xyz_laz_grid():
|
|
477
485
|
if force_format == np.float64:
|
478
486
|
fnzip = fn + '.zip'
|
479
487
|
with zipfile.ZipFile(fnzip,'w',zipfile.ZIP_DEFLATED) as zip_file:
|
480
|
-
zip_file.write(fn,
|
488
|
+
zip_file.write(fn, Path(fn).name)
|
481
489
|
remove(fn)
|
482
490
|
|
483
491
|
|
@@ -678,6 +686,639 @@ class xyz_laz_grids():
|
|
678
686
|
gridsize=[max(int(dx/ds),1), max(int(dy/ds),1)],
|
679
687
|
force_format=force_format))
|
680
688
|
|
689
|
+
|
690
|
+
class Base_LAZ_Data(Element_To_Draw):
|
691
|
+
|
692
|
+
def __init__(self, idx:str = '', plotted:bool = False, mapviewer = None, need_for_wx:bool = False) -> None:
|
693
|
+
|
694
|
+
self._bounds = [[0.,0.],[0.,0.]] # [[xmin,xmax],[ymin,ymax]]
|
695
|
+
super().__init__(idx, plotted, mapviewer, need_for_wx)
|
696
|
+
|
697
|
+
self.lazfile:Path = None
|
698
|
+
self._data:np.ndarray = None
|
699
|
+
self.classification = Classification_LAZ()
|
700
|
+
|
701
|
+
self.associated_color:int = Colors_Lazviewer.CODE_2023.value
|
702
|
+
|
703
|
+
self.viewer:viewer = None
|
704
|
+
self._colors:np.ndarray = None
|
705
|
+
|
706
|
+
self._point_size = .05
|
707
|
+
|
708
|
+
self._memory = []
|
709
|
+
|
710
|
+
self._myprops = None
|
711
|
+
|
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]
|
715
|
+
|
716
|
+
self._floor_level = 0.
|
717
|
+
self._floor_color = [0,0,0,255]
|
718
|
+
|
719
|
+
self._show_grid = True
|
720
|
+
self._show_axis = True
|
721
|
+
self._show_info = True
|
722
|
+
|
723
|
+
self._select_only_codes = []
|
724
|
+
|
725
|
+
self._xls:CpGrid = None
|
726
|
+
self._xlsFrame:wx.Frame = None
|
727
|
+
|
728
|
+
# self._auto_update_properties = False
|
729
|
+
# self._auto_update_interval = 100 # ms
|
730
|
+
# self._timer = None
|
731
|
+
|
732
|
+
def filter_data(self, codes:list[int]):
|
733
|
+
self._data = self._data[np.isin(self._data[:,3],codes)]
|
734
|
+
|
735
|
+
def bg_color(self, value):
|
736
|
+
if self.viewer is not None:
|
737
|
+
return self.viewer.set(bg_color = value)
|
738
|
+
|
739
|
+
def bg_color_top(self, value):
|
740
|
+
if self.viewer is not None:
|
741
|
+
return self.viewer.set(bg_color_top = value)
|
742
|
+
|
743
|
+
def bg_color_bottom(self, value):
|
744
|
+
if self.viewer is not None:
|
745
|
+
return self.viewer.set(bg_color_bottom = value)
|
746
|
+
|
747
|
+
def floor_level(self, value):
|
748
|
+
if self.viewer is not None:
|
749
|
+
return self.viewer.set(floor_level = value)
|
750
|
+
|
751
|
+
def floor_color(self, value):
|
752
|
+
if self.viewer is not None:
|
753
|
+
return self.viewer.set(floor_color = value)
|
754
|
+
|
755
|
+
def show_grid(self, value:bool):
|
756
|
+
if self.viewer is not None:
|
757
|
+
return self.viewer.set(show_grid = value)
|
758
|
+
|
759
|
+
def show_axis(self, value:bool):
|
760
|
+
if self.viewer is not None:
|
761
|
+
return self.viewer.set(show_axis = value)
|
762
|
+
|
763
|
+
def show_info(self, value:bool):
|
764
|
+
if self.viewer is not None:
|
765
|
+
return self.viewer.set(show_info = value)
|
766
|
+
|
767
|
+
@property
|
768
|
+
def selected(self):
|
769
|
+
if self.viewer is None:
|
770
|
+
return None
|
771
|
+
return self.viewer.get('selected')
|
772
|
+
|
773
|
+
@property
|
774
|
+
def xyz_selected(self) -> np.ndarray:
|
775
|
+
if self.viewer is None:
|
776
|
+
return None
|
777
|
+
|
778
|
+
if self.selected.shape[0] == 0:
|
779
|
+
return np.ndarray((0,3))
|
780
|
+
|
781
|
+
if len(self._select_only_codes)>0:
|
782
|
+
return self.data[self.selected,:3][np.isin(self.data[self.selected,3],self._select_only_codes)]
|
783
|
+
else:
|
784
|
+
return self.data[self.selected,:3]
|
785
|
+
|
786
|
+
@property
|
787
|
+
def code_selected(self) -> np.ndarray:
|
788
|
+
if self.viewer is None:
|
789
|
+
return None
|
790
|
+
|
791
|
+
if self.selected.shape[0] == 0:
|
792
|
+
return np.ndarray((0,1))
|
793
|
+
|
794
|
+
if len(self._select_only_codes)>0:
|
795
|
+
return self.data[self.selected,3][np.isin(self.data[self.selected,3],self._select_only_codes)]
|
796
|
+
else:
|
797
|
+
return self.data[self.selected,3]
|
798
|
+
|
799
|
+
@property
|
800
|
+
def num_points(self):
|
801
|
+
""" Number of points """
|
802
|
+
nb1 = self.data.shape[0]
|
803
|
+
if self.viewer is not None:
|
804
|
+
nb2 = self.viewer.get('num_points')[0]
|
805
|
+
assert nb1 == nb2, _('Incoherent number of points')
|
806
|
+
|
807
|
+
return nb1
|
808
|
+
|
809
|
+
@property
|
810
|
+
def nb_points(self):
|
811
|
+
""" Number of points """
|
812
|
+
return self.num_points
|
813
|
+
|
814
|
+
@property
|
815
|
+
def nb(self):
|
816
|
+
""" Number of points """
|
817
|
+
return self.num_points
|
818
|
+
|
819
|
+
@property
|
820
|
+
def right(self):
|
821
|
+
"""Camera Right vector """
|
822
|
+
return self.viewer.get('right')
|
823
|
+
|
824
|
+
@property
|
825
|
+
def mvp(self):
|
826
|
+
if self.viewer is None:
|
827
|
+
return None
|
828
|
+
return self.viewer.get('mvp')
|
829
|
+
|
830
|
+
@property
|
831
|
+
def eye(self):
|
832
|
+
if self.viewer is None:
|
833
|
+
return None
|
834
|
+
return self.viewer.get('eye')
|
835
|
+
|
836
|
+
@property
|
837
|
+
def lookat(self):
|
838
|
+
if self.viewer is None:
|
839
|
+
return None
|
840
|
+
return self.viewer.get('lookat')
|
841
|
+
|
842
|
+
@property
|
843
|
+
def phi(self):
|
844
|
+
if self.viewer is None:
|
845
|
+
return None
|
846
|
+
return self.viewer.get('phi')[0]
|
847
|
+
|
848
|
+
@property
|
849
|
+
def theta(self):
|
850
|
+
if self.viewer is None:
|
851
|
+
return None
|
852
|
+
return self.viewer.get('theta')[0]
|
853
|
+
|
854
|
+
@property
|
855
|
+
def r(self):
|
856
|
+
if self.viewer is None:
|
857
|
+
return None
|
858
|
+
return self.viewer.get('r')[0]
|
859
|
+
|
860
|
+
@phi.setter
|
861
|
+
def phi(self, value):
|
862
|
+
if self.viewer is None:
|
863
|
+
return None
|
864
|
+
self.viewer.set(phi=value)
|
865
|
+
|
866
|
+
@theta.setter
|
867
|
+
def theta(self, value):
|
868
|
+
if self.viewer is None:
|
869
|
+
return None
|
870
|
+
self.viewer.set(theta=value)
|
871
|
+
|
872
|
+
@r.setter
|
873
|
+
def r(self, value):
|
874
|
+
if self.viewer is None:
|
875
|
+
return None
|
876
|
+
self.viewer.set(r=value)
|
877
|
+
|
878
|
+
@lookat.setter
|
879
|
+
def lookat(self, value):
|
880
|
+
if self.viewer is None:
|
881
|
+
return None
|
882
|
+
self.viewer.set(lookat=value)
|
883
|
+
|
884
|
+
@eye.setter
|
885
|
+
def eye(self, value):
|
886
|
+
if self.viewer is None:
|
887
|
+
return None
|
888
|
+
x,y,z = value
|
889
|
+
lx, ly, lz = self.lookat
|
890
|
+
right = self.right
|
891
|
+
|
892
|
+
# Compute phi, theta, r based on eye and lookat and right vector
|
893
|
+
r = np.sqrt((x-lx)**2 + (y-ly)**2 + (z-lz)**2)
|
894
|
+
self.r =r
|
895
|
+
self.phi = np.arccos((z-lz)/r)
|
896
|
+
self.theta = np.arctan2((y-ly),(x-lx))
|
897
|
+
|
898
|
+
@property
|
899
|
+
def point_size(self):
|
900
|
+
return self._point_size
|
901
|
+
|
902
|
+
@point_size.setter
|
903
|
+
def point_size(self, value):
|
904
|
+
self._point_size = value
|
905
|
+
if self.viewer is not None:
|
906
|
+
self.viewer.set(point_size=value)
|
907
|
+
|
908
|
+
@property
|
909
|
+
def xyz(self):
|
910
|
+
return self.data[:,:3]
|
911
|
+
|
912
|
+
@property
|
913
|
+
def codes(self):
|
914
|
+
return self.data[:,3]
|
915
|
+
|
916
|
+
def codes_unique(self):
|
917
|
+
return list(np.unique(self.codes).astype(int))
|
918
|
+
|
919
|
+
def create_viewer(self, color_code:Colors_Lazviewer = None, classification:Classification_LAZ = None):
|
920
|
+
""" Create a viewer for las data """
|
921
|
+
|
922
|
+
if color_code is not None:
|
923
|
+
self.associated_color = color_code
|
924
|
+
|
925
|
+
if classification is not None:
|
926
|
+
self.classification = classification
|
927
|
+
|
928
|
+
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
929
|
+
|
930
|
+
self.viewer = viewer(self.xyz, self._colors)
|
931
|
+
self.viewer.set(point_size= self._point_size)
|
932
|
+
|
933
|
+
return self.viewer
|
934
|
+
|
935
|
+
def interactive_update_colors(self):
|
936
|
+
|
937
|
+
self.classification.interactive_update_colors()
|
938
|
+
|
939
|
+
self.classification._choose_colors.SetTitle(_('Colors of ') + self.idx)
|
940
|
+
|
941
|
+
def new_callback_colors():
|
942
|
+
self.classification.callback_colors()
|
943
|
+
self.set_colors()
|
944
|
+
|
945
|
+
self.classification._choose_colors.callback = new_callback_colors
|
946
|
+
|
947
|
+
def set_colors(self):
|
948
|
+
if self.viewer is not None:
|
949
|
+
self._colors = get_colors(self.data, self.associated_color, palette_classif= self.classification)
|
950
|
+
self.viewer.attributes(self._colors)
|
951
|
+
|
952
|
+
def set_classification(self, classification:str = None):
|
953
|
+
|
954
|
+
if classification is None:
|
955
|
+
logging.warning(_('No classification chosen - Abort !'))
|
956
|
+
elif classification == 'SPW 2013-2014':
|
957
|
+
self.classification.init_2013()
|
958
|
+
else:
|
959
|
+
self.classification.init_2023()
|
960
|
+
|
961
|
+
def find_minmax(self, update=False):
|
962
|
+
if self.data is not None:
|
963
|
+
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
964
|
+
|
965
|
+
@property
|
966
|
+
def xmin(self):
|
967
|
+
return self.bounds[0][0]
|
968
|
+
|
969
|
+
@xmin.setter
|
970
|
+
def xmin(self, value):
|
971
|
+
self._bounds[0][0] = value
|
972
|
+
|
973
|
+
@property
|
974
|
+
def xmax(self):
|
975
|
+
return self.bounds[0][1]
|
976
|
+
|
977
|
+
@xmax.setter
|
978
|
+
def xmax(self, value):
|
979
|
+
self._bounds[0][1] = value
|
980
|
+
|
981
|
+
@property
|
982
|
+
def ymin(self):
|
983
|
+
return self.bounds[1][0]
|
984
|
+
|
985
|
+
@ymin.setter
|
986
|
+
def ymin(self, value):
|
987
|
+
self._bounds[1][0] = value
|
988
|
+
|
989
|
+
@property
|
990
|
+
def ymax(self):
|
991
|
+
return self.bounds[1][1]
|
992
|
+
|
993
|
+
@ymax.setter
|
994
|
+
def ymax(self, value):
|
995
|
+
self._bounds[1][1] = value
|
996
|
+
|
997
|
+
@property
|
998
|
+
def data(self):
|
999
|
+
return self._data
|
1000
|
+
|
1001
|
+
@data.setter
|
1002
|
+
def data(self, value):
|
1003
|
+
self._data = value
|
1004
|
+
|
1005
|
+
@property
|
1006
|
+
def bounds(self):
|
1007
|
+
return self._bounds
|
1008
|
+
|
1009
|
+
@bounds.setter
|
1010
|
+
def bounds(self, value):
|
1011
|
+
self._bounds = value
|
1012
|
+
|
1013
|
+
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
|
+
|
1015
|
+
self.bounds = bounds
|
1016
|
+
self.data = grid.scan(bounds)
|
1017
|
+
|
1018
|
+
def from_file(self, fn:str):
|
1019
|
+
|
1020
|
+
self.data = read_laz(fn)
|
1021
|
+
self.bounds = [[np.min(self.data[:,0]), np.max(self.data[:,0])],[np.min(self.data[:,1]), np.max(self.data[:,1])]]
|
1022
|
+
|
1023
|
+
def decimate(self, step:int):
|
1024
|
+
self.data = self.data[::step]
|
1025
|
+
|
1026
|
+
def get_data_class(self, key:int):
|
1027
|
+
return self.data[self.data[:,3] == key]
|
1028
|
+
|
1029
|
+
def add_pose_in_memory(self, key_time:float = 1.):
|
1030
|
+
if self.viewer is not None:
|
1031
|
+
lookat = self.lookat
|
1032
|
+
new_pose = (lookat[0], lookat[1], lookat[2], self.phi, self.theta, self.r)
|
1033
|
+
self._memory.append((new_pose, key_time))
|
1034
|
+
|
1035
|
+
def play_flight(self, tlim=[-np.inf, np.inf], repeat=False, interp='cubic_natural'):
|
1036
|
+
|
1037
|
+
if self.viewer is not None:
|
1038
|
+
if len(self._memory)>0:
|
1039
|
+
poses = [cur[0] for cur in self._memory]
|
1040
|
+
times = [0.]
|
1041
|
+
for i in range(1,len(self._memory)):
|
1042
|
+
times.append(times[-1]+self._memory[i][1])
|
1043
|
+
self.viewer.play(poses, times, tlim, repeat, interp)
|
1044
|
+
|
1045
|
+
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))]
|
1048
|
+
|
1049
|
+
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))]
|
1052
|
+
|
1053
|
+
def get_times(self):
|
1054
|
+
return np.asarray([cur[1] for cur in self._memory])
|
1055
|
+
|
1056
|
+
def record_flight(self, dirout:str, tlim=[-np.inf, np.inf], interp='cubic_natural', fps=24, prefix:str = 'laz_', ext:str = '.png'):
|
1057
|
+
if self.viewer is not None:
|
1058
|
+
if len(self._memory)>0:
|
1059
|
+
poses = [cur[0] for cur in self._memory]
|
1060
|
+
times = [0.]
|
1061
|
+
for i in range(1,len(self._memory)):
|
1062
|
+
times.append(times[-1]+self._memory[i][1])
|
1063
|
+
self.viewer.record(dirout, poses, times, tlim, interp, fps=fps, prefix=prefix, ext=ext)
|
1064
|
+
|
1065
|
+
def save_flight(self, fn:str):
|
1066
|
+
""" Write memory to file JSON """
|
1067
|
+
import json
|
1068
|
+
|
1069
|
+
if len(self._memory)>0:
|
1070
|
+
with open(fn,'w') as f:
|
1071
|
+
json.dump(self._memory, f, indent=2)
|
1072
|
+
|
1073
|
+
def load_flight(self, fn:str):
|
1074
|
+
""" Load memory from file JSON """
|
1075
|
+
import json
|
1076
|
+
|
1077
|
+
if exists(fn):
|
1078
|
+
with open(fn,'r') as f:
|
1079
|
+
self._memory = json.load(f)
|
1080
|
+
|
1081
|
+
|
1082
|
+
def _callback_props(self):
|
1083
|
+
|
1084
|
+
self._update_props()
|
1085
|
+
|
1086
|
+
def _callback_destroy_props(self):
|
1087
|
+
|
1088
|
+
if self._myprops is not None:
|
1089
|
+
self._callback_props()
|
1090
|
+
self._myprops.Destroy()
|
1091
|
+
self._myprops = None
|
1092
|
+
|
1093
|
+
def _create_props(self):
|
1094
|
+
|
1095
|
+
if self._myprops is not None:
|
1096
|
+
return
|
1097
|
+
|
1098
|
+
self._myprops = Wolf_Param(None, title=_('Properties of ') + self.idx,
|
1099
|
+
to_read=False, force_even_if_same_default= True)
|
1100
|
+
|
1101
|
+
props = self._myprops
|
1102
|
+
|
1103
|
+
props.set_callbacks(self._callback_props, self._callback_destroy_props)
|
1104
|
+
props.hide_selected_buttons()
|
1105
|
+
|
1106
|
+
|
1107
|
+
ret = props.addparam('Camera', 'X', self.eye[0], Type_Param.Float, 'eye_x')
|
1108
|
+
ret = props.addparam('Camera', 'Y', self.eye[1], Type_Param.Float, 'eye_y')
|
1109
|
+
ret = props.addparam('Camera', 'Z', self.eye[2], Type_Param.Float, 'eye_z')
|
1110
|
+
|
1111
|
+
ret = props.addparam('Look at', 'X', self.lookat[0], Type_Param.Float, 'lookat_x')
|
1112
|
+
ret = props.addparam('Look at', 'Y', self.lookat[1], Type_Param.Float, 'lookat_y')
|
1113
|
+
ret = props.addparam('Look at', 'Z', self.lookat[2], Type_Param.Float, 'lookat_z')
|
1114
|
+
|
1115
|
+
ret = props.addparam('Relative Position', 'Phi', self.phi, Type_Param.Float, 'azimuthal angle (radians) - (phi, theta, r) are spherical coordinates specifying camera position relative to the look at position.')
|
1116
|
+
ret = props.addparam('Relative Position', 'Theta', self.theta, Type_Param.Float, 'elevation angle (radians) - (phi, theta, r) are spherical coordinates specifying camera position relative to the look at position.')
|
1117
|
+
ret = props.addparam('Relative Position', 'R', self.r, Type_Param.Float, 'distance to look-at point - (phi, theta, r) are spherical coordinates specifying camera position relative to the look at position.')
|
1118
|
+
|
1119
|
+
ret = props.addparam('Background', 'Color', self._bg_color, Type_Param.Color, 'Background color')
|
1120
|
+
ret = props.addparam('Background', 'Top Color', self._bg_color_top, Type_Param.Color, 'Top Background color')
|
1121
|
+
ret = props.addparam('Background', 'Bottom Color', self._bg_color_bottom, Type_Param.Color, 'Bottom Background color')
|
1122
|
+
|
1123
|
+
ret = props.addparam('Floor', 'Level', self._floor_level, Type_Param.Float, 'Floor level')
|
1124
|
+
ret = props.addparam('Floor', 'Color', self._floor_color, Type_Param.Color, 'Floor color')
|
1125
|
+
|
1126
|
+
ret = props.addparam('Infos', 'Grid', self._show_grid, Type_Param.Logical, 'Show grid')
|
1127
|
+
ret = props.addparam('Infos', 'Axis', self._show_axis, Type_Param.Logical, 'Show axis')
|
1128
|
+
ret = props.addparam('Infos', 'values', self._show_info, Type_Param.Logical, 'Show info')
|
1129
|
+
|
1130
|
+
ret = props.addparam('Points', 'Size', self._point_size, Type_Param.Float, 'Point size')
|
1131
|
+
|
1132
|
+
codes_sel = ''
|
1133
|
+
for curcode in self._select_only_codes:
|
1134
|
+
codes_sel += str(curcode) + ','
|
1135
|
+
ret = props.addparam('Selection', 'Codes', codes_sel, Type_Param.String, 'Codes to select')
|
1136
|
+
|
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
|
+
props.Populate()
|
1141
|
+
|
1142
|
+
updatebutton = wx.Button(props, label=_('Update from viewer'))
|
1143
|
+
props.sizerbut.Add(updatebutton,1,wx.EXPAND)
|
1144
|
+
props.Bind(wx.EVT_BUTTON, self._fill_props, updatebutton)
|
1145
|
+
|
1146
|
+
getselection = wx.Button(props, label=_('Get selection'))
|
1147
|
+
props.sizerbut.Add(getselection,1,wx.EXPAND)
|
1148
|
+
props.Bind(wx.EVT_BUTTON, self._get_selection, getselection)
|
1149
|
+
|
1150
|
+
props.Layout()
|
1151
|
+
|
1152
|
+
def _get_selection(self, event):
|
1153
|
+
|
1154
|
+
if self.viewer is None:
|
1155
|
+
logging.warning(_('No viewer'))
|
1156
|
+
return
|
1157
|
+
|
1158
|
+
xyz = self.xyz_selected
|
1159
|
+
|
1160
|
+
if xyz.shape[0]==0:
|
1161
|
+
logging.warning(_('No points selected'))
|
1162
|
+
return
|
1163
|
+
|
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)
|
1167
|
+
|
1168
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
1169
|
+
sizer.Add(self._xls, 1, wx.EXPAND)
|
1170
|
+
|
1171
|
+
nbclass = len(self.classification.classification)
|
1172
|
+
self._xls.CreateGrid(xyz.shape[0] + nbclass +1, 4)
|
1173
|
+
|
1174
|
+
self._xlsFrame.SetSizer(sizer)
|
1175
|
+
self._xlsFrame.Layout()
|
1176
|
+
self._xlsFrame.Show()
|
1177
|
+
|
1178
|
+
else:
|
1179
|
+
self._xls.ClearGrid()
|
1180
|
+
if self._xls.NumberRows < xyz.shape[0]:
|
1181
|
+
self._xls.AppendRows(xyz.shape[0]-self._xls.NumberRows)
|
1182
|
+
|
1183
|
+
self._xls.SetColLabelValue(0, 'X')
|
1184
|
+
self._xls.SetColLabelValue(1, 'Y')
|
1185
|
+
self._xls.SetColLabelValue(2, 'Z')
|
1186
|
+
self._xls.SetColLabelValue(3, 'Code')
|
1187
|
+
|
1188
|
+
codes = self.code_selected
|
1189
|
+
|
1190
|
+
for i in range(xyz.shape[0]):
|
1191
|
+
self._xls.SetCellValue(i,0,str(xyz[i,0]))
|
1192
|
+
self._xls.SetCellValue(i,1,str(xyz[i,1]))
|
1193
|
+
self._xls.SetCellValue(i,2,str(xyz[i,2]))
|
1194
|
+
self._xls.SetCellValue(i,3,str(codes[i]))
|
1195
|
+
|
1196
|
+
# Copy classification under the values
|
1197
|
+
for i, (key, val) in enumerate(self.classification.classification.items()):
|
1198
|
+
self._xls.SetCellValue(xyz.shape[0]+i+1,0,str(key))
|
1199
|
+
self._xls.SetCellValue(xyz.shape[0]+i+1,1,val[0])
|
1200
|
+
self._xls.SetCellValue(xyz.shape[0]+i+1,2,val[1])
|
1201
|
+
self._xls.SetCellValue(xyz.shape[0]+i+1,3,str(key))
|
1202
|
+
|
1203
|
+
self._xlsFrame.Show()
|
1204
|
+
|
1205
|
+
# Mettre la fenêtre au premier plan et centrée
|
1206
|
+
self._xlsFrame.Raise()
|
1207
|
+
self._xlsFrame.Center()
|
1208
|
+
|
1209
|
+
def _update_props(self):
|
1210
|
+
|
1211
|
+
if self._myprops is None:
|
1212
|
+
return
|
1213
|
+
|
1214
|
+
if self.viewer is None:
|
1215
|
+
return
|
1216
|
+
|
1217
|
+
props = self._myprops
|
1218
|
+
|
1219
|
+
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')])
|
1221
|
+
|
1222
|
+
color = np.asarray(props[('Background', 'Color')])
|
1223
|
+
self.bg_color(color / 255.)
|
1224
|
+
color = np.asarray(props[('Background', 'Top Color')])
|
1225
|
+
self.bg_color_top(color / 255.)
|
1226
|
+
color = np.asarray(props[('Background', 'Bottom Color')])
|
1227
|
+
self.bg_color_bottom(color / 255.)
|
1228
|
+
|
1229
|
+
self.floor_level(props[('Floor', 'Level')])
|
1230
|
+
color = np.asarray(props[('Floor', 'Color')])
|
1231
|
+
self.floor_color(color / 255.)
|
1232
|
+
|
1233
|
+
self.show_grid(props[('Infos', 'Grid')])
|
1234
|
+
self.show_axis(props[('Infos', 'Axis')])
|
1235
|
+
self.show_info(props[('Infos', 'values')])
|
1236
|
+
|
1237
|
+
self.point_size = props[('Points', 'Size')]
|
1238
|
+
|
1239
|
+
codes_sel = props[('Selection', 'Codes')]
|
1240
|
+
codes_sel = codes_sel.split(',')
|
1241
|
+
try:
|
1242
|
+
self._select_only_codes = list(set([int(curcode) for curcode in codes_sel]))
|
1243
|
+
except Exception as e:
|
1244
|
+
logging.error(e)
|
1245
|
+
logging.warning(_('Nullify selection filter - Check your input'))
|
1246
|
+
self._select_only_codes = []
|
1247
|
+
|
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
|
+
def _fill_props(self, full:bool = False):
|
1258
|
+
|
1259
|
+
if self._myprops is None:
|
1260
|
+
return
|
1261
|
+
|
1262
|
+
props = self._myprops
|
1263
|
+
|
1264
|
+
props[('Look at', 'X')] = self.lookat[0]
|
1265
|
+
props[('Look at', 'Y')] = self.lookat[1]
|
1266
|
+
props[('Look at', 'Z')] = self.lookat[2]
|
1267
|
+
|
1268
|
+
props[('Camera', 'X')] = self.eye[0]
|
1269
|
+
props[('Camera', 'Y')] = self.eye[1]
|
1270
|
+
props[('Camera', 'Z')] = self.eye[2]
|
1271
|
+
|
1272
|
+
props[('Relative Position', 'Phi')] = self.phi
|
1273
|
+
props[('Relative Position', 'Theta')] = self.theta
|
1274
|
+
props[('Relative Position', 'R')] = self.r
|
1275
|
+
|
1276
|
+
if full:
|
1277
|
+
|
1278
|
+
props[('Background', 'Color')] = self._bg_color
|
1279
|
+
props[('Background', 'Top Color')] = self._bg_color_top
|
1280
|
+
props[('Background', 'Bottom Color')] = self._bg_color_bottom
|
1281
|
+
|
1282
|
+
props[('Floor', 'Level')] = self._floor_level
|
1283
|
+
props[('Floor', 'Color')] = self._floor_color
|
1284
|
+
|
1285
|
+
props[('Infos', 'Grid')] = self._show_grid
|
1286
|
+
props[('Infos', 'Axis')] = self._show_axis
|
1287
|
+
props[('Infos', 'values')] = self._show_info
|
1288
|
+
|
1289
|
+
props[('Points', 'Size')] = self._point_size
|
1290
|
+
|
1291
|
+
# props[('Auto Update', 'Interval')] = self._auto_update_interval
|
1292
|
+
# props[('Auto Update', 'Active')] = self._auto_update_properties
|
1293
|
+
|
1294
|
+
props.Populate()
|
1295
|
+
|
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
|
+
def show_properties(self):
|
1315
|
+
|
1316
|
+
if self.viewer is None:
|
1317
|
+
return
|
1318
|
+
|
1319
|
+
self._create_props()
|
1320
|
+
self._myprops.Show()
|
1321
|
+
|
681
1322
|
def find_pointsXYZ(xyz:np.ndarray, bounds:Union[tuple[tuple[float,float],tuple[float,float]], list[list[float, float],list[float, float]]]) -> np.ndarray:
|
682
1323
|
|
683
1324
|
xb=bounds[0]
|
@@ -1092,7 +1733,7 @@ def myviewer(las:Union[np.ndarray, list[laspy.LasData], laspy.LasData], which_co
|
|
1092
1733
|
|
1093
1734
|
colors = get_colors(las, which_colors, fname=fname, palette_classif= palette_classif)
|
1094
1735
|
|
1095
|
-
v=viewer(xyz,colors)
|
1736
|
+
v = viewer(xyz,colors)
|
1096
1737
|
v.set(point_size=.05)
|
1097
1738
|
return v
|
1098
1739
|
|