Skip to content

Commit

Permalink
Merge master into persistence branch with new svg changes
Browse files Browse the repository at this point in the history
  • Loading branch information
lisham2000 committed Oct 29, 2024
2 parents ac4bb71 + 6f76aee commit 275fffb
Show file tree
Hide file tree
Showing 43 changed files with 2,873 additions and 1,199 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -73,5 +73,5 @@ jobs:
conda update -n base --all -y
conda update --all -y
conda install conda-build anaconda-client -y
conda-build -q -c nion --skip-existing --user nion --token ${{ secrets.anaconda_token }} ..
conda build -q --python ${{ matrix.python-version }} -c nion -c conda-forge --override-channels --skip-existing --user nion --token ${{ secrets.anaconda_token }} ..
popd
13 changes: 13 additions & 0 deletions artwork/zoom.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. include:: defs.rst
.. _index:

|AppName| User's Guide (16.11.0)
|AppName| User's Guide (16.12.0)
================================
|AppName| is an open-source scientific image-processing software that uses Python to integrate hardware control, data acquisition, visualization, processing, and analysis. It runs on Windows, Linux, and macOS.

Expand Down
21 changes: 21 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ them for evaluation and feedback. If you encounter an issue or have feedback abo
please contact us or `file issues
<https://github.com/nion-software/nionswift/issues>`_.

Version 16.12.0 (2024-10-27)
----------------------------
* Add zoom tool in toolbar. Also add zoom shortcut ‘z’. (`#21 <https://github.com/nion-software/nionswift/issues/21>`_)
* Display performance improvements and other drawing improvements.
* Display snapshot now includes display range, color table, etc. (`#44 <https://github.com/nion-software/nionswift/issues/44>`_)
* Export and copy to SVG uses sans-serif fonts now.
* Allow export to SVG to specify size in pixels, inches, or centimeters.
* Keep sizing of surrounding display panels when removing one.
* Retain state of inspector section twist downs when switching contexts or restarting.
* Fix issues with display layers and graphics becoming disconnected in inspector after insert/delete/move. (`#1212 <https://github.com/nion-software/nionswift/issues/1212>`_)
* Fix issue with custom data group not retaining data items added to it. (`#1189 <https://github.com/nion-software/nionswift/issues/1189>`_)
* Fix issue when clicking on graphic center in inspector. (`#1158 <https://github.com/nion-software/nionswift/issues/1158>`_)
* Add export result dialog to show final export status after exporting. (`#749 <https://github.com/nion-software/nionswift/issues/749>`_)
* Export single data items with same naming options as exporting multiple items. (`#932 <https://github.com/nion-software/nionswift/issues/932>`_)
* Sanitize file names during export to eliminate illegal characters, (`#591 <https://github.com/nion-software/nionswift/issues/591>`_)
* Exporting to CSV only allows 1D or 2D data. (`#245 <https://github.com/nion-software/nionswift/issues/245>`_)
* Improve handling of color table files and allow packages to register custom color tables. (`#707 <https://github.com/nion-software/nionswift/issues/707>`_)
* Fix issue of swapped color channels when exporting to PNG, JPEG, GIF, and BMP.
* Allow user to assign/unassign rectangle used for cropping computation data source inputs using icon overlay.
* Runs with Python 3.13 and Numpy 2.x. Drop Python 3.9 and 3.10 support.

Version 16.11.0 (2024-06-13)
----------------------------
* Computed data items titles update automatically when changing source title. (`#32 <https://github.com/nion-software/nionswift/issues/32>`_)
Expand Down
12 changes: 6 additions & 6 deletions meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

package:
name: 'nionswift'
version: '16.11.0'
version: '16.12.0'

source:
git_rev: 16.11.0
git_rev: 16.12.0
git_url: https://github.com/nion-software/nionswift.git

build:
Expand All @@ -23,10 +23,10 @@ requirements:
- setuptools
run:
- python >=3.11
- nionutils >=4.11.0,<5.0
- niondata >=15.6.2,<16.0
- nionui >=7.1.0,<8.0
- nionswift-io >=15.2,<16.0
- nionutils >=4.12,<5.0
- niondata >=15.7,<16.0
- nionui >=8.0,<9.0
- nionswift-io >=15.3,<16.0
- scipy
- numpy >=2.0,<3.0
- h5py
Expand Down
4 changes: 3 additions & 1 deletion nion/swift/Application.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def __init__(self, ui: UserInterface.UserInterface, set_global: bool = True) ->

ui.set_persistence_handler(PersistenceHandler())
setattr(self.ui, "persistence_root", "3") # sets of preferences
self.version_str = "16.11.0"
self.version_str = "16.12.0"

self.document_model_available_event = Event.Event()

Expand All @@ -217,6 +217,8 @@ def __init__(self, ui: UserInterface.UserInterface, set_global: bool = True) ->

Registry.register_component(Inspector.DeclarativeImageChooserConstructor(self), {"declarative_constructor"})
Registry.register_component(Inspector.DeclarativeDataSourceChooserConstructor(self), {"declarative_constructor"})
Registry.register_component(Inspector.DeclarativeTextPushButtonWidgetConstructor(self), {"declarative_constructor"})
Registry.register_component(Inspector.DeclarativeColorChooserConstructor(self), {"declarative_constructor"})

workspace_manager = Workspace.WorkspaceManager()
workspace_manager.register_panel(SessionPanel.SessionPanel, "session-panel", _("Session"), ["left", "right"], "right", {"min-width": 320, "height": 80})
Expand Down
2 changes: 1 addition & 1 deletion nion/swift/ComputationPanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ def property_changed(key: str) -> None:
if key == "specified_object":
computation_input = computation.get_input(variable.name)
data_item = computation_input.data_item if computation_input else None
display_item = document_model.get_display_item_for_data_item(data_item)
display_item = document_model.get_display_item_for_data_item(data_item) if data_item else None
if display_item:
data_item_thumbnail_source.set_display_item(display_item)

Expand Down
91 changes: 57 additions & 34 deletions nion/swift/DataItemThumbnailWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ def populate_mime_data_for_drag(self, mime_data: UserInterface.MimeData, size: G
return False, None


class BitmapOverlayCanvasItemComposer(CanvasItem.BaseComposer):
def __init__(self, canvas_item: CanvasItem.AbstractCanvasItem, layout_sizing: CanvasItem.Sizing, cache: CanvasItem.ComposerCache, is_active: bool, is_dropping: bool, is_focused: bool) -> None:
super().__init__(canvas_item, layout_sizing, cache)
self.__is_active = is_active
self.__is_dropping = is_dropping
self.__is_focused = is_focused

def _repaint(self, drawing_context: DrawingContext.DrawingContext, canvas_bounds: Geometry.IntRect, composer_cache: CanvasItem.ComposerCache) -> None:
is_active = self.__is_active
is_dropping = self.__is_dropping
is_focused = self.__is_focused
focused_style = "#3876D6" # TODO: platform dependent
with drawing_context.saver():
drawing_context.translate(canvas_bounds.left, canvas_bounds.top)
if is_active:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.round_rect(2, 2, 6, 6, 3)
drawing_context.fill_style = "rgba(0, 255, 0, 0.80)"
drawing_context.fill()
if is_dropping:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.rect(0, 0, canvas_bounds.width, canvas_bounds.height)
drawing_context.fill_style = "rgba(255, 0, 0, 0.10)"
drawing_context.fill()
if is_focused:
stroke_style = focused_style
drawing_context.begin_path()
drawing_context.rect(2, 2, canvas_bounds.width - 4, canvas_bounds.height - 4)
drawing_context.line_join = "miter"
drawing_context.stroke_style = stroke_style
drawing_context.line_width = 4.0
drawing_context.stroke()


class BitmapOverlayCanvasItem(CanvasItem.AbstractCanvasItem):
def __init__(self) -> None:
super().__init__()
Expand Down Expand Up @@ -90,32 +126,8 @@ def is_dropping(self, value: bool) -> None:
self.__is_dropping = value
self.update()

def _repaint(self, drawing_context: DrawingContext.DrawingContext) -> None:
super()._repaint(drawing_context)
# canvas size
canvas_size = self.canvas_size
if canvas_size:
focused_style = "#3876D6" # TODO: platform dependent
if self.__is_active:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.round_rect(2, 2, 6, 6, 3)
drawing_context.fill_style = "rgba(0, 255, 0, 0.80)"
drawing_context.fill()
if self.__is_dropping:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.rect(0, 0, canvas_size.width, canvas_size.height)
drawing_context.fill_style = "rgba(255, 0, 0, 0.10)"
drawing_context.fill()
if self.__is_focused:
stroke_style = focused_style
drawing_context.begin_path()
drawing_context.rect(2, 2, canvas_size.width - 4, canvas_size.height - 4)
drawing_context.line_join = "miter"
drawing_context.stroke_style = stroke_style
drawing_context.line_width = 4.0
drawing_context.stroke()
def _get_composer(self, composer_cache: CanvasItem.ComposerCache) -> typing.Optional[CanvasItem.BaseComposer]:
return BitmapOverlayCanvasItemComposer(self, self.layout_sizing, composer_cache, self.__is_active, self.__is_dropping, self.__is_focused)


class BitmapOverlayCanvasItemComposition(CanvasItem.CanvasItemComposition):
Expand Down Expand Up @@ -358,6 +370,23 @@ def close(self) -> None:
super().close()


class IsLiveOverlayCanvasItemComposer(CanvasItem.BaseComposer):
def __init__(self, canvas_item: CanvasItem.AbstractCanvasItem, layout_sizing: CanvasItem.Sizing, cache: CanvasItem.ComposerCache, is_active: bool) -> None:
super().__init__(canvas_item, layout_sizing, cache)
self.__is_active = is_active

def _repaint(self, drawing_context: DrawingContext.DrawingContext, canvas_bounds: Geometry.IntRect, composer_cache: CanvasItem.ComposerCache) -> None:
is_active = self.__is_active
with drawing_context.saver():
drawing_context.translate(canvas_bounds.left, canvas_bounds.top)
if is_active:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.round_rect(2, 2, 6, 6, 3)
drawing_context.fill_style = "rgba(0, 255, 0, 0.80)"
drawing_context.fill()


class IsLiveOverlayCanvasItem(CanvasItem.AbstractCanvasItem):
def __init__(self) -> None:
super().__init__()
Expand All @@ -373,14 +402,8 @@ def active(self, value: bool) -> None:
self.__active = value
self.update()

def _repaint(self, drawing_context: DrawingContext.DrawingContext) -> None:
super()._repaint(drawing_context)
if self.active:
with drawing_context.saver():
drawing_context.begin_path()
drawing_context.round_rect(2, 2, 6, 6, 3)
drawing_context.fill_style = "rgba(0, 255, 0, 0.80)"
drawing_context.fill()
def _get_composer(self, composer_cache: CanvasItem.ComposerCache) -> typing.Optional[CanvasItem.BaseComposer]:
return IsLiveOverlayCanvasItemComposer(self, self.layout_sizing, composer_cache, self.__active)


class DataItemThumbnailSource(AbstractThumbnailSource):
Expand Down
77 changes: 49 additions & 28 deletions nion/swift/DisplayCanvasItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,48 @@ def handle_auto_display(self) -> bool: ...
def update_display_data_delta(self, display_data_delta: DisplayItem.DisplayDataDelta) -> None: ...


class FrameRateCanvasItemComposer(CanvasItem.BaseComposer):
def __init__(self, canvas_item: CanvasItem.AbstractCanvasItem, layout_sizing: CanvasItem.Sizing, cache: CanvasItem.ComposerCache, fps: str, fps2: str, fps3: str) -> None:
super().__init__(canvas_item, layout_sizing, cache)
self.__fps = fps
self.__fps2 = fps2
self.__fps3 = fps3

def _repaint(self, drawing_context: DrawingContext.DrawingContext, canvas_bounds: Geometry.IntRect, composer_cache: CanvasItem.ComposerCache) -> None:
fps = self.__fps
fps2 = self.__fps2
fps3 = self.__fps3
with drawing_context.saver():
drawing_context.translate(canvas_bounds.left, canvas_bounds.top)
font = "normal 11px serif"
text_pos = canvas_bounds.top_left
drawing_context.begin_path()
drawing_context.move_to(text_pos.x, text_pos.y)
drawing_context.line_to(text_pos.x + 200, text_pos.y)
drawing_context.line_to(text_pos.x + 200, text_pos.y + 60)
drawing_context.line_to(text_pos.x, text_pos.y + 60)
drawing_context.close_path()
drawing_context.fill_style = "rgba(255, 255, 255, 0.6)"
drawing_context.fill()
drawing_context.font = font
drawing_context.text_baseline = "middle"
drawing_context.text_align = "left"
drawing_context.fill_style = "#000"
drawing_context.fill_text("display:" + fps, text_pos.x + 8, text_pos.y + 10)
drawing_context.fill_text("frame:" + fps2, text_pos.x + 8, text_pos.y + 30)
drawing_context.fill_text("update:" + fps3, text_pos.x + 8, text_pos.y + 50)
drawing_context.statistics("display")


class FrameRateCanvasItem(CanvasItem.AbstractCanvasItem):
"""Display frame rate in the display canvas item.
There are three rates that are tracked. The "display" rate is the rate at which the display is updated and is
"ticked" when low level repaint occurs, which generally happens once per update. The "frame" rate is the rate at
the frame index changes. The "update" rate is the rate at which the display is updated and is "ticked" when the
display is updated. It is different from the "display" since it is just a request to update; whereas the "display"
is the actual repaint being performed.
"""
def __init__(self) -> None:
super().__init__()
self.__display_frame_rate_id: typing.Optional[str] = None
Expand Down Expand Up @@ -149,31 +190,11 @@ def frame_tick(self, frame_index: int) -> None:
Utility.fps_tick("update_" + self.__display_frame_rate_id)
self.update()

def _repaint(self, drawing_context: DrawingContext.DrawingContext) -> None:
super()._repaint(drawing_context)
display_frame_rate_id = self.__display_frame_rate_id
canvas_bounds = self.canvas_bounds
if canvas_bounds and display_frame_rate_id:
Utility.fps_tick("display_" + display_frame_rate_id)
fps = Utility.fps_get("display_" + display_frame_rate_id)
fps2 = Utility.fps_get("frame_" + display_frame_rate_id)
fps3 = Utility.fps_get("update_" + display_frame_rate_id)
with drawing_context.saver():
font = "normal 11px serif"
text_pos = canvas_bounds.top_left
drawing_context.begin_path()
drawing_context.move_to(text_pos.x, text_pos.y)
drawing_context.line_to(text_pos.x + 200, text_pos.y)
drawing_context.line_to(text_pos.x + 200, text_pos.y + 60)
drawing_context.line_to(text_pos.x, text_pos.y + 60)
drawing_context.close_path()
drawing_context.fill_style = "rgba(255, 255, 255, 0.6)"
drawing_context.fill()
drawing_context.font = font
drawing_context.text_baseline = "middle"
drawing_context.text_align = "left"
drawing_context.fill_style = "#000"
drawing_context.fill_text("display:" + fps, text_pos.x + 8, text_pos.y + 10)
drawing_context.fill_text("frame:" + fps2, text_pos.x + 8, text_pos.y + 30)
drawing_context.fill_text("update:" + fps3, text_pos.x + 8, text_pos.y + 50)
drawing_context.statistics("display")
def _get_composer(self, composer_cache: CanvasItem.ComposerCache) -> typing.Optional[CanvasItem.BaseComposer]:
if self.__display_frame_rate_id:
fps = Utility.fps_get("display_" + self.__display_frame_rate_id)
fps2 = Utility.fps_get("frame_" + self.__display_frame_rate_id)
fps3 = Utility.fps_get("update_" + self.__display_frame_rate_id)
return FrameRateCanvasItemComposer(self, self.sizing, composer_cache, fps, fps2, fps3)
return CanvasItem.EmptyCanvasItemComposer(self, self.sizing, composer_cache)

Loading

0 comments on commit 275fffb

Please sign in to comment.