secv_guis package

Submodules

secv_guis.base_widgets module

This module is a library of reusable, extendable widgets.

class secv_guis.base_widgets.CheckBoxGroup(parent=None, horizontal=False)[source]

Bases: PySide2.QtWidgets.QWidget

A group of CheckBox es

add_box(name, tristate=False, initial_val=True)[source]
Parameters:tristate (bool) – If true, the added check box will have 3 states.
remove_box(idx)[source]
Parameters:idx (int) – Boxes are added in increasing index order, so this the lower this index the ‘older’ the box that is being removed.
state()[source]
Returns:A list with all the current states, in index order.
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class secv_guis.base_widgets.FileList(label, parent=None, default_path=None, extensions=None, sort=True)[source]

Bases: PySide2.QtWidgets.QWidget

A file dialog button followed by a list that shows the files in the selected folder.

staticMetaObject = <PySide2.QtCore.QMetaObject object>
update_path(dirname, selected_image=None)[source]
Parameters:
  • dirname (str) – The new directory path to be listed.
  • selected_images – The name of an image with which the list can be filtered
class secv_guis.base_widgets.MaskPaintForm(brush_names, max_brush_size=100, parent=None, thresh_min=0, thresh_max=1, thresh_num_steps=100, min_alpha=1, max_alpha=255)[source]

Bases: PySide2.QtWidgets.QWidget

This widget contains one section for the masks and one for the painter. The mask section contains a set of elements, one per mask. A radio button selects the currently active mask, and each mask features an RGBA box and a thershold slider. The painter section contains a ComboBox to select the painter type, and a slider for the painter size.

To use it in specific applications override button_pressed, combo_box_changed, rgba_box_changed...

add_item(name, rgba, slider_visible=True, activate=False)[source]

Add an element to the ‘mask’ section, with the given name and color.

Parameters:
  • slider_visible – If false, the slider will be still there but hidden.
  • activate – Once created, select this item in the radio buttons.
brush_size_changed(sz)[source]

Override me!

brush_type_changed(idx)[source]

Override me!

Parameters:idx (int) – Starts with 0 and respects ordering given at construction. So when overriding this method, you can assume that 0 will correspond to the firstly added element, and so on.
button_pressed(but)[source]

Override me!

Parameters:idx (int) –

Starts with 0 and respects ordering given at construction. So when overriding this method, you can assume that 0 will correspond to the firstly added element, and so on. Implementation example:

i = self._buttons.index(but)
print("button pressed: >>>", i, but.text())
remove_item(idx)[source]

Remove an element from the ‘mask’ section by index. Indexes are in increasing order, so lowest is oldest.

rgba_box_changed(idx, r, g, b, a)[source]

Override me!

slider_to_p_val(sl_val)[source]

Since the slider goes from 0 to thresh_num_steps, this function linearly interpolates the, so that 0 maps to thresh_min and thresh_num_steps maps to thresh_max. Note that min does not neccesarily have to be smaller than max.

Parameters:sl_val (int) – The actual slider value from 0 to num_steps.
Returns:The converted and interpolated value.
staticMetaObject = <PySide2.QtCore.QMetaObject object>
threshold_slider_changed(idx, val)[source]

Override me!

class secv_guis.base_widgets.RGBASpinbox(initial_rgba=(0, 255, 0, 100), parent=None, min_alpha=1, max_alpha=255)[source]

Bases: PySide2.QtWidgets.QWidget

A cluster of 4 [0-255] spin boxes, representing (and having) an RGBA color. Use self.connect to wire this widget to any method.

connect(fn)[source]
Parameters:fn – A function to connect this widget to. It must have the following signature``fn(idx, r, g, b, a)``.

When calling self.connect(f), any value changes in R, G, B or A will trigger f(r, g, b, a) with the changed values

get_current_rgba()[source]
Returns:Current state as (r, g, b, a) numeric tuple.
staticMetaObject = <PySide2.QtCore.QMetaObject object>
class secv_guis.base_widgets.SaveForm(parent=None, default_path=None)[source]

Bases: PySide2.QtWidgets.QWidget

A formulary providing functionality for selecting what to save, where to save, the output suffix and overwriting policy.

DIALOG_TEXT = 'Output\nfolder'
OVERWRITE_TEXT = 'Overwrite\nsaved'
SAVE_TEXT = 'Save\nselected'
add_checkbox(checkbox_name, initial_val=True, initial_txt=None)[source]

Adds an element that can be selected to be saved.

Parameters:
  • checkbox_name – The element identifier
  • initial_txt – The initial suffix to be appended to the files. If none is given, the checkbox_name is picked as default. The user can change this from the GUI.
save_masks(states, suffixes, overwrite)[source]
Parameters:
  • states – A list with booleans, representing the checkbox states for the contained elements.
  • suffixes – A list with the corresponding suffixes
  • overwrite – A boolean determining whether the ‘overwrite’ checkbox has been activated.

Override me!

staticMetaObject = <PySide2.QtCore.QMetaObject object>

secv_guis.commands module

This module contains all the ‘undoable’ actions. They must implement a way to undo and redo them.

Composite commands deserve a special mention: they are trains of actions that only track, store and report the initial and final state. They are particularly useful when performing interactive editings on big datastructures like pixmaps, to prevent memory bloating.

class secv_guis.commands.CompositeCommand(parent=None)[source]

Bases: PySide2.QtWidgets.QUndoCommand

In some cases like painting a stroke into a pixmap, it doesn’t make sense to store every single update: rather, the prior and finished states only. This class provides a structure for such cases:

  1. Instantiate the command with the parameters that belong to the whole
    composite action.
  2. Call action for every desired update of the finished state
  3. Call finish to crystalize the final state. No further action s
    will be allowed, and (optionally) the action will be added to a Qt UndoStack.

The following is required to extend the class: 1. Define a COMMAND_NAME 2. Extend the __init__, action, undo and redo methods.

COMMAND_NAME = NotImplemented
action()[source]

Extend me!

finish(undo_stack=None)[source]
Parameters:undo_stack – If given, this command will be added to the stack, wich then allows to undo/redo.

Call this function once you are done with the action s. Once finish is called, no more action s are possible, so that the undo/redo actions stay frozen.

class secv_guis.commands.DrawCommand(pmi, rgba, diameter, comp_mode=PySide2.QtGui.QPainter.CompositionMode.CompositionMode_Source, parent=None)[source]

Bases: secv_guis.commands.CompositeCommand

A composite command to draw a stroke of circles into a PixmapIten.

COMMAND_NAME = 'Draw'
action(x_pos, y_pos)[source]

Once the object has been constructed and ``finish()`` hasn’t been called yet, Call this function to paint a circle at given position. Check constructor for further variables.

finish(undo_stack=None)[source]

Usually we don’t override finish, but since pixmaps are so big, we don’t want to store the command if original and final are equal.

redo()[source]

This function implements the interface for the UndoStack. Don’t call this directly.

undo()[source]

This function implements the interface for the UndoStack. Don’t call this directly.

class secv_guis.commands.DrawOverlappingCommand(pmi, ref_pmi, rgba, diameter, comp_mode=PySide2.QtGui.QPainter.CompositionMode.CompositionMode_Source, parent=None)[source]

Bases: secv_guis.commands.DrawCommand

Like DrawCommand, but accepts 2 PixmapItems instead of one, so that the drawing onto the first is only allowed if the same pixel is active in the second.

COMMAND_NAME = 'Draw Overlapping'
action(x_pos, y_pos)[source]

Paint a circle on pmi at given position, masked by ref_pmi.

class secv_guis.commands.EraseCommand(pmi, diameter, comp_mode=PySide2.QtGui.QPainter.CompositionMode.CompositionMode_Source, parent=None)[source]

Bases: secv_guis.commands.DrawCommand

A composite command to erase a stroke of circles into a PixmapIten. See DrawCommand docstrings for more info.

COMMAND_NAME = 'Erase'
class secv_guis.commands.UndoableLambda(command_name, undo_fn, redo_fn, parent=None)[source]

Bases: PySide2.QtWidgets.QUndoCommand

This kind of functor can be used to create them on the spot and send them to the UndoStack. Useful to split down a composite action in arbitrary undo-able subactions. Usage example:

# in action ... do something ...
cmd = UndoableLambda("My partial action",
                     lambda: print("undo"), lambda: print("redo"))
undo_stack.push(cmd)
redo()[source]
undo()[source]

secv_guis.dialogs module

This module defines several reusable dialog types.

class secv_guis.dialogs.ExceptionDialog(error_msg, timeout_ms=None, parent=None)[source]

Bases: secv_guis.dialogs.InfoDialog

This class is intended to be used at the main loop level, to catch any exceptions that the app may have and show them in a Dialog. To do that, it suffices to put the following line anywhere before app.exec_():

sys.excepthook = ExceptionDialog.excepthook

Source: https://stackoverflow.com/a/55819545/4511978

DEACTIVATE = False
ERROR_TXT = '\nERROR!\nIf you think this is an app error consider reporting the following\nto the developers (see Help->About):'
classmethod excepthook(exc_type, exc_value, exc_tb)[source]

Set this method as sys.excepthook = <THIS_CLASS>.excepthook somewhere before app.exec_() to wrap all Python exceptions with this dialog.

on_reject()[source]

If the user presses on don’t show errors again, the whole class gets deactivated, so further created instances won’t pop up.

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class secv_guis.dialogs.FlexibleDialog(accept_button_name=None, reject_button_name=None, timeout_ms=None, parent=None)[source]

Bases: PySide2.QtWidgets.QDialog

Dialog class that allows for OK, Yes/No, and timeout interactions. To extend this dialog class, override setup_ui_body, on_accept and on_reject, store the instance and call it with show or exec_.

Note that setup_ui_body is being called IN the constructor, so any variables that it may need when extending the class need to be set before super().__init__ is called.

As it can be seen here, https://stackoverflow.com/questions/56449605/pyside2-qdialog-possible-bug

implementing a Dialog in PySide2 is a little tricky. These are some things to consider:

  • Do not implement accept, reject directly. Rather, connect the buttons to accept, reject, and then connect the accepted, rejected signals to custom methods (in this case on_accept, on_reject).
  • When calling the Dialog from the main window, the dialog must be persistently stored as a field of the main window i.e. self.d = .... Otherwise it will not show up. Then it can be called in modal or modeless way, as follows: XXX.connect(self.d.show), ...(self.d.exec_).
TIMEOUT_LBL_TXT = 'Closing in {} seconds...'
exec_(*args, **kwargs)[source]

Start the dialog in ‘exclusive’ way, blocking the rest of the app.

on_accept()[source]

This method will be called if the user presses the (optional) accept button.

on_reject()[source]

This method will be called if the user presses the (optional) reject button.

setup_ui_body(widget)[source]

Populate the widget with your desired contents. The widget will be above the buttons.

show(*args, **kwargs)[source]

Start the dialog in parallel to the rest of the app.

staticMetaObject = <PySide2.QtCore.QMetaObject object>
class secv_guis.dialogs.InfoDialog(header, message, accept_button_name=None, reject_button_name=None, timeout_ms=None, parent=None, print_msg=True, header_style='font-weight: bold; color: black')[source]

Bases: secv_guis.dialogs.FlexibleDialog

A type of dialog that shows a header and body strings.

INTERACT_BODY = True
INTERACT_HEADER = False
exec_(*args, **kwargs)[source]
setup_ui_body(widget)[source]
show(*args, **kwargs)[source]
staticMetaObject = <PySide2.QtCore.QMetaObject object>

secv_guis.masked_scene module

This module contains the QGraphicsScene+QGraphicsView binomial, typically used in Qt apps to display and navigate images, together with some specific functionality to annotate.

class secv_guis.masked_scene.DisplayView(scene=None, parent=None, scale_percent=15)[source]

Bases: secv_guis.mouse_event_manager.MouseEventManager, PySide2.QtWidgets.QGraphicsView

In Qt applications, it is usual to wrap a scene with a view. This allows to dynamically and easily change the perspective on the scene.

fit_in_scene()[source]

Moves perspective so that the whole scene can be seen in view.

on_mid_press(event)[source]

Override me

on_move(event, has_left, has_mid, has_right, this_pos, last_pos)[source]

Override me

on_right_press(event)[source]

Override me

on_right_release(event)[source]

Override me

on_wheel(event, has_ctrl, has_alt, has_shift)[source]

Override me

shift_view(delta_x, delta_y)[source]

Move perspective to shift through the scene

staticMetaObject = <PySide2.QtCore.QMetaObject object>
zoom(pos_x, pos_y, zoom_out=False)[source]

Source for wheel zoom: https://stackoverflow.com/a/29026916/4511978

class secv_guis.masked_scene.MaskedImageScene(img_arr=None, parent=None)[source]

Bases: PySide2.QtWidgets.QGraphicsScene, secv_guis.objects.ObjectContainer

Basic area that allows to display a color image, together with a set of binary masks on top of it.

DEFAULT_MASK_ALPHA = 100
add_mask(mask_arr, rgba=None, item_on_top=None)[source]
Parameters:
  • mask_arr – A np.bool(h, w) array.
  • item_on_top – If given, mask will be added underneath that item. Otherwise will be added on top of item stack.
Returns:

The added PixmapItem.

items(ascending=True)[source]
Returns:This scene’s items.
Parameters:ascending – If true, items are given from background to foreground.
mask_as_bool_arr(pmi)[source]

Asserts that the given pmi is in self.mask_pmis, and returns the map as np.bool(h, w) array, in which all non-zero values are true.

num_items()[source]

Number of items in this scene, ordered from foreground to background.

remove_mask(pmi)[source]
Parameters:pmi – The PixmapItem to remove. It has to be a mask added via add_mask
replace_mask_pmi(pmi, new_mask_arr, new_rgba=None)[source]

If we call remove_mask and then add_mask fails, we will lose the removed mask forever. This method updates the mask in an atomary way: either succeeds or does nothing.

Warning

The input pmi gets removed from the scene and the reference becomes invalid. Use the reference returned by this function instead.

staticMetaObject = <PySide2.QtCore.QMetaObject object>
update_image(img_arr)[source]

Clears whole scene, and adds the given numpy array as Pixmap.

Parameters:img_arr – A np.uint8(h, w [, ?]) array.

secv_guis.mouse_event_manager module

This module contains a convenience mixin that provides the following functionality for mouse tracking:

  • Record previous mouse states
  • Demultiplex mouse events and preprocess relevant informations

To make a widget responsive to mouse events, simply this class there and override the desired methods.

class secv_guis.mouse_event_manager.MouseEventManager(track=True)[source]

Bases: object

Extend this class and then instantiate it once as a property in your desired widget.

Warning

The Mixin is compatible with multiple inheritance, but not all initializations work. The following does:

class A(MouseEventManager, QtWidgets.XXX):
def __init__(self, …):
QtWidgets.XXX.__init__(self, …) MouseEventManager.__init__(self, …)

In this example, class A will respond to the overriden on_move, etc… methods.

mouseMoveEvent(event)[source]
mousePressEvent(event)[source]

Wheel click event handler

mouseReleaseEvent(event)[source]

Wheel release event handler

on_left_press(event)[source]

Override me

on_left_release(event)[source]

Override me

on_mid_press(event)[source]

Override me

on_mid_release(event)[source]

Override me

on_move(event, has_left, has_mid, has_right, this_pos, last_pos)[source]

Override me

on_right_press(event)[source]

Override me

on_right_release(event)[source]

Override me

on_wheel(event, has_ctrl, has_alt, has_shift)[source]

Override me

wheelEvent(event)[source]

Override me. This is a simple wrapper, but may include functionality like storing positions if needed.

secv_guis.objects module

Object composite commands are intended for composite objects (like e.g. a train of points). The main difference with the regular commands is that they implement a state method (which e.g. for a train of points returns a list of (x, y) tuples), and a clear method, which allows to ‘remove’ the object without having to roll back or break the undo queue.

class secv_guis.objects.ObjectContainer[source]

Bases: object

This class is a Mixin. When a QGraphicsScene inherits from it, it acquires functionality to add multiple composite objects from this module.

close_current_object_action(undo_stack=None)[source]

If there is an open object action, closes it and optionally adds it to the undo stack

object_action(obj_class, action_args, obj_instantiation_args, undo_stack=None)[source]

This function implements a protocol to add composite objects to the scene.

  1. If a different composite action was running, it closes it and starts
    this one, adding it to self.objects.
  2. If no composite action was running, starts this one and adds it.
  3. If obj_class is already running, does nothing here.

In all cases, including if obj_class was already running, performs the action obj.action(*action_args).

Note

The scene simply calls the object’s action. The object is responsible for keeping track of the scene items it generates, and also removing/adding them to the scene when needed.

Parameters:
  • obj_instantiation_args – If this action needs to be started, it will be called via cmd = action_class(*instantiation_args)
  • action_args – The action will be called with this args.

Usage example:

# adds a point to the existing cloud or starts one otherwise
scene.object_action(ExamplePointCloud, [x, y],
                    [cloud_color, points_size...])
# check the state of the last added point cloud (this one):
scene.objects[ExamplePointCloud][-1].state()
class secv_guis.objects.PointList(scene, diameter, fill_rgba=(100, 100, 100, 100), contour_rgba=(0, 0, 0, 255), draw_lines_between_dots=False, comp_mode=PySide2.QtGui.QPainter.CompositionMode.CompositionMode_SourceOver, parent=None)[source]

Bases: secv_guis.commands.CompositeCommand

This class provides functionality to add circles to a scene (optionally connected by lines), and to return their centers as a list of (x, y) positions. It also

COMMAND_NAME = 'Draw point list'
action(x, y, undo_stack=None)[source]

Add a new point at given position. :param undo_stack: If given, this action is added to the undo stack.

clear()[source]

Removes all the active points from the datastructure and the scene.

finish(undo_stack=NotImplemented)[source]

Simply sets self.finished to true, because the undo stack gets the separate actions instead of the whole composite one.

redo()[source]

Unused

state()[source]
Returns:A list in the form [(x1, y1), ...] with the center of the currently active points.
undo()[source]

Unused

secv_guis.utils module

This module contains helper functions and utilities that may be used anywhere else in the project.

class secv_guis.utils.RandomColorGenerator(seed=None)[source]

Bases: randomcolor.RandomColor

Flexible generator for nice random colors. For more details check https://pypi.org/project/randomcolor/

Usage example::
r, g, b = next(RandomColorGenerator().generate(form=”rgbArray”))
generate(hue=None, luminosity=None, count=1, form='rgbArray')[source]
Parameters:form – Popular ones: rgbArray, rgba, hex, rgb
Returns:A generator with count random colors.
Overriden to return a generator instead of a list. Source:
https://github.com/kevinwuhoo/randomcolor-py
secv_guis.utils.bool_arr_to_rgba_pixmap(arr, rgba=(255, 0, 0, 255))[source]
Parameters:
  • arr – Expects a np.bool(h, w) array.
  • rgba – 4 values between 0 and 255. Alpha=255 means full opacity.
Returns:

A QtGui.QPixmap in format RGBA8888(w, h), where the false values are all zeros and the true values have the specified rgba color.

secv_guis.utils.load_exif(img_path)[source]
Returns:A dictionary with the EXIF data contained at img_path.
secv_guis.utils.load_img_and_exif(img_path: str, as_np_array=True, ignore_alpha=True)[source]

Loads the image at given path using PIL, and its EXIF data. If the EXIF data contains extra info about orientation, also rotates the image accordingly.

Parameters:
  • as_np_array – If true, the image will be converted from PIL format to np via np.asarray(image)
  • ignore_alpha – If the type of the image is RGBA, it will be converted to RGB.
Returns:

A tuple (image, exif_dict).

Inspired in https://stackoverflow.com/a/26928142

secv_guis.utils.pixmap_to_arr(pm, img_format=PySide2.QtGui.QImage.Format.Format_RGBA8888)[source]
Parameters:
Returns:

A np.uint8(h, w, C) array, where the number of channels C depends on the image format.

..note::
Pixmaps are in format (w, h, …) but arrays are returned in (h, w, ...), as usual for numpy
secv_guis.utils.rgb_arr_to_rgb_pixmap(arr)[source]
Parameters:arr – Expects a np.uint8(h, w, 3) array.
Returns:A QtGui.QPixmap in format RGB888(w, h).
secv_guis.utils.unique_filename(path, suffix='_({})', max_iters=10000)[source]

Given a path, returns the same path if unique, or adds (N) before the extension to make it unique, for N being the lowest integer possible starting from 1.

Module contents