Skip to content

Commit 07187cb

Browse files
authored
Merge pull request #191 from ebranlard/dev
Version 0.5 - Merging dev to main
2 parents 4140c24 + 3a29687 commit 07187cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+4648
-1880
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-22.04
1313
strategy:
1414
matrix:
15-
python-version: [3.8, 3.9, 3.11]
15+
python-version: [3.8, 3.9, 3.11, 3.12]
1616

1717
steps:
1818
# --- Install steps
@@ -27,7 +27,7 @@ jobs:
2727
run: |
2828
git fetch --unshallow > /dev/null
2929
git fetch --tags --force > /dev/null
30-
export CURRENT_TAG="v0.4"
30+
export CURRENT_TAG="v0.5"
3131
export CURRENT_DEV_TAG="$CURRENT_TAG-dev"
3232
# BRANCH FAILS
3333
export BRANCH1=`git rev-parse --abbrev-ref HEAD`

_tools/travis_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
openpyxl
22
numpy
33
pandas
4+
xarray
45
pyarrow # for parquet files
56
matplotlib
67
chardet

installer.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[Application]
22
name=pyDatView
3-
version=0.4
3+
version=0.5
44
entry_point=pydatview:show_sys_args
55
icon=ressources/pyDatView.ico
66

@@ -35,6 +35,7 @@ pypi_wheels =
3535
Pillow==9.1.1
3636
packaging==21.2
3737
fatpack==0.7.3
38+
xarray==2023.2.0
3839

3940
# numpy==1.19.3
4041
# wxPython==4.0.3

pydatview/Fields2D.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
TODO come up with some decent sepcs. Potentially use pandas or xarray
3+
4+
"""
5+
import numpy as np
6+
7+
def extract2Dfields(fo, force=False, **kwargs):
8+
if not hasattr(fo, 'fields2D_tmp') or force:
9+
fo.fields2D_tmp = None
10+
#print('[INFO] Attempting to extract 2D field for file {}'.format(fo.filename))
11+
if not hasattr(fo, 'to2DFields'):
12+
print('[WARN] type {} does not have a `to2DFields` method'.format(type(fo)))
13+
return None
14+
try:
15+
fields = fo.to2DFields(**kwargs)
16+
except:
17+
print('[FAIL] Attempting to extract 2D field for file {}'.format(fo.filename))
18+
return None
19+
if fields is None:
20+
print('[WARN] type {} has a `to2DFields` method but returned None'.format(type(fo)))
21+
return None
22+
# Convert to pydatview datatype for 2d fields
23+
fo.fields2D_tmp = Fields2D(fields)
24+
fo.fields2D_tmp.keys()
25+
print('[ OK ] 2D field computed successfully')
26+
else:
27+
print('[INFO] 2D field already computed for file {}'.format(fo.filename))
28+
if not isinstance(fo.fields2D_tmp, Fields2D):
29+
raise Exception('ImplementationError')
30+
31+
return fo.fields2D_tmp
32+
33+
34+
class Fields2D():
35+
"""
36+
Fields2D is a list of xarray, readonly
37+
"""
38+
def __init__(self, ds=None):
39+
if ds is None:
40+
ds = []
41+
self.ds = ds
42+
self._keys = None
43+
44+
def __repr__(self):
45+
s='<{} object>:\n'.format(type(self).__name__)
46+
s+='|Main attributes:\n'
47+
s+='| - ds: {}\n'.format(self.ds)
48+
s+='| - _keys: {}\n'.format(self._keys)
49+
return s
50+
51+
def keys(self):
52+
if self._keys is not None:
53+
return self._keys
54+
keys =[]
55+
variables = self.ds.variables
56+
# Filter variables based on dimensions (r, t)
57+
dims = np.unique(np.array([self.ds[var].dims for var in variables], dtype=object))
58+
dims2d = [d for d in dims if len(d)==2]
59+
for D in dims2d:
60+
for var in variables:
61+
if self.ds[var].dims==(D[0],D[1]):
62+
keys.append(var)
63+
self._keys = keys
64+
return keys
65+
66+
def loc(self, svar):
67+
try:
68+
i1, i2 = self.ds[svar].dims
69+
except:
70+
raise IndexError('Variable {} not found in field'.format(svar))
71+
sx = i1
72+
sy = i2
73+
if 'unit' in self.ds[i1].attrs.keys():
74+
sx += ' ['+ self.ds[i1].attrs['unit'] + ']'
75+
if 'unit' in self.ds[i2].attrs.keys():
76+
sy += ' ['+ self.ds[i2].attrs['unit'] + ']'
77+
fieldname = svar
78+
if 'unit' in self.ds[svar].attrs.keys():
79+
fieldname += ' ['+ self.ds[svar].attrs['unit'] + ']'
80+
return {'M': self.ds[svar].values, 'x':self.ds[i1].values, 'y':self.ds[i2].values, 'sx':sx, 'sy':sy, 'fieldname':fieldname}
81+
82+
def iloc(self, i):
83+
var = self._keys[i]
84+
return self.loc(var)
85+

pydatview/GUICommon.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ def __init__(self, parent, title, message):
6363
text = wx.TextCtrl(self, style=wx.TE_READONLY|wx.BORDER_NONE|wx.TE_MULTILINE|wx.TE_AUTO_URL)
6464
text.SetValue(message)
6565
text.SetBackgroundColour(wx.SystemSettings.GetColour(4))
66+
# box_sizer = wx.BoxSizer(wx.VERTICAL)
67+
# box_sizer.Add(text)
68+
# self.SetSizerAndFit(box_sizer)
6669
self.ShowModal()
6770
self.Destroy()
6871
MessageBox(parent, 'About', message)

pydatview/GUIFields1D.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
"""
3+
import wx
4+
from pydatview.GUIPlotPanel import PlotPanel
5+
from pydatview.GUIInfoPanel import InfoPanel
6+
from pydatview.GUISelectionPanel import SelectionPanel, SEL_MODES_ID
7+
# from pydatview.GUISelectionPanel import SelectionPanel,SEL_MODES,SEL_MODES_ID
8+
# from pydatview.GUISelectionPanel import ColumnPopup,TablePopup
9+
# from pydatview.GUIPipelinePanel import PipelinePanel
10+
# from pydatview.GUIToolBox import GetKeyString, TBAddTool
11+
# from pydatview.Tables import TableList, Table
12+
13+
14+
SIDE_COL = [160,160,300,420,530]
15+
SIDE_COL_LARGE = [200,200,360,480,600]
16+
BOT_PANL =85
17+
18+
class Fields1DPanel(wx.SplitterWindow): # TODO Panel
19+
20+
def __init__(self, parent, mainframe):
21+
# Superclass constructor
22+
super(Fields1DPanel, self).__init__(parent)
23+
# Data
24+
self.parent = parent
25+
self.mainframe = mainframe
26+
27+
self.vSplitter = self # Backward compatibility
28+
29+
# --- Create a selPanel, plotPanel and infoPanel
30+
mode = SEL_MODES_ID[mainframe.comboMode.GetSelection()]
31+
self.selPanel = SelectionPanel(self.vSplitter, mainframe.tabList, mode=mode, mainframe=mainframe)
32+
self.tSplitter = wx.SplitterWindow(self.vSplitter)
33+
#self.tSplitter.SetMinimumPaneSize(20)
34+
self.infoPanel = InfoPanel(self.tSplitter, data=mainframe.data['infoPanel'])
35+
self.plotPanel = PlotPanel(self.tSplitter, self.selPanel, infoPanel=self.infoPanel, pipeLike=mainframe.pipePanel, data=mainframe.data['plotPanel'])
36+
self.livePlotFreezeUnfreeze() # Dont enable panels if livePlot is not allowed
37+
self.tSplitter.SetSashGravity(0.9)
38+
self.tSplitter.SplitHorizontally(self.plotPanel, self.infoPanel)
39+
self.tSplitter.SetMinimumPaneSize(BOT_PANL)
40+
self.tSplitter.SetSashGravity(1)
41+
self.tSplitter.SetSashPosition(400)
42+
43+
self.vSplitter.SplitVertically(self.selPanel, self.tSplitter)
44+
self.vSplitter.SetMinimumPaneSize(SIDE_COL[0])
45+
self.tSplitter.SetSashPosition(SIDE_COL[0])
46+
47+
48+
# --- Bind
49+
# The selPanel does the binding, but the callback is stored here because it involves plotPanel... TODO, rethink it
50+
#self.selPanel.bindColSelectionChange(self.onColSelectionChangeCallBack)
51+
self.selPanel.setTabSelectionChangeCallback(mainframe.onTabSelectionChangeTrigger)
52+
self.selPanel.setRedrawCallback(mainframe.redrawCallback)
53+
self.selPanel.setUpdateLayoutCallback(mainframe.mainFrameUpdateLayout)
54+
self.plotPanel.setAddTablesCallback(mainframe.load_dfs)
55+
56+
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, mainframe.onSashChangeMain, self.vSplitter)
57+
58+
# --- Mainframe backward compatibility
59+
mainframe.selPanel = self.selPanel
60+
mainframe.plotPanel = self.plotPanel
61+
mainframe.infoPanel = self.infoPanel
62+
63+
64+
def updateSashLayout(self, event=None):
65+
# try:
66+
nWind = self.selPanel.splitter.nWindows
67+
if self.Size[0]<=800:
68+
sash=SIDE_COL[nWind]
69+
else:
70+
sash=SIDE_COL_LARGE[nWind]
71+
self.resizeSideColumn(sash)
72+
# except:
73+
# print('[Fail] An error occured in mainFrameUpdateLayout')
74+
75+
76+
# --- Side column
77+
def resizeSideColumn(self,width):
78+
# To force the replot we do an epic unsplit/split...
79+
#self.vSplitter.Unsplit()
80+
#self.vSplitter.SplitVertically(self.selPanel, self.tSplitter)
81+
self.vSplitter.SetMinimumPaneSize(width)
82+
self.vSplitter.SetSashPosition(width)
83+
#self.selPanel.splitter.setEquiSash()
84+
85+
def livePlotFreezeUnfreeze(self):
86+
pass
87+
#if self.cbLivePlot.IsChecked():
88+
# if hasattr(self,'plotPanel'):
89+
# #print('[INFO] Enabling live plot')
90+
# #self.plotPanel.Enable(True)
91+
# self.infoPanel.Enable(True)
92+
#else:
93+
# if hasattr(self,'plotPanel'):
94+
# #print('[INFO] Disabling live plot')
95+
# #self.plotPanel.Enable(False)
96+
# self.infoPanel.Enable(False)
97+

pydatview/GUIFields2D.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""
2+
"""
3+
import os
4+
import numpy as np
5+
import wx
6+
from wx.lib.splitter import MultiSplitterWindow
7+
# Local
8+
from pydatview.common import ellude_common
9+
from pydatview.common import CHAR
10+
from pydatview.GUICommon import getMonoFont
11+
from pydatview.Fields2D import extract2Dfields
12+
from pydatview.GUIPlot2DPanel import Plot2DPanel
13+
14+
# --------------------------------------------------------------------------------}
15+
# --- Fields 2D Panel
16+
# --------------------------------------------------------------------------------{
17+
class Fields2DPanel(wx.Panel):
18+
def __init__(self, parent, mainframe):
19+
wx.Panel.__init__(self, parent)
20+
# Data
21+
self.parent = parent
22+
self.mainframe = mainframe
23+
self.fileobjects = None
24+
25+
multi_split = MultiSplitterWindow(self)
26+
self.filesPanel = wx.Panel(multi_split)
27+
self.fieldsPanel = wx.Panel(multi_split)
28+
self.canvasPanel = Plot2DPanel(multi_split)
29+
30+
# GUI
31+
self.btExtractFields = wx.Button(self.filesPanel, label=CHAR['compute']+' '+"Extract 2D fields for all", style=wx.BU_EXACTFIT)
32+
self.textArgs = wx.TextCtrl(self.filesPanel, wx.ID_ANY, '', style = wx.TE_PROCESS_ENTER)
33+
self.lbFiles = wx.ListBox(self.filesPanel, style=wx.LB_EXTENDED)
34+
self.lbFields = wx.ListBox(self.fieldsPanel, style=wx.LB_EXTENDED)
35+
self.textArgs.SetValue('DeltaAzi=10') # TODO
36+
self.lbFiles.SetFont(getMonoFont(self))
37+
self.lbFields.SetFont(getMonoFont(self))
38+
self.textArgs.SetFont(getMonoFont(self))
39+
40+
# Layout
41+
sizer_files = wx.BoxSizer(wx.VERTICAL)
42+
sizer_files.Add(self.textArgs, 0, wx.EXPAND | wx.ALL, 1)
43+
sizer_files.Add(self.btExtractFields, 0, wx.EXPAND | wx.ALL, 1)
44+
sizer_files.Add(self.lbFiles, 1, wx.EXPAND | wx.ALL, 1)
45+
self.filesPanel.SetSizer(sizer_files)
46+
47+
sizer_fields = wx.BoxSizer(wx.VERTICAL)
48+
sizer_fields.Add(self.lbFields, 1, wx.EXPAND | wx.ALL, 1)
49+
self.fieldsPanel.SetSizer(sizer_fields)
50+
51+
multi_split.AppendWindow(self.filesPanel, 200)
52+
multi_split.AppendWindow(self.fieldsPanel, 200)
53+
multi_split.AppendWindow(self.canvasPanel)
54+
55+
sizer = wx.BoxSizer(wx.VERTICAL)
56+
sizer.Add(multi_split, 1, wx.EXPAND)
57+
self.SetSizer(sizer)
58+
59+
# Bind
60+
self.lbFiles.Bind(wx.EVT_LISTBOX, self.on_file_selected)
61+
self.lbFields.Bind(wx.EVT_LISTBOX, self.on_2d_field_selected)
62+
self.btExtractFields.Bind(wx.EVT_BUTTON, self.onExtract)
63+
#self.textArgs.Bind(wx.EVT_TEXT_ENTER, self.onParamChangeEnter)
64+
65+
def cleanGUI(self, event=None):
66+
self.deselect()
67+
self.lbFiles.Clear()
68+
self.lbFields.Clear()
69+
self.canvasPanel.clean_plot()
70+
71+
def deselect(self, event=None):
72+
[self.lbFiles.Deselect(i) for i in self.lbFiles.GetSelections()]
73+
[self.lbFields.Deselect(i) for i in self.lbFields.GetSelections()]
74+
75+
def updateFiles(self, filenames, fileobjects):
76+
self.fileobjects=fileobjects
77+
filenames = [os.path.abspath(f).replace('/','|').replace('\\','|') for f in filenames]
78+
filenames = ellude_common(filenames)
79+
self.lbFiles.Set(filenames)
80+
#self.lbFiles.SetSelection(0)
81+
#self.on_file_selected()
82+
83+
def getArgs(self):
84+
args = self.textArgs.GetValue().split(',')
85+
kwargs={}
86+
for arg in args:
87+
k, v = arg.split('=')
88+
kwargs[k.strip()] = v.strip()
89+
return kwargs
90+
91+
def onExtract(self, event=None):
92+
self.deselect()
93+
kwargs = self.getArgs()
94+
for fo in self.fileobjects:
95+
extract2Dfields(fo, force=True, **kwargs)
96+
97+
def on_file_selected(self, event=None):
98+
self.canvasPanel.clean_plot()
99+
100+
ISelF = self.lbFiles.GetSelections()
101+
kwargs = self.getArgs()
102+
# --- Compute 2d field if not done yet
103+
for iself in ISelF:
104+
file_object = self.fileobjects[iself]
105+
if not hasattr(file_object, 'fields2D_tmp'):
106+
# Computing fields here if not already done for this file
107+
fields = extract2Dfields(file_object, **kwargs)
108+
else:
109+
fields = file_object.fields2D_tmp
110+
111+
iself = ISelF[0]
112+
fieldListByFile=[]
113+
for iself in ISelF:
114+
fields = file_object.fields2D_tmp
115+
if fields is not None:
116+
fieldListByFile.append(fields.keys())
117+
else:
118+
print('[WARN] No 2D fields for this file')
119+
120+
# --- Get common columns
121+
if len(fieldListByFile)>0:
122+
commonCols = fieldListByFile[0]
123+
for i in np.arange(1,len(fieldListByFile)):
124+
commonCols = list( set(commonCols) & set( fieldListByFile[i]))
125+
# Respect order of first
126+
commonCols = [c for c in fieldListByFile[0] if c in commonCols]
127+
#commonCols.sort()
128+
self.lbFields.Set(commonCols)
129+
130+
# Trigger, we select the first field...
131+
if len(commonCols)>0:
132+
self.lbFields.SetSelection(0)
133+
self.on_2d_field_selected()
134+
else:
135+
print('[WARN] No 2D fields')
136+
137+
138+
def on_2d_field_selected(self, event=None):
139+
ISelF = self.lbFiles.GetSelections()
140+
self.canvasPanel.fields=[]
141+
for iself in ISelF :
142+
file_object = self.fileobjects[iself]
143+
ISelC = self.lbFields.GetSelections()
144+
for iselc in ISelC:
145+
sfield = self.lbFields.GetString(iselc)
146+
# Field is a dictionary with keys: M, x, y, sx, sy, fieldname
147+
field = file_object.fields2D_tmp.loc(sfield)
148+
self.canvasPanel.add_field(**field)
149+
self.canvasPanel.update_plot()
150+
151+
152+
153+

0 commit comments

Comments
 (0)