Skip to content

Commit

Permalink
Fix Numpy 2 compatiblity (#139)
Browse files Browse the repository at this point in the history
* conversion of timeseries changed to mixed np array creation

* omit dtype

* fix handling of arrays, only allow numerical data in puts for eval.

* precommit

* f

* fix

* test

* pre-commit

* pre-commit

* unpin numpy again and set printing behaviour to np-1.25

* line width 120 chars in year 2024 just seems to be right

* format

* add py313 to test matrix

* remove py313

* update dependencies

pint / py13 problems reported in hgrecco/pint#1969 (comment)

* fmt

* allow pre-commit fix

* update deps

---------

Co-authored-by: Çağtay Fabry <[email protected]>
  • Loading branch information
marscher and CagtayFabry authored Dec 5, 2024
1 parent 8203cdd commit 650cee2
Show file tree
Hide file tree
Showing 17 changed files with 103 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ci:
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
autofix_prs: false
autofix_prs: true
autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate'
autoupdate_schedule: monthly
skip: []
Expand Down
2 changes: 1 addition & 1 deletion .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ extend-exclude = [
]

# Same as Black.
line-length = 88
line-length = 120
indent-width = 4

# Assume Python 3.9
Expand Down
10 changes: 2 additions & 8 deletions demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
"\n",
"\n",
"def all_subclasses(cls):\n",
" return set(cls.__subclasses__()).union(\n",
" [s for c in cls.__subclasses__() for s in all_subclasses(c)]\n",
" )"
" return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])"
]
},
{
Expand All @@ -46,11 +44,7 @@
" for k, v in args.parameters.items():\n",
" if k == \"self\":\n",
" continue\n",
" if (\n",
" k != \"kwargs\"\n",
" and v.POSITIONAL_OR_KEYWORD\n",
" and v.default is inspect.Parameter.empty\n",
" ):\n",
" if k != \"kwargs\" and v.POSITIONAL_OR_KEYWORD and v.default is inspect.Parameter.empty:\n",
" _mock_args[k] = MagicMock()\n",
" print(\"mocked \", k, v) # noqa: T201\n",
" try:\n",
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ dependencies = [
"ipywidgets",
"k3d>=2.12",
"matplotlib<3.9",
"numpy<2",
"numpy",
"pint",
"tqdm",
"weldx>=0.6",
]
Expand Down
49 changes: 35 additions & 14 deletions weldx_widgets/generic.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Generic widgets."""

import ast
import base64
import contextlib
import hashlib
import re
from functools import partial
from typing import Callable, Optional

import numpy as np
import pandas as pd
from ipyfilechooser import FileChooser
from IPython import get_ipython
from ipywidgets import HTML, Button, HBox, Label
Expand Down Expand Up @@ -90,18 +94,12 @@ class WidgetTimeSeries(WidgetMyVBox, WeldxImportExport):
"""Preliminary time series editing widget."""

# TODO: handle math-expr
def __init__(
self, base_unit, time_unit="s", base_data="0", time_data="0", title=""
):
def __init__(self, base_unit, time_unit="s", base_data="0", time_data="0", title=""):
layout_prefilled_text = copy_layout(textbox_layout)
layout_prefilled_text.width = "300px"

self.base_data = WidgetLabeledTextInput(
label_text="Input dimension", prefilled_text=base_data
)
self.time_data = WidgetLabeledTextInput(
label_text="Time steps", prefilled_text=time_data
)
self.base_data = WidgetLabeledTextInput(label_text="Input dimension", prefilled_text=base_data)
self.time_data = WidgetLabeledTextInput(label_text="Time steps", prefilled_text=time_data)
self.base_data.text.layout = layout_prefilled_text
self.time_data.text.layout = layout_prefilled_text

Expand All @@ -120,13 +118,21 @@ def to_tree(self) -> dict:
"""Get mapping of input fields."""
from weldx import Q_, TimeSeries

# TODO: eval - the root of evil!
base_data = self.convert_to_numpy_array(self.base_data.text_value)
time_data = self.convert_to_numpy_array(self.time_data.text_value)
ts = TimeSeries(
data=Q_(eval(self.base_data.text_value), units=self.base_unit.text_value),
time=Q_(eval(self.time_data.text_value), units=self.time_unit.text_value),
data=Q_(base_data, units=self.base_unit.text_value),
time=pd.TimedeltaIndex(time_data, unit=self.time_unit.text_value),
)
return {"timeseries": ts}

@staticmethod
def convert_to_numpy_array(input_str):
if not is_safe_nd_array(input_str):
raise RuntimeError(f"input_str '{input_str}' is not a safe array")
a = np.array(ast.literal_eval(input_str))
return a

def from_tree(self, tree: dict):
"""Read in data from given dict."""
ts: weldx.TimeSeries = tree["timeseries"]
Expand All @@ -135,11 +141,26 @@ def from_tree(self, tree: dict):
self.time_data.text_value = f"[{foo}]"
else:
self.time_data.text_value = ""

self.base_data.text_value = repr(list(ts.data.magnitude))
if np.__version__ > "2":
with np.printoptions(legacy="1.25"):
self.base_data.text_value = repr(list(ts.data.magnitude))
else:
self.base_data.text_value = repr(list(ts.data.magnitude))
self.base_unit.text_value = format(ts.data.units, "~")


def is_safe_nd_array(input_str: str):
"""Check if input_string is a numerical array (allowing floats [with scientific notation), and ints."""
# Regex pattern to match 1-D and N-D arrays with numbers
pattern = (
r"^\s*(\[\s*(?:(-?\d+(\.\d+)?([eE][+-]?\d+)"
r"?|\[\s*.*?\s*\])\s*(,\s*)?)*\]\s*|\s*(-?\d+(\.\d+)"
r"?([eE][+-]?\d+)?)(\s*,\s*(-?\d+(\.\d+)?([eE][+-]?\d+)?))*\s*)?\s*$"
)

return bool(re.match(pattern, input_str))


def download_button(
content: bytes,
filename: str,
Expand Down
10 changes: 10 additions & 0 deletions weldx_widgets/tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import weldx
from weldx_widgets import WidgetTimeSeries
from weldx_widgets.generic import is_safe_nd_array


def test_import_export():
Expand All @@ -19,3 +20,12 @@ def test_import_export():

ts2 = w.to_tree()
assert ts2["timeseries"] == ts


def test_is_safe_nd_array():
assert is_safe_nd_array("1, 2, 3")
assert is_safe_nd_array("[1, 2, 3]")
assert is_safe_nd_array("[[1, 2, 3], [4, 5, 6]]")
assert is_safe_nd_array("[[1.2e3, -4.5E-2], [3.4]]")
assert not is_safe_nd_array("[1, 2, 'evil']")
assert not is_safe_nd_array("1, 2, (x) => x")
4 changes: 2 additions & 2 deletions weldx_widgets/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"kind",
(
"spray",
"UI",
"II",
# "UI",
# "II",
),
)
def test_import_export(kind):
Expand Down
8 changes: 2 additions & 6 deletions weldx_widgets/tests/test_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ def test_plot_coordinate_system():
[[-1, 0, 0], [0, -1, 0], [0, 0, 1]],
]
coordinates_tdp = Q_([[0, 0, 1], [0, 0, 2], [0, -1, 0]], "mm")
lcs_tdp = tf.LocalCoordinateSystem(
orientation=orientation_tdp, coordinates=coordinates_tdp, time=time
)
lcs_tdp = tf.LocalCoordinateSystem(orientation=orientation_tdp, coordinates=coordinates_tdp, time=time)

_, ax = plt.subplots(subplot_kw=dict(projection="3d"))

vs.draw_coordinate_system_matplotlib(lcs_constant, ax, "g")
vs.draw_coordinate_system_matplotlib(lcs_tdp, ax, "r", "2016-01-10")
vs.draw_coordinate_system_matplotlib(lcs_tdp, ax, "b", "2016-01-11", time_idx=1)
vs.draw_coordinate_system_matplotlib(
lcs_tdp, ax, "y", "2016-01-12", pd.TimedeltaIndex([12], "s")
)
vs.draw_coordinate_system_matplotlib(lcs_tdp, ax, "y", "2016-01-12", pd.TimedeltaIndex([12], "s"))

# exceptions ------------------------------------------

Expand Down
40 changes: 10 additions & 30 deletions weldx_widgets/visualization/csm_k3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ def _get_limits_from_stack(limits):
return np.vstack([mins, maxs])


def _get_coordinates_and_orientation(
lcs: LocalCoordinateSystem, index: int = 0
) -> tuple[pint.Quantity, pint.Quantity]:
def _get_coordinates_and_orientation(lcs: LocalCoordinateSystem, index: int = 0) -> tuple[pint.Quantity, pint.Quantity]:
"""Get the coordinates and orientation of a coordinate system.
Parameters
Expand All @@ -68,20 +66,14 @@ def _get_coordinates_and_orientation(
"Interpolate values before plotting to solve this issue"
)

coordinates = lcs.coordinates.isel(time=index, missing_dims="ignore").data.astype(
"float32"
)
coordinates = lcs.coordinates.isel(time=index, missing_dims="ignore").data.astype("float32")

orientation = lcs.orientation.isel(time=index, missing_dims="ignore").data.astype(
"float32"
)
orientation = lcs.orientation.isel(time=index, missing_dims="ignore").data.astype("float32")

return coordinates, orientation


def _create_model_matrix(
coordinates: pint.Quantity, orientation: np.ndarray
) -> np.ndarray:
def _create_model_matrix(coordinates: pint.Quantity, orientation: np.ndarray) -> np.ndarray:
"""Create the model matrix from an orientation and coordinates.
Parameters
Expand Down Expand Up @@ -487,9 +479,7 @@ def update_model_matrix(self, model_mat):
if self._mesh is not None:
self._mesh.model_matrix = model_mat
if self._label is not None:
self._label.position = (
np.matmul(model_mat[0:3, 0:3], self._label_pos) + model_mat[0:3, 3]
)
self._label.position = np.matmul(model_mat[0:3, 0:3], self._label_pos) + model_mat[0:3, 3]


class CoordinateSystemManagerVisualizerK3D:
Expand Down Expand Up @@ -780,26 +770,20 @@ def _create_controls(
traces_cb = Checkbox(value=show_traces, description="show traces", layout=lo)
labels_cb = Checkbox(value=show_labels, description="show labels", layout=lo)
wf_cb = Checkbox(value=show_wireframe, description="show wireframe", layout=lo)
data_labels_cb = Checkbox(
value=show_data_labels, description="show data labels", layout=lo
)
data_labels_cb = Checkbox(value=show_data_labels, description="show data labels", layout=lo)

jslink((play, "value"), (time_slider, "value"))
play.disabled = disable_time_widgets
time_slider.disabled = disable_time_widgets

# register callbacks
time_slider.observe(lambda c: self.update_time_index(c["new"]), names="value")
reference_dropdown.observe(
lambda c: self.update_reference_system(c["new"]), names="value"
)
reference_dropdown.observe(lambda c: self.update_reference_system(c["new"]), names="value")
vectors_cb.observe(lambda c: self.show_vectors(c["new"]), names="value")
origin_cb.observe(lambda c: self.show_origins(c["new"]), names="value")
traces_cb.observe(lambda c: self.show_traces(c["new"]), names="value")
labels_cb.observe(lambda c: self.show_labels(c["new"]), names="value")
data_dropdown.observe(
lambda c: self.set_data_visualization_method(c["new"]), names="value"
)
data_dropdown.observe(lambda c: self.set_data_visualization_method(c["new"]), names="value")
data_labels_cb.observe(lambda c: self.show_data_labels(c["new"]), names="value")
wf_cb.observe(lambda c: self.show_wireframes(c["new"]), names="value")

Expand All @@ -817,9 +801,7 @@ def _get_model_matrix(self, lcs_name):
return lcs_vis.origin.model_matrix

lcs = self._csm.get_cs(lcs_name, self._current_reference_system)
coordinates, orientation = _get_coordinates_and_orientation(
lcs, self._current_time_index
)
coordinates, orientation = _get_coordinates_and_orientation(lcs, self._current_time_index)
return _create_model_matrix(coordinates, orientation)

def _update_spatial_data(self):
Expand Down Expand Up @@ -916,9 +898,7 @@ def update_reference_system(self, reference_system):
"""
self._current_reference_system = reference_system
for lcs_name, lcs_vis in self._lcs_vis.items():
lcs_vis.update_lcs(
self._csm.get_cs(lcs_name, reference_system), self._current_time_index
)
lcs_vis.update_lcs(self._csm.get_cs(lcs_name, reference_system), self._current_time_index)
self._update_spatial_data()

def update_time_index(self, index: int):
Expand Down
10 changes: 2 additions & 8 deletions weldx_widgets/visualization/csm_mpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ def new_3d_figure_and_axes(
The matplotlib axes object
"""
fig, ax = plt.subplots(
ncols=num_subplots, subplot_kw={"projection": "3d", "proj_type": "ortho"}
)
fig, ax = plt.subplots(ncols=num_subplots, subplot_kw={"projection": "3d", "proj_type": "ortho"})
try:
fig.canvas.layout.height = f"{height}px"
fig.canvas.layout.width = f"{width}px"
Expand Down Expand Up @@ -260,11 +258,7 @@ def plot_local_coordinate_system_matplotlib(
show_vectors=show_vectors,
)

if (
show_trace
and not isinstance(lcs.coordinates, TimeSeries)
and lcs.coordinates.data.ndim > 1
):
if show_trace and not isinstance(lcs.coordinates, TimeSeries) and lcs.coordinates.data.ndim > 1:
coords = lcs.coordinates.data
if isinstance(coords, Q_):
coords = coords.to(_DEFAULT_LEN_UNIT).m
Expand Down
6 changes: 1 addition & 5 deletions weldx_widgets/widget_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ def metaclass_resolver(*classes):
def cls_name(classes):
return "_".join(mcls.__name__ for mcls in classes)

metaclass = (
metaclass[0]
if len(metaclass) == 1
else type(cls_name(metaclass), metaclass, {})
) # class M_C
metaclass = metaclass[0] if len(metaclass) == 1 else type(cls_name(metaclass), metaclass, {}) # class M_C
return metaclass(cls_name(classes), classes, {}) # class C


Expand Down
21 changes: 5 additions & 16 deletions weldx_widgets/widget_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ def make_output():
# start and end time of experiment
t = (file["TCP"].time[[0, -1]]).as_timedelta()

WidgetProcessInfo(
file["process"]["welding_process"], t, out=tabs["Process parameters"]
)
WidgetProcessInfo(file["process"]["welding_process"], t, out=tabs["Process parameters"])

groove = file["workpiece"]["geometry"]["groove_shape"]
with tabs["Specimen"]:
Expand All @@ -132,10 +130,7 @@ def make_output():
# clean up scan data (fill up NaNs)
scans_available = True
try:
foo = [
_clean_nans_from_spatial_data(csm.get_data(f"scan_{i}"))
for i in range(0, 2)
]
foo = [_clean_nans_from_spatial_data(csm.get_data(f"scan_{i}")) for i in range(0, 2)]
assert len(foo) == 2
# assert csm.get_data("scan_1").coordinates.
except KeyError:
Expand All @@ -145,18 +140,14 @@ def make_output():
spatial_data_geo_full = geometry_full_width.spatial_data(
profile_raster_width=Q_(4, "mm"), trace_raster_width=Q_(60, "mm")
)
spatial_data_geo_full.coordinates = spatial_data_geo_full.coordinates.astype(
"float32"
)
spatial_data_geo_full.coordinates = spatial_data_geo_full.coordinates.astype("float32")

spatial_data_geo_reduced = geometry.spatial_data(
profile_raster_width=Q_(4, "mm"), trace_raster_width=Q_(60, "mm")
)

csm.assign_data(spatial_data_geo_full, "workpiece geometry", "workpiece")
csm.assign_data(
spatial_data_geo_reduced, "workpiece geometry (reduced)", "workpiece"
)
csm.assign_data(spatial_data_geo_reduced, "workpiece geometry (reduced)", "workpiece")

with tabs["CSM-Subsystems"]:
csm.plot_graph()
Expand Down Expand Up @@ -284,9 +275,7 @@ def _welding_wire_geo_data(radius, length, cross_section_resolution=8):
triangles[-1][1] = 1
triangles[-1][2] = 0

return SpatialData(
Q_(points, "mm").astype("float32"), np.array(triangles, dtype="uint32")
)
return SpatialData(Q_(points, "mm").astype("float32"), np.array(triangles, dtype="uint32"))

@staticmethod
def _create_geometry(groove, seam_length, width):
Expand Down
4 changes: 1 addition & 3 deletions weldx_widgets/widget_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ class WidgetFloatWithUnit(WidgetMyHBox):

def __init__(self, text, unit, value: float = 0.0, min=0):
self._label = Label(text, layout=description_layout)
self._float = BoundedFloatText(
value=value, min=min, max=2**32, layout=textbox_layout
)
self._float = BoundedFloatText(value=value, min=min, max=2**32, layout=textbox_layout)
self._unit = Text(value=unit, placeholder="unit", layout=textbox_layout)

super().__init__(
Expand Down
Loading

0 comments on commit 650cee2

Please sign in to comment.