8000 simplified and optimized larray editor internals (issue #93) · gdementen/larray-editor@4bdee96 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4bdee96

Browse files
committed
simplified and optimized larray editor internals (issue larray-project#93)
* one of the goal was to make switching from one array to another as fast as possible by cutting down on the repeated calls (to various set_data and datamodel.reset()) * tightened what is accepted by each internal class. There is only one expected type for the data. External facing classes should still accept the same objects. * all internal classes which "hold" data are created without any data in __init__ but require a set_data before they can function. * data_model.reset_minmax needs to be called explicitly. * data_model.reset() needs to be called explicitly when "everything is done"
1 parent 21ef4c8 commit 4bdee96

File tree

4 files changed

+117
-115
lines changed

4 files changed

+117
-115
lines changed

larray_editor/arrayadapter.py

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,24 @@
22

33
import numpy as np
44
import larray as la
5-
from larray_editor.utils import Product, _LazyNone, _LazyDimLabels
5+
6+
from larray_editor.utils import Product, _LazyDimLabels
67

78

89
class LArrayDataAdapter(object):
9-
def __init__(self, axes_model, xlabels_model, ylabels_model, data_model,
10-
data=None, changes=None, current_filter=None, bg_gradient=None, bg_value=None):
10+
def __init__(self, axes_model, xlabels_model, ylabels_model, data_model):
1111
# set models
1212
self.axes_model = axes_model
1313
self.xlabels_model = xlabels_model
1414
self.ylabels_model = ylabels_model
1515
self.data_model = data_model
16-
# set current filter
17-
if current_filter is None:
18-
current_filter = {}
19-
assert isinstance(current_filter, dict)
20-
self.current_filter = current_filter
21-
# set changes
22-
if changes is None:
23-
changes = {}
24-
self.set_changes(changes)
25-
# set data
26-
if data is None:
27-
data = np.empty((0, 0), dtype=np.int8)
28-
self.set_data(data, bg_value, current_filter)
29-
30-
def set_changes(self, changes=None):
31-
assert isinstance(changes, dict)
32-
self.changes = changes
16+
17+
# these are not valid values, but should overwritten by set_data which must be called before the adapter is used
18+
self.current_filter = None
19+
self.changes = None
20+
self.la_data = None
21+
self.bg_value = None
22+
self.filtered_data = None
3323

3424
def get_axes_names(self):
3525
return self.filtered_data.axes.display_names
@@ -39,7 +29,7 @@ def get_axes(self):
3929
# test self.filtered_data.size == 0 is required in case an instance built as LArray([]) is passed
4030
# test len(axes) == 0 is required when a user filters until to get a scalar
4131
if self.filtered_data.size == 0 or len(axes) == 0:
42-
return None
32+
return [[]]
4333
else:
4434
axes_names = axes.display_names
4535
if len(axes_names) >= 2:
@@ -49,7 +39,7 @@ def get_axes(self):
4939
def get_xlabels(self):
5040
axes = self.filtered_data.axes
5141
if self.filtered_data.size == 0 or len(axes) == 0:
52-
return None
42+
return [[]]
5343
else:
5444
# this is a lazy equivalent of:
5545
# return [(label,) for label in axes.labels[-1]]
@@ -58,7 +48,7 @@ def get_xlabels(self):
5848
def get_ylabels(self):
5949
axes = self.filtered_data.axes
6050
if self.filtered_data.size == 0 or len(axes) == 0:
61-
return None
51+
return [[]]
6252
elif len(axes) == 1:
6353
return [['']]
6454
else:
@@ -101,34 +91,37 @@ def get_bg_value_2D(self, shape_2D):
10191
# XXX: or create two methods?:
10292
# - set_data (which reset the current filter)
10393
# - update_data (which sets new data but keeps current filter unchanged)
104-
def set_data(self, data, bg_value=None, current_filter=None):
105-
if data is None:
106-
data = la.LArray([])
107-
if current_filter is None:
108-
self.current_filter = {}
94+
def set_data(self, data, bg_value=None):
95+
assert isinstance(data, la.LArray)
96+
self.current_filter = {}
10997
self.changes = {}
11098
self.la_data = la.aslarray(data)
11199
self.bg_value = la.aslarray(bg_value) if bg_value is not None else None
112-
self.update_filtered_data(current_filter, reset_minmax=True)
100+
self.update_filtered_data()
101+
self.data_model.reset_minmax()
102+
self.data_model.reset()
113103

114-
def update_filtered_data(self, current_filter=None, reset_minmax=False):
115-
if current_filter is not None:
116-
assert isinstance(current_filter, dict)
117-
self.current_filter = current_filter
104+
def update_filtered_data(self):
105+
assert isinstance(self.la_data, la.LArray)
118106
self.filtered_data = self.la_data[self.current_filter]
107+
119108
if np.isscalar(self.filtered_data):
120109
self.filtered_data = la.aslarray(self.filtered_data)
110+
121111
axes = self.get_axes()
122112
xlabels = self.get_xlabels()
123113
ylabels = self.get_ylabels()
124114
data_2D = self.get_2D_data()
125115
changes_2D = self.get_changes_2D()
126116
bg_value_2D = self.get_bg_value_2D(data_2D.shape)
117+
127118
self.axes_model.set_data(axes)
128119
self.xlabels_model.set_data(xlabels)
129120
self.ylabels_model.set_data(ylabels)
130-
self.data_model.set_data(data_2D, changes_2D, reset_minmax=reset_minmax)
131-
self.data_model.set_bg_value(bg_value_2D)
121+
# using the protected version of the method to avoid calling reset() several times
122+
self.data_model._set_data(data_2D)
123+
self.data_model._set_changes(changes_2D)
124+
self.data_model._set_bg_value(bg_value_2D)
132125

133126
def get_data(self):
134127
return self.la_data
@@ -223,6 +216,7 @@ def change_filter(self, axis, indices):
223216
else:
224217
self.current_filter[axis_id] = axis.labels[indices]
225218
self.update_filtered_data()
219+
self.data_model.reset()
226220

227221
def clear_changes(self):
228222
self.changes.clear()
@@ -238,6 +232,7 @@ def accept_changes(self):
238232
self.la_data.i[axes.translate_full_key(k)] = v
239233
# update models
240234
self.update_filtered_data()
235+
self.data_model.reset()
241236
# clear changes
242237
self.clear_changes()
243238
# return modified data

larray_editor/arraymodel.py

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ class AbstractArrayModel(QAbstractTableModel):
2020
----------
2121
parent : QWidget, optional
2222
Parent Widget.
23-
data : array-like, optional
24-
Input data.
2523
readonly : bool, optional
2624
If True, data cannot be changed. False by default.
2725
font : QFont, optional
@@ -30,7 +28,7 @@ class AbstractArrayModel(QAbstractTableModel):
3028
ROWS_TO_LOAD = 500
3129
COLS_TO_LOAD = 40
3230

33-
def __init__(self, parent=None, data=None, readonly=False, font=None):
31+
def __init__(self, parent=None, readonly=False, font=None):
3432
QAbstractTableModel.__init__(self)
3533

3634
self.dialog = parent
@@ -45,13 +43,12 @@ def __init__(self, parent=None, data=None, readonly=False, font=None):
4543
self.cols_loaded = 0
4644
self.total_rows = 0
4745
self.total_cols = 0
48-
self.set_data(data)
4946

50-
def _set_data(self, data, changes=None):
47+
def _set_data(self, data):
5148
raise NotImplementedError()
5249

53-
def set_data(self, data, changes=None, **kwargs):
54-
self._set_data(data, changes, **kwargs)
50+
def set_data(self, data):
51+
self._set_data(data)
5552
self.reset()
5653

5754
def rowCount(self, parent=QModelIndex()):
@@ -119,20 +116,16 @@ class LabelsArrayModel(AbstractArrayModel):
119116
----------
120117
parent : QWidget, optional
121118
Parent Widget.
122-
data : nested list or tuple, optional
123-
Input data.
124119
readonly : bool, optional
125120
If True, data cannot be changed. False by default.
126121
font : QFont, optional
127122
Font. Default is `Calibri` with size 11.
128123
"""
129-
def __init__(self, parent=None, data=None, readonly=False, font=None):
130-
AbstractArrayModel.__init__(self, parent, data, readonly, font)
124+
def __init__(self, parent=None, readonly=False, font=None):
125+
AbstractArrayModel.__init__(self, parent, readonly, font)
131126
self.font.setBold(True)
132127

133-
def _set_data(self, data, changes=None):
134-
if data is None:
135-
data = [[]]
128+
def _set_data(self, data):
136129
# TODO: use sequence instead
137130
if not isinstance(data, (list, tuple, Product)):
138131
QMessageBox.critical(self.dialog, "Error", "Expected list, tuple or Product")
@@ -188,42 +181,44 @@ class DataArrayModel(AbstractArrayModel):
188181
189182
Parameters
190183
----------
191-
data : Numpy ndarray, optional
192-
Input 2D array.
193-
format : str, optional
194-
Indicates how data are represented in cells.
195-
By default, they are represented as floats with 3 decimal points.
184+
parent : QWidget, optional
185+
Parent Widget.
196186
readonly : bool, optional
197187
If True, data cannot be changed. False by default.
188+
format : str, optional
189+
Indicates how data is represented in cells.
190+
By default, they are represented as floats with 3 decimal points.
198191
font : QFont, optional
199192
Font. Default is `Calibri` with size 11.
200-
parent : QWidget, optional
201-
Parent Widget.
202193
bg_gradient : LinearGradient, optional
203194
Background color gradient
204195
bg_value : Numpy ndarray, optional
205196
Background color value. Must have the shape as data
206-
minvalue : scalar
197+
minvalue : scalar, optional
207198
Minimum value allowed.
208-
maxvalue : scalar
199+
maxvalue : scalar, optional
209200
Maximum value allowed.
210201
"""
211202

212203
ROWS_TO_LOAD = 500
213204
COLS_TO_LOAD = 40
214205

215-
def __init__(self, parent=None, data=None, readonly=False, format="%.3f", font=None,
216-
bg_gradient=None, bg_value=None, minvalue=None, maxvalue=None):
217-
AbstractArrayModel.__init__(self, parent, data, readonly, font)
206+
def __init__(self, parent=None, readonly=False, format="%.3f", font=None, minvalue=None, maxvalue=None):
207+
AbstractArrayModel.__init__(self, parent, readonly, font)
218208
self._format = format
219209

220210
self.minvalue = minvalue
221211
self.maxvalue = maxvalue
222-
self._set_data(data)
223-
self._set_bg_gradient(bg_gradient)
224-
self._set_bg_value(bg_value)
225-
# XXX: unsure this is necessary at all in __init__
226-
self.reset()
212+
213+
self.changes = None
214+
self.color_func = None
215+
216+
self.vmin = None
217+
self.vmax = None
218+
self.bgcolor_possible = False
219+
220+
self.bg_value = None
221+
self.bg_gradient = None
227222

228223
def get_format(self):
229224
"""Return current format"""
@@ -234,16 +229,12 @@ def get_data(self):
234229
"""Return data"""
235230
return self._data
236231

237-
def _set_data(self, data, changes=None, reset_minmax=True):
238-
if changes is None:
239-
changes = {}
232+
def _set_changes(self, changes):
240233
self.changes = changes
241234

235+
def _set_data(self, data):
242236
# TODO: check that data respects minvalue/maxvalue
243-
if data is None:
244-
data = np.empty((0, 0), dtype=np.int8)
245-
if not (isinstance(data, np.ndarray) and data.ndim == 2):
246-
QMessageBox.critical(self.dialog, "Error", "Expect Numpy ndarray of 2 dimensions")
237+
assert isinstance(data, np.ndarray) and data.ndim == 2
247238
self._data = data
248239

249240
dtype = data.dtype
@@ -262,8 +253,6 @@ def _set_data(self, data, changes=None, reset_minmax=True):
262253
self.color_func = None
263254
# --------------------------------------
264255
self.total_rows, self.total_cols = self._data.shape
265-
if reset_minmax:
266-
self.reset_minmax()
267256
self._compute_rows_cols_loaded()
268257

269258
def reset_minmax(self):
@@ -283,9 +272,12 @@ def reset_minmax(self):
283272

284273
def set_format(self, format):
285274
"""Change display format"""
286-
self._format = format
275+
self._set_format(format)
287276
self.reset()
288277

278+
def _set_format(self, format):
279+
self._format = format
280+
289281
def set_bg_gradient(self, bg_gradient):
290282
self._set_bg_gradient(bg_gradient)
291283
self.reset()
@@ -459,17 +451,22 @@ def set_values(self, left, top, right, bottom, values):
459451

460452
# Update vmin/vmax if necessary
461453
if self.vmin is not None and self.vmax is not None:
454+
# FIXME: -inf/+inf and non-number values should be ignored here too
462455
colorval = self.color_func(values) if self.color_func is not None else values
463456
old_colorval = self.color_func(oldvalues) if self.color_func is not None else oldvalues
457+
# we need to lower vmax or increase vmin
464458
if np.any(((old_colorval == self.vmax) & (colorval < self.vmax)) |
465459
((old_colorval == self.vmin) & (colorval > self.vmin))):
466460
self.reset_minmax()
461+
self.reset()
467462
# this is faster, when the condition is False (which should be most of the cases) than computing
468463
# subset_max and checking if subset_max > self.vmax
469464
if np.any(colorval > self.vmax):
470465
self.vmax = float(np.nanmax(colorval))
466+
self.reset()
471467
if np.any(colorval < self.vmin):
472468
self.vmin = float(np.nanmin(colorval))
469+
self.reset()
473470

474471
top_left = self.index(left, top)
475472
# -1 because Qt index end bounds are inclusive

0 commit comments

Comments
 (0)
0