Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
gjover committed Jan 29, 2024
2 parents 3f8ce6d + 97214ae commit 8520bb9
Show file tree
Hide file tree
Showing 108 changed files with 35,885 additions and 1,660 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
python-version: '3.10'
tox_env: pyqt6
name: PyQt6
- os: ubuntu-22.04
python-version: '3.11'
tox_env: beta
name: "Scientific Python nightly wheels"

services:
postgres:
Expand Down Expand Up @@ -96,7 +100,7 @@ jobs:
- name: Skip testing workflows at coverage
if: |
matrix.python-version == '3.8' && matrix.tox_env == 'orange-released'
matrix.python-version == '3.11' && matrix.tox_env == 'orange-released'
run: |
echo 'SKIP_EXAMPLE_WORKFLOWS=1' >> $GITHUB_ENV
Expand All @@ -107,7 +111,7 @@ jobs:
ORANGE_TEST_DB_URI: postgres://postgres_user:postgres_password@localhost:5432/postgres_db|mssql://SA:sqlServerPassw0rd@localhost:1433

- name: Upload code coverage
if: matrix.python-version == '3.8' && matrix.tox_env == 'orange-released'
if: matrix.python-version == '3.11' && matrix.tox_env == 'orange-released'
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/translations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Check translations

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test-translations:
uses: biolab/orange-ci-cd/.github/workflows/test-translations.yml@master
with:
package-dir: Orange
9 changes: 3 additions & 6 deletions Orange/canvas/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from collections import defaultdict
from datetime import date
from urllib.request import urlopen, Request
from packaging.version import Version

import pkg_resources
import yaml

from AnyQt.QtGui import QColor, QDesktopServices, QIcon, QPalette
Expand Down Expand Up @@ -101,9 +101,8 @@ def run(self):
self.resultReady.emit(contents)

def compare_versions(latest):
version = pkg_resources.parse_version
skipped = settings.value('startup/latest-skipped-version', "", type=str)
if version(latest) <= version(current) or \
if Version(latest) <= Version(current) or \
latest == skipped:
return

Expand Down Expand Up @@ -187,8 +186,6 @@ def pull_notifications():
if not check_notifs:
return None

Version = pkg_resources.parse_version

# create settings_dict for notif requirements purposes (read-only)
spec = canvasconfig.spec + config.spec
settings_dict = canvasconfig.Settings(defaults=spec, store=settings)
Expand All @@ -198,7 +195,7 @@ def pull_notifications():
if ep.dist is not None]
installed = defaultdict(lambda: "-1")
for addon in installed_list:
installed[addon.project_name] = addon.version
installed[addon.name] = addon.version

# get set of already displayed notification IDs, stored in settings["notifications/displayed"]
displayedIDs = literal_eval(settings.value("notifications/displayed", "set()", str))
Expand Down
37 changes: 22 additions & 15 deletions Orange/canvas/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Orange Canvas Configuration
"""
import pkgutil

import random
import uuid
import warnings
Expand All @@ -11,15 +13,16 @@
from typing import Dict, Any, Optional, Iterable, List

import packaging.version
import pkg_resources
import requests

from AnyQt.QtGui import (
QPainter, QFont, QFontMetrics, QColor, QPixmap, QIcon, QGuiApplication
QPainter, QFont, QFontMetrics, QColor, QImage, QPixmap, QIcon,
QGuiApplication
)
from AnyQt.QtCore import Qt, QPoint, QRect, QSettings

from orangecanvas import config as occonfig
from orangecanvas.config import entry_points, EntryPoint
from orangecanvas.utils.settings import config_slot
from orangewidget.workflow import config
from orangewidget.settings import set_widget_settings_dir_components
Expand Down Expand Up @@ -61,6 +64,11 @@
spec = [config_slot(*t) for t in spec]


def _pixmap_from_pkg_data(package, path, format):
contents = pkgutil.get_data(package, path)
return QPixmap.fromImage(QImage.fromData(contents, format))


class Config(config.Config):
"""
Orange application configuration
Expand Down Expand Up @@ -96,17 +104,16 @@ def application_icon():
"""
Return the main application icon.
"""
path = pkg_resources.resource_filename(
__name__, "icons/orange-256.png"
return QIcon(
_pixmap_from_pkg_data(__package__, "icons/orange-256.png", "png")
)
return QIcon(path)

@staticmethod
def splash_screen():
splash_n = random.randint(1, 3)
path = pkg_resources.resource_filename(
__name__, f"icons/orange-splash-screen-{splash_n:02}.png")
pm = QPixmap(path)
pm = _pixmap_from_pkg_data(
__name__, f"icons/orange-splash-screen-{splash_n:02}.png", "png"
)

version = Config.ApplicationVersion
if version:
Expand Down Expand Up @@ -138,9 +145,9 @@ def widgets_entry_points():
# Ensure the 'this' distribution's ep is the first. iter_entry_points
# yields them in unspecified order.
all_eps = sorted(
pkg_resources.iter_entry_points(WIDGETS_ENTRY),
entry_points(group=WIDGETS_ENTRY),
key=lambda ep:
0 if ep.dist.project_name.lower() == "orange3" else 1
0 if ep.dist.name.lower() == "orange3" else 1
)
return iter(all_eps)

Expand Down Expand Up @@ -173,18 +180,18 @@ def core_packages():

@staticmethod
def examples_entry_points():
# type: () -> Iterable[pkg_resources.EntryPoint]
# type: () -> Iterable[EntryPoint]
"""
Return an iterator over the entry points yielding 'Example Workflows'
"""
# `iter_entry_points` yields them in unspecified order, so we order
# them by name. The default is at the beginning, unless another
# entrypoint precedes it alphabetically (e.g. starting with '!').
default_ep = pkg_resources.EntryPoint(
"000-Orange3", "Orange.canvas.workflows",
dist=pkg_resources.get_distribution("Orange3"))
default_ep = EntryPoint(
"000-Orange3", "Orange.canvas.workflows", "orange.widgets.tutorials",
)

all_ep = list(pkg_resources.iter_entry_points("orange.widgets.tutorials"))
all_ep = list(entry_points(group="orange.widgets.tutorials"))
all_ep.append(default_ep)
all_ep.sort(key=lambda x: x.name)
return iter(all_ep)
Expand Down
65 changes: 50 additions & 15 deletions Orange/data/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import numpy as np
import pandas as pd


import xlrd
import xlsxwriter
import openpyxl
import h5py

from Orange.data import _io, Table, Domain, ContinuousVariable, update_origin
from Orange.data import Compression, open_compressed, detect_encoding, \
Expand All @@ -33,7 +33,6 @@

from Orange.util import flatten


# Support values longer than 128K (i.e. text contents features)
csv.field_size_limit(100*1024*1024)

Expand Down Expand Up @@ -542,34 +541,56 @@ class GenericHDF5Reader(FileFormat):

def __init__(self, filename):
super().__init__(filename=filename)

Check warning on line 543 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L543

Added line #L543 was not covered by tests
self.data = None

self.h5_file = h5py.File(filename)

Check warning on line 545 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L545

Added line #L545 was not covered by tests

self.datasets = {}
self._load_group("/", self.h5_file)

Check warning on line 548 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L547-L548

Added lines #L547 - L548 were not covered by tests

@property
def sheets(self) -> List:
"""List of datasets in the file.
Returns
-------
List of dataset paths
"""
return list(self.datasets.keys())

Check warning on line 558 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L558

Added line #L558 was not covered by tests

def select_sheet(self, sheet):
"""Select dataset to be read
Parameters
----------
sheet : str
dataset path
"""
if sheet is None:
sheet = self.sheets[0]
self.sheet = sheet

Check warning on line 570 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L568-L570

Added lines #L568 - L570 were not covered by tests

def read(self):
"""Processes the data stored in self.data and returns it as an Orange
"""Process data stored in self.data and returns it as an Orange
Table object.
Returns
-------
table (Orange.Table object):
Contains the information of the chosen dataset in the hdf5 file.
Raises
------
Exception: If the self.data variable has not been filled yet.
"""
if self.data is None:
raise ValueError("The data has not been loaded correctly")

if self.data.name is not None:
name = self.data.name.split('/')[-1]

if self.sheet is not None:
name = self.sheet.split('/')[-1]

Check warning on line 583 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L582-L583

Added lines #L582 - L583 were not covered by tests
else:
name = "Data"

Check warning on line 585 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L585

Added line #L585 was not covered by tests

data = self.datasets[self.sheet]

Check warning on line 587 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L587

Added line #L587 was not covered by tests

# Standard names for the columns of the dataset, can be changed manually
# in the widget itself
columns = [str(i) for i in range(len(self.data.shape))]
columns = [str(i) for i in range(len(data.shape))]

Check warning on line 591 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L591

Added line #L591 was not covered by tests

dataset = np.array(self.data)
dataset = np.array(data)

Check warning on line 593 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L593

Added line #L593 was not covered by tests

# Indexs are created to keep track of the position of the values in the
# original data file
Expand All @@ -583,3 +604,17 @@ def read(self):
table = Table.from_numpy(domain=Domain(attributes=attrs), X=df.values)

Check warning on line 604 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L603-L604

Added lines #L603 - L604 were not covered by tests

return table

Check warning on line 606 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L606

Added line #L606 was not covered by tests

def _load_group(self, root, group):
"""Recursive procedure that constructs the list of datasets
stored in the .hdf5 file.
Given a root, iterates over all its children to decide whether
they are a dataset or another group of data.
"""
for name, obj in group.items():
path = root + name
if isinstance(obj, h5py.Group):
self._load_group(path + "/", group[name])
elif isinstance(obj, h5py.Dataset):
self.datasets[path] = obj

Check warning on line 620 in Orange/data/io.py

View check run for this annotation

Codecov / codecov/patch

Orange/data/io.py#L615-L620

Added lines #L615 - L620 were not covered by tests
3 changes: 2 additions & 1 deletion Orange/data/io_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from glob import glob

import numpy as np
import pandas

from Orange.data import Table, Domain, Variable, DiscreteVariable, \
StringVariable, ContinuousVariable, TimeVariable
Expand Down Expand Up @@ -665,7 +666,7 @@ def formatter(cls, var):
elif var.is_discrete:
return lambda value: "" if isnan(value) else var.values[int(value)]
elif var.is_string:
return lambda value: value
return lambda value: "" if pandas.isnull(value) else value
else:
return var.repr_val

Expand Down
33 changes: 31 additions & 2 deletions Orange/data/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1551,18 +1551,47 @@ def has_missing_class(self):
return bn.anynan(self._Y)

@staticmethod
def __get_nan_frequency(data):
def __get_nan_count(data):
if data.size == 0:
return 0
dense = data if not sp.issparse(data) else data.data
return np.isnan(dense).sum() / np.prod(data.shape)
return np.isnan(dense).sum()

@classmethod
def __get_nan_frequency(cls, data):
return cls.__get_nan_count(data) / (np.prod(data.shape) or 1)

def get_nan_count_attribute(self):
return self.__get_nan_count(self.X)

def get_nan_count_class(self):
return self.__get_nan_count(self.Y)

def get_nan_count_metas(self):
if self.metas.dtype != object:
return self.__get_nan_count(self.metas)

data = self.metas
if sp.issparse(data):
data = data.tocsc()

count = 0
for i, attr in enumerate(self.domain.metas):
col = data[:, i]
missing = np.isnan(col.astype(float)) \
if not isinstance(attr, StringVariable) else data == ""
count += np.sum(missing)
return count

def get_nan_frequency_attribute(self):
return self.__get_nan_frequency(self.X)

def get_nan_frequency_class(self):
return self.__get_nan_frequency(self.Y)

def get_nan_frequency_metas(self):
return self.get_nan_count_metas() / (np.prod(self.metas.shape) or 1)

def checksum(self, include_metas=True):
# TODO: zlib.adler32 does not work for numpy arrays with dtype object
# (after pickling and unpickling such arrays, checksum changes)
Expand Down
7 changes: 4 additions & 3 deletions Orange/data/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def setUp(self):
self.domain,
np.array([[1, 0.5], [2, np.nan], [np.nan, 1.0625]]),
np.array([3, 1, 7]),
np.array(["foo bar baz".split()]).T
np.array([["foo", "bar", np.nan]], dtype=object).T
)

def test_write_tab(self):
Expand All @@ -137,7 +137,7 @@ def test_write_tab(self):
class\tmeta\t\t
3\tfoo\ty\t0.500
1\tbar\tz\t
7\tbaz\t\t1.06250""".strip())
7\t\t\t1.06250""".strip())
finally:
os.remove(fname)

Expand All @@ -149,7 +149,8 @@ def test_roundtrip_xlsx(self):
data = ExcelReader(fname).read()
np.testing.assert_equal(data.X, self.data.X)
np.testing.assert_equal(data.Y, self.data.Y)
np.testing.assert_equal(data.metas, self.data.metas)
np.testing.assert_equal(data.metas[:2], self.data.metas[:2])
self.assertEqual(data.metas[2, 0], "")
np.testing.assert_equal(data.domain, self.data.domain)
finally:
os.remove(fname)
Expand Down
2 changes: 2 additions & 0 deletions Orange/data/tests/test_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ def test_val(self):
self.assertEqual(a.str_val(""), "?")
self.assertEqual(a.str_val(Value(a, "")), "?")
self.assertEqual(a.repr_val(Value(a, "foo")), '"foo"')
self.assertEqual(a.str_val(np.nan), "?")
self.assertEqual(a.str_val(None), "?")


@variabletest(TimeVariable)
Expand Down
Loading

0 comments on commit 8520bb9

Please sign in to comment.