diff --git a/.gitignore b/.gitignore index bfc40fde..ad1fb101 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ imagepy/plugins/ # Distribution / packaging .Python env/ +.metadata/ build/ develop-eggs/ dist/ @@ -71,6 +72,7 @@ target/ # IPython Notebook .ipynb_checkpoints +.ipynb # pyenv .python-version @@ -90,3 +92,7 @@ ENV/ # Rope project settings .ropeproject + +# Pycharm project +.idea/ +*.nov diff --git a/ImagePy.bat b/ImagePy.bat old mode 100755 new mode 100644 diff --git a/README.md b/README.md index c2cd24d8..47946596 100644 --- a/README.md +++ b/README.md @@ -1,227 +1,288 @@ -ImagePy Basic Tutorial -============== -[https://github.com/yxdragon/imagepy.git](https://github.com/yxdragon/imagepy.git) +# Introduction -**Introduction:** -ImagePy is an image processing software developed in Python, supporting bmp, rgb, png and other commonly used image formats. It can handle grayscale images and multi-channel (color) images, and supports image stack (sequence) operations. It supports a variety of selection operations (point, line, surface, multi-line, multi-face, hollow polygon). It can carry out a variety of commonly used mathematical operations, commonly used filter operation, image measurements, as well as pixel statistics. It can carry on dem surface reconstruction and three-dimensional reconstruction of image sequences. And the framework is based around Python development. The image data is represented by numpy. And thus it can easily access scikit-image, opencv, itk, mayavi and other third-party mature image processing libraries. +ImagePy is an open source image processing framework written in Python. Its UI interface, image data structure and table data structure are wxpython-based, Numpy-based and pandas-based respectively. Furthermore, it supports any plug-in based on Numpy and pandas, which can talk easily between scipy.ndimage, scikit-image, simpleitk, opencv and other image processing libraries. -Download and install ------------------------ -**works on windows, linux, mac, under python2.7 and python3.4+** +![newdoc01](http://idoc.imagepy.org/imgs/newdoc01.png) +
Overview, mouse measurement, geometric transformation, filtering, segmentation, counting, etc.

-```bash -# Now ImagePy is on Pypi -pip install imagepy +![newdoc02](http://idoc.imagepy.org/imgs/newdoc02.png) +
If you are more a IJ-style user, try `Windows -> Windows Style` to switch

-# Or install with conda -conda install imagepy +ImagePy: +- has a user-friendly interface +- can read/save a variety of image data formats +- supports ROI settings, drawing, measurement and other mouse operations +- can perform image filtering, morphological operations and other routine operations +- can do image segmentation, area counting, geometric measurement and density analysis. +- is able to perform data analysis, filtering, statistical analysis and others related to the parameters extracted from the image. -# Then start imagepy like this -python -m imagepy -``` -**some trouble** -1. ImagePy is a ui framework based on wxpython, which can not install with pip on Linux. You need download **[the whl acrodding to your Linux system](https://wxpython.org/pages/downloads/)**. -2. On Linux and Mac, there may be permission denied promblem, for ImagePy will write some config information, So please **start with sudo**. If you install with pip, please add --user parameter like this: **pip install -user imagepy** -3. If you install ImagePy in a Anaconda virtual environment, you may got a error when start like this: **This program needs access to the screen. Please run with a Framework -build of python, and only when you are logged in on the main display**, if so, please start with pythonw -m imagepy. +Our long-term goal of this project is to be used as ImageJ + SPSS (although not achieved yet) -Main Interface ----------------- -![](http://idoc.imagepy.org/imgs/main.png "main") -The main interface consists of four parts, from top to bottom: the title bar, menu bar, toolbar, and status bar. -Here are a few examples to illustrate what ImagePy can do. +## Installation -First example: Mathematical operations, filter operations. --------------------------------------------------------------- -![](http://idoc.imagepy.org/imgs/astadjust.png "pixcels") +__OS support:windows, linux, mac, with python3.x__ -**Selection Introduction**: -Selection refers to processing the image only in the the specific identification areas on the image. ImagePy supports single point, multi-point, single line, multi-line, rectangular, circular, arbitrary polygon and free curve selection. It can superimpose something using Shift key, hollow out something using Ctrl key. In addition, all the selection objects can carry out expansion, shrink, convex hull and other geometric operations. +1. ImagePy is a ui framework based on wxpython, which can not be installed + with pip on Linux. You need download [the whl according to your + Linux system](https://wxpython.org/pages/downloads/). +2. On Linux and Mac, there may be permission denied promblem, for + ImagePy will write some config information, so please start with + sudo. If you install with pip, please add \--user parameter like + this: pip install --user imagepy +3. If you install ImagePy in an Anaconda virtual environment, you may + get a error when starting like this: This program needs access to the + screen. Please run with a Framework build of python, and only when + you are logged in on the main display, if so, please start with + pythonw -m imagepy. -![](http://idoc.imagepy.org/imgs/astroi.png "ROI") +### - Pre-compiled package +This is the simplest option to run ImagePy. +A precompiled archive can be downloaded from the [release tab](https://github.com/Image-Py/imagepy/releases) of the repository. +Simply unzip the archive and run the ImagePy.bat file. +This will open a command line window and open the GUI of ImagePy. -**Geometric Transformation:**ImagePy supports geometric transformations. It can carry out rotation, translation and other conventional matrix transformations. What’s more, these rotations are interactive and support selection. +### - Using pip +In a command-prompt type `pip install imagepy`. +~~On Windows you currently need to first install shapely using conda.~~ This should also work for windows, now that shapely is available via pip. +Once installed, ImagePy can be run by typing `python -m imagepy` in a command prompt. -![](http://idoc.imagepy.org/imgs/asttransform.png "transform") -Second example: An example of a cell count ----------------------------------------------- -**Look up table introduction:** -![](http://idoc.imagepy.org/imgs/indexcolor.png "index color") +## Citation: +[ImagePy: an open-source, Python-based and platform-independent software package for bioimage analysis](https://academic.oup.com/bioinformatics/article/34/18/3238/4989871) -**Index color** is also called false color. The essence of it is to map the gray color to a predefined spectrum. The index color does not increase the amount of information in the image, but does enhance the visual contrast. +## Forum -![](http://idoc.imagepy.org/imgs/cell1.png "cell counter") -![](http://idoc.imagepy.org/imgs/cell2.png "cell counter") +ImagePy is a community partner of forum.image.sc, Anything about the usage and development of ImagePy could be discussed in https://forum.image.sc. -Here, for a cell under a microscope, we organize the image and compute statistics. -1. Open the original image and go on Gaussian blur to anti-noise. -2. In order to highlight the cells, a large-scale USM mask treatment was performed. -3. After processing the picture, it is easy to use the threshold function to carry on binarization. -4. Label the binary image, mark unicom area. -5. Calculate the centroid of each Unicom area -6. Calculate the area occupied by each cell +## Contribute +**Contribute Manual:** All markdown file under [doc folder](https://github.com/Image-Py/imagepy/tree/master/imagepy/doc) be parsed as manual. Plugins and manual are paired by plugins's title and manual's file name. We can browse document from the parameter dialog's Help button. We need more manual contributors, just pull request markdown file [here](https://github.com/Image-Py/imagepy/tree/master/imagepy/doc). -Third example: Image matching ----------------------------------- +**Contribute Plugins:** Here is a [demo plugin](https://github.com/Image-Py/demoplugin) repositories with document to show how to write plugins and publish on ImagePy. You are wellcom and feel free to contact with us if you need help. -Use the Surf feature matching algorithm implemented in OpenCV. -![](http://idoc.imagepy.org/imgs/surf.png "surf") +**Improve Main Framework:** Just fork ImagePy, then give Pull Request. But if you want to add some new feature, Please have a issue with us firstly. -1. The two graphs are covered by points, that is, Surf feature points, where the correct match is shown in yellow. -2. Also output a log of the opations. Identify the feature points of the two graphs, the correct number of matches, and the rotation matrix between the two graphs. -3. When a point is clicked with the mouse, the dot will be red with the corresponding match point of the other picture at the same time. +## Basic operations: -Fourth example: Dem Reconstruction --------------------------------------- -Use the mayavi library, to perform a large number of three-dimensional reconstructions and three-dimensional visualization functions. +ImagePy has a very rich set of features, and here, we use a specific example to show you a glimpse of the capacity of ImagePy. We choose the official coin split of scikit-image, since this example is simple and comprehensive. -![](http://idoc.imagepy.org/imgs/dem.png "DEM") +### Open image -**Dem** is the digital elevation model, which means that the brightness of the image represents the elevation. Through the Dem data, you can calculate the height, slope. You can draw contours, and perform surface reconstruction. +`menu: File -> Local Samples -> Coins` to open the sample image within ImagePy. +_PS: ImagePy supports bmp, jpg, png, gif, tif and other commonly used file formats. By installing ITK plug-in,dicom,nii and other medical image formats can also be read/saved. It is also possible to read/write wmv, avi and other video formats by installing OpenCV._ -Fifth example: CT data 3D reconstruction --------------------------------------------- +![newdoc03](http://idoc.imagepy.org/imgs/newdoc03.png) +
Coins

-The following image represents dental MicroCT data. The data were filtered, segmented and three-dimensional reconstructed, as well as visually manipulated. -![](http://idoc.imagepy.org/imgs/teeth.png "teeth") -The figure above is a tooth CT data. Importing the image sequence, you can view the three views, and then go on its three-dimensional reconstruction. +### Filtering & Segmentation -**Image Stack:** ImagePy supports image stack processing, it has the following two characteristics: +`menu:Process -> Hydrology -> Up And Down Watershed` Here, a composite filter is selected to perform sobel gradient extraction on the image, and then the upper and lower thresholds are used as the mark, and finally we watershed on the gradient map. +Filtering and segmentation are the crucial skills in the image processing toolkit, and are the key to the success or failure of the final measurement. +Segmentation methods such as adaptive thresholds, watersheds and others are also supported. -1. Images in the image stack have the same format and the same size. -2. They will act on each image in the stack when processed. +![newdoc04](http://idoc.imagepy.org/imgs/newdoc04.png) +
Up And Down Watershed

-Plugins and Macros: -------------------- +![newdoc05](http://idoc.imagepy.org/imgs/newdoc05.png) +
Mask

-In ImagePy itself, each functional component is plug-in (all menus, tools). The implementation of each function, in essence, is through interaction to get a group of parameters and then act on the current image. We can view the plug-in's organizational structure in Plugin Tree View, find plug-ins quickly in Plugin List View, record macros in Macros Recorder, and batch process when needed to do series of related functions and improve work efficiency. +### Binarization -![](http://idoc.imagepy.org/imgs/plgview.png "view") -From the two views above, you can get a global view of all the plug-ins, like viewing its related information, introduction, and source code. You can -quickly find the commands. You can run a related command directly by double-clicking. +`menu:Process -> Binary -> Binary Fill Holes` After the segmentation, we obtained a relatively clean mask image, but there is still some hollowing out, as well as some impurities, which will interfere with counting and measurement. +_ImagePy supports binary operations such as erode, dilate, opening and closing, as well as skeletonization, central axis extraction, and distance transformation._ -**Macro Recording of Cell Count Example:** -We open the Plugins -> Macros -> Macros Recorder plug-in, and then re-operate the cell counting process... -![](http://idoc.imagepy.org/imgs/step.png "step") +![newdoc06](http://idoc.imagepy.org/imgs/newdoc06.png) +
Fill Holes

-After each step, Macros Recorder will add a log. When all is completed, you can get the following log: +### Geometry filtering -These logs, each line essentially records “plug-in name> {parameter}”. Click “Run> Run Macros (F5)” to perform each action of the record in turn. You can also use the mouse to select a line or a few lines. Click “Run> Run Line (F6)” to implement the selected line. In addition macros have the following characteristics. +`menu:Analysis -> Region Analysis -> Geometry Filter` ImagePy can perform geometric filtering based on :__the area, the perimeter, the topology, the solidity, the eccentricity__ and other parameters. You can also use multiple conditions for filtering. Each number can be positive|negative, which indicates the kept object will have the corresponding parameter greater|smaller than the value respectively. The kept objects will be set to the front color, the rejected ones will be set to the back color. In this demo, the back color is set to 100 in order to see which ones are filtered out. Once satisfied with the result, set the back color to 0 to reject them. In addition, ImagePy also supports gray density filtering, color filtering, color clustering and other functions. -![](http://idoc.imagepy.org/imgs/macros.png "macros") +![newdoc07](http://idoc.imagepy.org/imgs/newdoc07.png) +
Geometry filtering (the area is over-chosen to emphasize the distinction)

-1. You can save a file where the suffix of macro is (.mc). You can run the specified macro file via Plugins -> Macros -> Run Macros. -2. Put the macro file on the menus directory or any of its subdirectories in the project. Starting once again, the macro will be loaded as a menu item. The title is the file name. In fact, some project function are in series by the macro. -Extend a filter: ----------------- +### Geometry Analysis -The examples above only list some of the functionality of the ImagePy. However, ImagePy is not only an image processing program, but a highly scalable framework. Any numpy-based processing function can be easily incorporated. For example, to make a Gaussian blur filter, we only need: +`menu:Process -> Region Analysis -> Geometry Analysis` Count the area and analyze the parameters. By choosing the `cov` option, ImagePy will fit each area with an ellipse calculated via the covariance. +The parameters such as area, perimeter, eccentricity, and solidity shown in the previous step are calculated here. In fact, the filtering of the previous step is a downstream analysis of this one. -``` python -# -*- coding: utf-8 -* -import scipy.ndimage as nimg -from imagepy.core.engine import Filter +![newdoc08](http://idoc.imagepy.org/imgs/newdoc08.png) +
Geometry Analysis

-class Gaussian(Filter): - title = 'Gaussian' - note = ['all', 'auto_msk', 'auto_snap','preview'] +![newdoc09](http://idoc.imagepy.org/imgs/newdoc09.png) +
Generate the result table (dark to emphasize the ellipse)

- #parameter - para = {'sigma':2} - view = [(float, (0,30), 1, 'sigma', 'sigma', 'pix')] - #process - def run(self, ips, snap, img, para = None): - nimg.gaussian_filter(snap, para['sigma'], output=img) -``` +### Sort Table by area + +`menu:Table -> Statistic -> Table Sort By Key` Select the major key as area, and select descend. The table will be sorted in descending order of area. A table is another important piece of data other than an image. In a sense, many times we need to get the required information on the image and then post-process the data in the form of a table. ImagePy supports table I/O (xls, xlsx, csv), filtering, slicing, statistical analysis, sorting and more. (Right click on the column header to set the text color, decimal precision, line style, etc.) + +![newdoc10](http://idoc.imagepy.org/imgs/newdoc10.png) +
Table

+ + +### Charts + +`menu:Table -> Chart -> Hist Chart` From tabular data, we often need to draw a graph. Here, we plot the histograms of the area and the perimeter columns. ImagePy's tables can be used to draw common charts such as line charts, pie charts, histograms, and scatter plots (matplotlib-based). The chart comes with zooming, moving and other functions. The table can also be saved as an image. + +![newdoc11](http://idoc.imagepy.org/imgs/newdoc11.png) +
Histograms

+ + +### 3D chart + +`menu:Kit3D -> Viewer 3D -> 2D Surface` Surface reconstruction of the image. This image shows the three reconstructed results including, sobel gradient map, high threshold and low threshold. It shows how the Up And Down Watershed works: +- calculate the gradient. +- mark the coin and background through the high and low thresholds, +- simulate the rising water on the dem diagram to form the segmentation. + +ImagePy can perform 3D filtering of images, 3D skeletons, 3D topological analysis, 2D surface reconstruction, and 3D surface visualization. The 3D view can be freely dragged, rotated, and the image results can be saved as a .stl file. -![](http://idoc.imagepy.org/imgs/filter.png "filter") +![newdoc12](http://idoc.imagepy.org/imgs/newdoc12.png) +
3D visualisation

-Create a Filter: -1. Import related class library. It can be third-party or implemented in C. -2. Create a class that subclasses `Filter`. -3. `title`, this line is the name of the plug-in and will also serve as the title of the menu. -4. `note` indicates how the plug-in works and the associated preprocessing and post-processing work. This includes which types of images can be processed, whether selections are supported or not, and so on. -5. `para` is the parameter the kernel function needs. -6. `view` and `para` are corresponding. They inform how the various parameters of the framework plug-in interact (the framework will generate your interactive interface automatically). -7. `run` is the core function, if conditional, given `img` to the results of the process (save memory), or return out (the framework will help you give img). -8. Place the file in any subdirectory of menus in the project, and it will be loaded as a menu item at startup. -**What has the framework helped us do?** +### Macro recording and execution -They framework enables complex tasks in a uniform way. Simply, you do not need to determine for yourself whether the image type is legitimate. You do not need to make your own image cache to support undo. You do not need to support the selection by yourself. Do not need to monitor the interface by yourself to achieve real-time preview. You do not need to write any interface code after you have defined the required parameters, the type and the range of values for each parameter. When a color image is encountered, the each channel of the image is processed sequentially. When the image stack is encountered, each frame is automatically traversed. You are free to work on either the task -of image analysis, or creating new plugins for the framework. +`menu:Window -> Develop Tool Suite` Macro recorder is shown in the develop tool panel. We have manually completed an image segmentation. However, batch processing more than 10 images can be tedious. So, assuming that these steps are highly repeatable and robust for dealing with such problems, we can record a macro to combine several processes into a one-click program. The macro recorder is similar to a radio recorder. When it is turned on, each step of the operation will be recorded. We can click the pause button to stop recording, then click the play button to execute. When the macro is running, the recorded commands will be executed sequentially, therefore achieving simplicity and reproducibility. -Extend a Tool: --------------- -Another scenario is to interact on the canvas through the mouse, like the selection operations mentioned above. Here is an example of a brush: +Macros are saved into .mc files. drag and drop the file to the status bar at the bottom of ImagePy, the macro will be executed automatically. we can also copy the .mc file to the submenu of the menus under the ImagePy file directory. When ImagePy is started, the macro file will be parsed into a menu item at the corresponding location. By clicking the menu, the macro will also be executed. -``` python -from imagepy.core.draw import paint -from imagepy.core.engine import Tool -import wx +![newdoc13](http://idoc.imagepy.org/imgs/newdoc13.png) +
Macro Recording

-class Plugin(Tool): - title = 'Pencil' - para = {'width':1} - view = [(int, (0,30), 0, 'width', 'width', 'pix')] - def __init__(self): - self.sta = 0 - self.paint = paint.Paint() - self.cursor = wx.CURSOR_CROSS +### Workflow - def mouse_down(self, ips, x, y, btn, **key): - self.sta = 1 - self.paint.set_curpt(x,y) - ips.snapshot() +A macro is a sequence of predefined commands. By recording a series of fixed operations into macros, you can improve your work efficiency. However, the disadvantage is the lack of flexibility. For example, sometimes the main steps are fixed, but the parameter tuning needs human interaction. In this case, the workflow is what you want. A workflow in ImagePy is a flow chart that can be visualized, divided into two levels: __chapters and sections__. +The chapter corresponds to a rectangular area in the flow chart, and the section is a button in the rectangular area, which is also a command and is accompanied by a graphic explanation. The message window on the right will display the corresponding function description, while mousing hovering above. Click on the `Detail Document` in the top right corner to see the documentation of the entire process. - def mouse_up(self, ips, x, y, btn, **key): - self.sta = 0 - def mouse_move(self, ips, x, y, btn, **key): - if self.sta==0:return - self.paint.lineto(ips.img,x,y, self.para['width']) - ips.update = True +The workflow is actually written in MarkDown (a markup language), but it needs to be written respecting several specifications, as follows: - def mouse_wheel(self, ips, x, y, d, **key):pass +```markdown +Title +===== +## Chapter1 +1. Section1 +some coment for section1 ... +2. ... +## Chapter 2 + ... ``` +![newdoc14](http://idoc.imagepy.org/imgs/newdoc14.png) +
Workflow

-![](http://idoc.imagepy.org/imgs/painter.png "painter") +### Report Plugin + +Sometimes we need to make a report to print or generate a PDF document. ImagePy can generate report from a xlsx template. We just need put specific mark in some cells, ImagePy will parse the template and generate a parameter dialog, then we can input some information, or give image/table in, the report will be generated! more about how to make template please see [here](https://github.com/Image-Py/demoplugin/blob/master/doc/report.md). + +![newdoc14](http://idoc.imagepy.org/demoplugin/38.png) + +
generate report

+ +### Filter Plugin + +We introduced macros and workflows in the last sections, using macros and workflows to connect existing functions is convenient. But sometimes we need to create new features. In this section, we are trying to add a new feature to ImagePy. ImagePy can easily access any Numpy-based function. Let's take the Canny operator of scikit-image as an example. + +```python +from skimage import feature +from imagepy.core.engine import Filter + +class Plugin(Filter): + title = 'Canny' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'sigma':1.0, 'low_threshold':10, 'high_threshold':20} + + view = [(float, 'sigma', (0,10), 1, 'sigma', 'pix'), + ('slide', 'low_threshold', (0,50), 4, 'low_threshold'), + ('slide', 'high_threshold', (0,50), 4, 'high_threshold')] + +def run(self, ips, snap, img, para = None): + return feature.canny(snap, para['sigma'], para['low_threshold'], + para['high_threshold'], mask=ips.get_msk())*255 +``` +![newdoc15](http://idoc.imagepy.org/imgs/newdoc15.png) +
Canny Filter Demo

+ +#### Steps to create a your own filter: + +1. Import the package(s), often third party. +2. Inherit the __`Filter`__ class。 +3. The __`title`__ will be used as the name of the menu and the title of the parameter dialog, also as a command for macro recording. +4. Tell the framework what needs to do for you in __`Note`__, whether to do type checking, to support the selection, to support _UNDO_, etc. +5. __`Para`__ is the a dictionary of parameters, including needed parameters for the + functions. +6. Define the interaction method for each of the parameters in __`View`__, the framework will automatically generate the dialog for parameter tuning by reading these information. +7. Write the core function __`run`__. `img` is the current image, `para` is the result entre by user. if `auto_snap` is set in `note`, `snap` will be a duplicate of `img`. We can process the `snap`, store the result in `img`. If the function does not support the specified output, we can also return the result, and the framework will help us copy the result to img and display it. +8. Save the file as `xxx_plg.py` and copy to the `menu` folder, restart ImagePy. + It will be loaded as a menu item. + +#### What did the framework do for us? + +The framework unifies the complex tasks in a formal manner and helps us to perform: +- type checking. If the current image type does not meet the requirements in the note, the analysis is terminated. +- according to the `para`, generate automatically a dialog box to detect the input legality from the `view`. +- Real-time preview +- automatic ROI support +- undo support +- parallelization support +- image stack support +- etc. + +### Table + +As mentioned earlier, the table is another very important data type other than the image. Similarly, ImagePy also supports the extension of table. Here we give an example of sorting-by-key used in the previous description. + +```python +from imagepy.core.engine import Table +import pandas as pd + +class Plugin(Table): + title = 'Table Sort By Key' + para = {'major':None, 'minor':None, 'descend':False} + + view = [('field', 'major', 'major', 'key'), + ('field', 'minor', 'minor', 'key'), + (bool, 'descend', 'descend')] + +def run(self, tps, data, snap, para=None): + by = [para['major'], para['minor']] + data.sort_values(by=[i for i in by if i != 'None'], + axis=0, ascending = not para['descend'], inplace=True) +``` +![newdoc16](http://idoc.imagepy.org/imgs/newdoc16.png) +
Table Sort Demo

-**Create Tool:** +#### How Table works -1. Inherited from `Tool` in the `engine`. -2. Specify the `title`, which will be the tool name, and the message of the status bar. -3. Adds several methods to achieve mouse_down, mouse_up, mouse_move, mouse_wheel. -4. If the tool requires parameters (for example, pen width), use the dictionary to assign to `para`. Similarly, `view` specifies its interactive interface. When the tool is double-clicked, the dialog box will pop-up in accordance with the specified interface. -5. Files are stored in the sub-folder of tools, with a generated 16 * 16 thumbnail icon. The icon and the tool are stored in the same name as the gif file. +Same as `Filter`,`Table` also has parameters such as `title`,`note`,`para`,`view`. +When the plugin is running, the framework will generate a dialog box according to `para` +and `view`. After the parameters are chosen, they are passed to the `run` together with the current table and be processed. The table data is a pandas.DataFrame object in the current table, stored in `tps`. Other information, such as `tps.rowmsk`, `tps.colmsk` can also be retrieved from `tps` to get the row and column mask of the current selected table. -About ImagePy: --------------- -The above only lists some features of ImagePy, covering the basic mathematical operations, filters, pixel statistics, a slightly complex feature extraction, 3D reconstruction and other functions. It gives a brief introduction to macros , how to write new filters, tools and integrate them like ImagePy. Stay tuned for more detail in the coming manual and development documents. +### Other type of plugins -I (yxdragon) have used ImageJ for a long time and also used to use Python for scientific computing. ImageJ's outstanding plug-in design philosophy allows it to absorb the contributions of industry professionals quickly. However, Python has an advantage over Java in image processing. +The `Filter` and `Table` described above are the two most important plugins, but ImagePy also supports some other types of plugin extensions. There are currently ten, they are: -1. Java is a system language, the relative threshold of it is relatively high. -2. The related open source libraries under Java are not as rich as C / C++. -3. Python has simple grammar so it is easy to learn. It is a good choice for non-computer professionals. -4. Python has a wealth of third-party extensions, such as Scikit-image, OpenCV, Matplotlib, Mayavi, etc. -5. Almost all scientific computing class libraries are based on Numpy! so the framework can be built up easily. -6. Python can be extended by C/C++ with ctypes/cython. +1. `Filter`: mainly for image processing +2. `Simple`: similar to `Filter`, but focus on the overall characteristics of the image, such as the operation of the ROI, the operation of the false color, the area measurement, or the three-dimensional analysis of the entire image stack, visualization, and so on. +3. `Free`: operate that are independant of image. Used to open image, close software etc. +4. `Tool`: use the mouse to interact on the diagram and show small icons on the toolbar, such as a brush. +5. `Table`: operate on the table, such as statistics analysis, sorting, plotting. +6. `Widget`: widgets that are displayed in panels, such as the navigation bar on the right, the macro recorder, and others. +7. `Markdown`: markup language, when clicked, a separate window will pop up to display the document. +8. `Macros`:command sequence file for serially fixed operational procedures. +9. `Workflow`: combination of macro and MarkDown to create an interactive guidance process. +10. `Report`: a xlsx template with specific mark, rename as `.rpt`, used to auto generate report. -Because of busy work, I wrote ImagePy in my spare time. All of the development work lasted about two months. Personally I think that this efficiency is mainly due to a large number of third-party libraries of Python as well as the project’s "borrowlism" design ideas. The project uses wxpython as the interface library, Numpy as the base data type. Because the time is short, many interactive details of the plug-in will show problems, you please give a positive feedback to me. I will do my best to safeguard the healthy growth of this project. +## Motivation & Goal -[https://github.com/yxdragon/imagepy.git](https://github.com/yxdragon/imagepy.git) +Python is a simple, elegant, powerful language, and has very rich third-party libraries for scientific computing. Based on the universal matrix structure and the corresponding rules, numpy-based libraries such as scipy, scikit-image, scikit-learn and other scientific computing libraries have brought great convenience to scientific research. On the other hand, more and more problems in biology, material science and other scientific research can be efficiently and accurately solved via scientific computing, image processing. -![](http://idoc.imagepy.org/imgs/me.png "me") -It is me, yxdragon. -Email:imagepy@sina.com +However there are still many researchers that lack programming skills. Thus it is a crucial to make the Numpy-based scientific computing libraries available to more researchers. ImagePy brings the computing capacities closer to the non-programmer researchers, so that they won't need to be concerned about the UI and interaction design, and focus exclusively on the algorithm itself, and finally, accelerate open-source tool building or even commercial products incubation. These tools, meanwhile, can let more researchers, who are not good at programming, gain, promote and popularize scientific knowledge such as image processing and statistics. diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..bec63780 --- /dev/null +++ b/environment.yml @@ -0,0 +1,23 @@ +name: imagepy +channels: + - conda-forge + - nsls2forge +dependencies: + - numba + - numpy-stl + - openpyxl + - pandas + - pydicom + - pypubsub + - read-roi + - scikit-image + - scikit-learn + - shapely + - wxpython + - xlrd + - xlwt + - markdown + - python-markdown-math + - moderngl + - dulwich + - pystackreg diff --git a/imagepy/IPy.py b/imagepy/IPy.py deleted file mode 100644 index 10a8aa27..00000000 --- a/imagepy/IPy.py +++ /dev/null @@ -1,217 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Oct 15 10:03:00 2016 - -@author: yxl -""" - -import wx, os.path as osp -from wx.lib.pubsub import pub - -root_dir = osp.abspath(osp.dirname(__file__)) - -curapp = None - -def load_plugins(): - from imagepy.core.loader import loader - loader.build_plugins('menus', True) - from glob import glob - extends = glob('plugins/*/menus') - for i in extends: loader.build_plugins(i, True) - -def uimode(): - if curapp is None: return 'no' - from .core import manager - return manager.ConfigManager.get('uistyle') - -def get_window(): - from .core import manager - return manager.WindowsManager.get() - -def get_twindow(): - from .core import manager - return manager.WTableManager.get() - -def get_ips(): - from .core import manager - return manager.ImageManager.get() - -def get_tps(): - from .core import manager - return manager.TableManager.get() - # return None if win==None else win.canvas.ips - -def showips(ips): - if uimode()=='ipy': - from .ui.canvasframe import CanvasPanel - canvasp = CanvasPanel(curapp.canvasnb) - canvasp.set_ips(ips) - curapp.canvasnb.add_page( canvasp, ips) - #canvasp.canvas.initBuffer() - - curapp.auimgr.Update() - elif uimode()=='ij': - from .ui.canvasframe import CanvasFrame - frame = CanvasFrame(curapp) - frame.set_ips(ips) - frame.Show() - -pub.subscribe(showips, 'showips') -def show_ips(ips): - if uimode()=='no': - from .core import manager - from .ui.canvasframe import VirturlCanvas - frame = VirturlCanvas(ips) - print('ImagePy New ImagePlus >>> %s'%ips.title) - else: wx.CallAfter(pub.sendMessage, 'showips', ips=ips) - -def showimg(imgs, title): - print('show img') - from .core import ImagePlus - ips = ImagePlus(imgs, title) - showips(ips) - -pub.subscribe(showimg, 'showimg') -def show_img(imgs, title): - if uimode()=='no': - from .core import manager, ImagePlus - from .ui.canvasframe import VirturlCanvas - frame = VirturlCanvas(ImagePlus(imgs, title)) - else:wx.CallAfter(pub.sendMessage, 'showimg', imgs=imgs, title=title) - -def reloadplgs(report=False, menus=True, tools=False, widgets=False): - print('reload........') - curapp.reload_plugins(report, menus, tools, widgets) - -pub.subscribe(reloadplgs, 'reload') -def reload_plgs(report=False, menus=True, tools=False, widgets=False): - print('reload========') - wx.CallAfter(pub.sendMessage, 'reload', report=report, menus=menus, tools=tools, widgets=widgets) - -def showmd(title, cont, url=''): - from .ui.mkdownwindow import MkDownWindow - MkDownWindow(curapp, title, cont, url).Show() - -pub.subscribe(showmd, 'showmd') -def show_md(title, cont, url=''): - wx.CallAfter(pub.sendMessage, 'showmd', title=title, cont=cont, url=url) -''' -def stepmacros(macros): - macros.next() - -pub.subscribe(stepmacros, 'stepmacros') -def step_macros(macros): - wx.CallAfter(pub.sendMessage, "stepmacros", macros=macros) -''' -def alert(info, title="ImagePy Alert!"): - if uimode()=='no': - print('ImagePy Alert >>> %s'%title) - print(info) - else: - dlg=wx.MessageDialog(curapp, info, title, wx.OK) - dlg.ShowModal() - dlg.Destroy() - -# MT alert = lambda info, title='image-py':callafter(alert_, *(info, title)) - -def yes_no(info, title="ImagePy Yes-No ?!"): - dlg = wx.MessageDialog(curapp, info, title, wx.YES_NO | wx.CANCEL) - rst = dlg.ShowModal() - dlg.Destroy() - dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} - return dic[rst] - -def getpath(title, filt, k, para=None): - """Get the defaultpath of the ImagePy""" - from .core import manager - dpath = manager.ConfigManager.get('defaultpath') - if dpath ==None: - dpath = root_dir # './' - dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} - dialog = wx.FileDialog(curapp, title, dpath, '', filt, dic[k]) - rst = dialog.ShowModal() - path = None - if rst == wx.ID_OK: - path = dialog.GetPath() - dpath = osp.split(path)[0] - manager.ConfigManager.set('defaultpath', dpath) - if para!=None:para['path'] = path - dialog.Destroy() - - return rst if para!=None else path - -def getdir(title, filt, para=None): - from .core import manager - dpath = manager.ConfigManager.get('defaultpath') - if dpath ==None: - dpath = root_dir - dialog = wx.DirDialog(curapp, title, dpath ) - rst = dialog.ShowModal() - path = None - if rst == wx.ID_OK: - path = dialog.GetPath() - if para!=None:para['path'] = path - dialog.Destroy() - return rst if para!=None else path - -def get_para(title, view, para): - from .ui.panelconfig import ParaDialog - pd = ParaDialog(curapp, title) - pd.init_view(view, para) - rst = pd.ShowModal() - pd.Destroy() - return rst - -def showtable(data, title): - from .core import TablePlus - if uimode()=='ipy': - from .ui.tableframe import TablePanel - tps = TablePlus(data, title) - tablep = TablePanel(curapp.tablenb) - tablep.set_tps(tps) - curapp.tablenb.add_page( tablep, tps) - info = curapp.auimgr.GetPane(curapp.tablenb) - info.Show(True) - curapp.auimgr.Update() - elif uimode()=='ij': - from .ui.tableframe import TableFrame - tps = TablePlus(data, title) - frame = TableFrame(curapp) - frame.set_tps(tps) - frame.Show() - - # MT callafter(TableLog.table, *(title, data, cols, rows)) - -pub.subscribe(showtable, 'showtable') -def show_table(data, title): - wx.CallAfter(pub.sendMessage, "showtable", data=data, title=title) - -def showlog(title, cont): - from .ui.logwindow import TextLog - TextLog.write(cont, title) -pub.subscribe(showlog, 'showlog') - -def write(cont, title='ImagePy'): - if curapp is None: - print('ImagePy Write >>> %s'%title) - print(cont) - else: - from .ui.logwindow import TextLog - wx.CallAfter(pub.sendMessage, 'showlog', title=title, cont=cont) - -def plot(title, gtitle='Graph', labelx='X-Unit', labely='Y-Unit'): - from .ui.plotwindow import PlotFrame - return PlotFrame.get_frame(title, gtitle, labelx, labely) - -#def set_progress(i): -# curapp.set_progress(i) - # MT callafter(curapp.set_progress, i) - -def set_info(i): - if curapp is None: print('ImagePy Info >>> %s'%i) - else: wx.CallAfter(curapp.set_info, i) - # MT callafter(curapp.set_info, i) - -def run(cmd): - from imagepy.core.engine import Macros - Macros('', cmd.replace('\r\n', '\n').split('\n')).start() \ No newline at end of file diff --git a/imagepy/__init__.py b/imagepy/__init__.py index a4516923..9fc44552 100644 --- a/imagepy/__init__.py +++ b/imagepy/__init__.py @@ -1,16 +1,2 @@ -#from __future__ import absolute_import import os.path as osp -import sys, os, wx - -from .IPy import * root_dir = osp.abspath(osp.dirname(__file__)) -os.chdir(root_dir) -# sys.path.append(root_dir) - -from .ui.mainframe import ImagePy - -def show(ui = True): - app = wx.App(False) - mainFrame = ImagePy(None) - mainFrame.Show() - app.MainLoop() \ No newline at end of file diff --git a/imagepy/__main__.py b/imagepy/__main__.py index 762bc4c9..7a8f2546 100644 --- a/imagepy/__main__.py +++ b/imagepy/__main__.py @@ -1,4 +1,4 @@ -import sys, os -sys.path.append('..') -import imagepy -imagepy.show() \ No newline at end of file +import sys +sys.path.append('../') +from imagepy.app import startup +startup.start() \ No newline at end of file diff --git a/imagepy/app/__init__.py b/imagepy/app/__init__.py new file mode 100644 index 00000000..b691dd4e --- /dev/null +++ b/imagepy/app/__init__.py @@ -0,0 +1,6 @@ +from .imagepy import ImagePy +from .imagej import ImageJ +from .console import Console +from .import startup + +from .manager import ConfigManager, ShortcutManager, ColorManager, DictManager, DocumentManager \ No newline at end of file diff --git a/imagepy/app/console.py b/imagepy/app/console.py new file mode 100644 index 00000000..8999dbe5 --- /dev/null +++ b/imagepy/app/console.py @@ -0,0 +1,21 @@ +from sciapp import App, Source +from imagepy import root_dir +from .startup import load_plugins + +class Console(App): + def __init__( self ): + App.__init__(self, False) + + def load_plugins(self, plgs=None): + if plgs is None: plgs = load_plugins()[0] + if isinstance(plgs, tuple): + if callable(plgs[1]): + name, plg = plgs[:2] + self.add_plugin(name, plg, 'plugin') + else: self.load_plugins(plgs[1]) + if isinstance(plgs, list): + for i in plgs: self.load_plugins(i) + +if __name__ == '__main__': + import numpy as np + import pandas as pd diff --git a/imagepy/app/imagej.py b/imagepy/app/imagej.py new file mode 100644 index 00000000..dcc7d7b6 --- /dev/null +++ b/imagepy/app/imagej.py @@ -0,0 +1,524 @@ +import wx, os, sys +import time, threading +sys.path.append('../../../') +import wx.lib.agw.aui as aui +from sciwx.widgets import MenuBar, ToolBar, ChoiceBook, ParaDialog, WorkFlowPanel +from sciwx.canvas import CanvasFrame +from sciwx.widgets import ProgressBar +from sciwx.grid import GridFrame +from sciwx.mesh import Canvas3DFrame +from sciwx.text import MDFrame, TextFrame +from sciwx.plot import PlotFrame +from skimage.data import camera +from sciapp import App, Source +from sciapp.object import Image, Table +from imagepy import root_dir +from .startup import load_plugins, load_tools, load_widgets +from .manager import ConfigManager, DictManager, ShortcutManager, DocumentManager +#from .source import * + +class ImageJ(wx.Frame, App): + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImagePy', + size = wx.Size(-1,-1), pos = wx.DefaultPosition, + style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + App.__init__(self) + self.auimgr = aui.AuiManager() + self.auimgr.SetManagedWindow( self ) + self.SetSizeHints( wx.Size(600,-1) ) + + logopath = os.path.join(root_dir, 'data/logo.ico') + self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) + + self.init_menu() + self.init_tool() + self.init_widgets() + self.init_text() + self.init_status() + self._load_all() + self.Fit() + + self.Layout() + self.auimgr.Update() + self.Fit() + self.Centre( wx.BOTH ) + + self.Bind(wx.EVT_CLOSE, self.on_close) + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_pan_close) + self.source() + + def source(self): + self.manager('color').add('front', (255, 255, 255)) + self.manager('color').add('back', (0, 0, 0)) + + def init_status(self): + self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizersta = wx.BoxSizer( wx.HORIZONTAL ) + self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImagePy v0.2", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.txt_info.Wrap( -1 ) + sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) + #self.pro_bar = wx.Gauge( stapanel, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size( 100,15 ), wx.GA_HORIZONTAL ) + self.pro_bar = ProgressBar(stapanel) + sizersta.Add( self.pro_bar, 0, wx.ALL|wx.ALIGN_CENTER, 0 ) + stapanel.SetSizer(sizersta) + class OpenDrop(wx.FileDropTarget): + def __init__(self, app): + wx.FileDropTarget.__init__(self) + self.app = app + def OnDropFiles(self, x, y, path): + self.app.run_macros(["Open>{'path':'%s'}"%i.replace('\\', '/') for i in path]) + stapanel.SetDropTarget(OpenDrop(self)) + self.auimgr.AddPane( stapanel, aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ) + .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) + . MinSize(wx.Size(-1, 20)). MaxSize(wx.Size(-1, 20)).Layer( 10 ) ) + + def flatten(self, plgs, lst=None): + if lst is None: lst = [] + if isinstance(plgs, tuple): + if callable(plgs[1]): lst.append((plgs)) + else: self.flatten(plgs[1], lst) + if isinstance(plgs, list): + for i in plgs: self.flatten(i, lst) + return lst + + def _load_all(self): + lang = ConfigManager.get('language') + dic = DictManager.get('common', tag=lang) or {} + self.auimgr.GetPane(self.widgets).Caption('Widgets') + for i in self.auimgr.GetAllPanes(): + i.Caption(dic[i.caption] if i.caption in dic else i.caption) + self.auimgr.Update() + plgs, errplg = load_plugins() + self.plugin_manager.remove() + for name, plg in self.flatten(plgs): + self.add_plugin(name, plg, 'plugin') + self.load_menu(plgs) + tols, errtol = load_tools() + for name, plg in self.flatten(tols): + self.add_plugin(plg.title, plg, 'tool') + self.load_tool(tols, 'Transform') + wgts, errwgt = load_widgets() + for name, plg in self.flatten(wgts): + self.add_plugin(name, plg, 'widget') + self.load_widget(wgts) + err = errplg + errtol + errwgt + if len(err)>0: + err = [('File', 'Name', 'Error')] + err + cont = '\n'.join(['%-30s\t%-20s\t%s'%i for i in err]) + self.show_txt(cont, 'loading error log') + + def load_all(self): wx.CallAfter(self._load_all) + + def load_menu(self, data): + self.menubar.clear() + lang = ConfigManager.get('language') + ls = DictManager.gets(tag=lang) + short = ShortcutManager.gets() + acc = self.menubar.load(data, dict([i[:2] for i in short])) + self.translate(dict([(i,j[i]) for i,j,_ in ls]))(self.menubar) + self.SetAcceleratorTable(acc) + + def load_tool(self, data, default=None): + self.toolbar.clear() + lang = ConfigManager.get('language') + ls = DictManager.gets(tag=lang) + dic = dict([(i,j[i]) for i,j,_ in ls]) + for i, (name, tols) in enumerate(data[1]): + name = dic[name] if name in dic else name + self.toolbar.add_tools(name, tols, i==0) + default = dic[default] if default in dic else default + if not default is None: self.toolbar.add_pop(os.path.join(root_dir, 'tools/drop.gif'), default) + self.toolbar.Layout() + + def load_widget(self, data): + self.widgets.clear() + lang = ConfigManager.get('language') + self.widgets.load(data) + for cbk in self.widgets.GetChildren(): + for i in range(cbk.GetPageCount()): + dic = DictManager.get(cbk.GetPageText(i), tag=lang) or {} + translate = self.translate(dic) + title = cbk.GetPageText(i) + cbk.SetPageText(i, dic[title] if title in dic else title) + self.translate(dic)(cbk.GetPage(i)) + # self.translate(self.widgets) + + def init_menu(self): + self.menubar = MenuBar(self) + + def init_tool(self): + sizer = wx.BoxSizer(wx.VERTICAL) + self.toolbar = ToolBar(self, False) + def on_help(evt, tol): + lang = ConfigManager.get('language') + doc = DocumentManager.get(tol.title, tag=lang) + doc = doc or DocumentManager.get(tol.title, tag='English') + self.show_md(doc or 'No Document!', tol.title) + self.toolbar.on_help = on_help + self.toolbar.Fit() + + self.auimgr.AddPane(self.toolbar, aui.AuiPaneInfo() .Top() .PinButton( True ).PaneBorder( False ) + .CaptionVisible( False ).Dock().FloatingSize( wx.DefaultSize ).MinSize(wx.Size( -1,34 )).DockFixed( True ) + . BottomDockable( False ).TopDockable( False ).Layer( 10 ) ) + + def set_background(self, img): + class ImgArtProvider(aui.AuiDefaultDockArt): + def __init__(self, img): + aui.AuiDefaultDockArt.__init__(self) + self.bitmap = wx.Bitmap(img, wx.BITMAP_TYPE_PNG) + + def DrawBackground(self, dc, window, orient, rect): + aui.AuiDefaultDockArt.DrawBackground(self, dc, window, orient, rect) + + memDC = wx.MemoryDC() + memDC.SelectObject(self.bitmap) + w, h = self.bitmap.GetWidth(), self.bitmap.GetHeight() + dc.Blit((rect[2]-w)//2, (rect[3]-h)//2, w, h, memDC, 0, 0, wx.COPY, True) + self.canvasnb.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_task(self, task): + self.task_manager.add(task.title, task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def remove_task(self, task): + self.task_manager.remove(obj=task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def init_widgets(self): + self.widgets = ChoiceBook(self) + self.auimgr.AddPane( self.widgets, aui.AuiPaneInfo() .Right().Caption('Widgets') .PinButton( True ).Hide() + .Float().Resizable().FloatingSize( wx.DefaultSize ).MinSize( wx.Size( 266,300 ) ).Layer( 10 ) ) + + def init_text(self): return + #self.mdframe = MDNoteFrame(self, 'Sci Document') + #self.txtframe = TextNoteFrame(self, 'Sci Text') + + def on_pan_close(self, event): + if event.GetPane().window in [self.toolbar, self.widgets]: + event.Veto() + if hasattr(event.GetPane().window, 'close'): + event.GetPane().window.close() + + def on_active_img(self, event): + self.active_img(event.GetEventObject().canvas.image.name) + # self.add_img_win(event.GetEventObject().canvas) + + def on_close_img(self, event): + #event.GetEventObject().Bind(wx.EVT_ACTIVATE, None) + App.close_img(self, event.GetEventObject().canvas.image.title) + event.Skip() + + def on_active_table(self, event): + self.active_table(event.GetEventObject().grid.table.title) + + def on_close_table(self, event): + App.close_table(self, event.GetEventObject().grid.table.title) + event.Skip() + + def on_new_mesh(self, event): + self.add_mesh(event.GetEventObject().canvas.mesh) + self.add_mesh_win(event.GetEventObject().canvas) + + def on_close_mesh(self, event): + self.remove_mesh(event.GetEventObject().canvas.mesh) + self.remove_mesh_win(event.GetEventObject().canvas) + event.Skip() + + def info(self, value): + lang = ConfigManager.get('language') + dics = DictManager.gets(tag=lang) + dic = dict(j for i in dics for j in i[1].items()) + value = dic[value] if value in dic else value + wx.CallAfter(self.txt_info.SetLabel, value) + + def set_progress(self, value): + v = max(min(value, 100), 0) + self.pro_bar.SetValue(v) + if value==-1: + self.pro_bar.Hide() + elif not self.pro_bar.IsShown(): + self.pro_bar.Show() + self.stapanel.GetSizer().Layout() + self.pro_bar.Update() + + def on_close(self, event): + print('close') + #ConfigManager.write() + self.auimgr.UnInit() + del self.auimgr + self.Destroy() + ConfigManager.write() + sys.exit() + + def _show_img(self, img, title=None): + cframe = CanvasFrame(self, True) + canvas = cframe.canvas + if not isinstance(img, Image): + img = Image(img, title) + App.show_img(self, img, img.title) + cframe.Bind(wx.EVT_ACTIVATE, self.on_active_img) + cframe.Bind(wx.EVT_CLOSE, self.on_close_img) + canvas.set_img(img) + cframe.SetIcon(self.GetIcon()) + cframe.Show() + + def show_img(self, img, title=None): + wx.CallAfter(self._show_img, img, title) + + def _show_table(self, tab, title): + cframe = GridFrame(self) + grid = cframe.grid + if not isinstance(tab, Table): + tab = Table(tab, title) + App.show_table(self, tab, tab.title) + grid.set_data(tab) + cframe.Bind(wx.EVT_ACTIVATE, self.on_active_table) + cframe.Bind(wx.EVT_CLOSE, self.on_close_table) + cframe.SetIcon(self.GetIcon()) + cframe.Show() + + def show_table(self, tab, title=None): + wx.CallAfter(self._show_table, tab, title) + + def show_plot(self, title): + fig = PlotFrame(self) + fig.SetIcon(self.GetIcon()) + fig.figure.title = title + return fig + + def _show_md(self, cont, title='ImagePy'): + mdframe = MDFrame(self) + mdframe.SetIcon(self.GetIcon()) + mdframe.set_cont(cont) + mdframe.mdpad.title = title + mdframe.Show(True) + + def show_md(self, cont, title='ImagePy'): + wx.CallAfter(self._show_md, cont, title) + + def _show_workflow(self, cont, title='ImagePy'): + pan = WorkFlowPanel(self) + pan.SetValue(cont) + info = aui.AuiPaneInfo(). DestroyOnClose(True). Left(). Caption(title) .PinButton( True ) \ + .Resizable().FloatingSize( wx.DefaultSize ).Dockable(False).Float().Top().Layer( 5 ) + pan.Bind(None, lambda x:self.run_macros(['%s>None'%x])) + self.auimgr.AddPane(pan, info) + self.auimgr.Update() + + def show_workflow(self, cont, title='ImagePy'): + wx.CallAfter(self._show_workflow, cont, title) + + def _show_txt(self, cont, title='ImagePy'): + TextFrame(self, title, cont).Show() + + def show_txt(self, cont, title='ImagePy'): + wx.CallAfter(self._show_txt, cont, title) + + def _show_mesh(self, mesh=None, title=None): + if mesh is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + + elif hasattr(mesh, 'vts'): + canvas = self.get_mesh_win() + if canvas is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + canvas.add_surf(title, mesh) + else: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.set_mesh(mesh) + canvas.GetParent().Show() + canvas.GetParent().Bind(wx.EVT_ACTIVATE, self.on_new_mesh) + canvas.GetParent().Bind(wx.EVT_CLOSE, self.on_close_mesh) + self.add_mesh(canvas.mesh) + self.add_mesh_win(canvas) + + def show_mesh(self, mesh=None, title=None): + wx.CallAfter(self._show_mesh, mesh, title) + + def show_widget(self, panel, title='Widgets'): + print(self.stapanel.GetSize(), '===========') + obj = self.manager('widget').get(panel.title) + if obj is None: + obj = panel(self, self) + self.manager('widget').add(panel.title, obj) + self.auimgr.AddPane(obj, aui.AuiPaneInfo().Caption(title).Left().Layer( 15 ).PinButton( True ) + .Float().Resizable().FloatingSize( wx.DefaultSize ).Dockable(True)) #.DestroyOnClose()) + lang = ConfigManager.get('language') + dic = DictManager.get(obj.title, tag=lang) or {} + info = self.auimgr.GetPane(obj) + info.Show(True).Caption(dic[obj.title] if obj.title in dic else obj.title) + self.translate(dic)(obj) + + self.Layout() + self.auimgr.Update() + print(self.stapanel.GetSize(), '===========') + + def switch_widget(self, visible=None): + info = self.auimgr.GetPane(self.widgets) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def switch_toolbar(self, visible=None): + info = self.auimgr.GetPane(self.toolbar) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def switch_table(self, visible=None): + info = self.auimgr.GetPane(self.tablenbwrap) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def close_img(self, name=None): + names = self.img_names() if name is None else [name] + for name in names: + idx = self.canvasnb.GetPageIndex(self.get_img_win(name)) + self.remove_img(self.get_img_win(name).image) + self.remove_img_win(self.get_img_win(name)) + self.canvasnb.DeletePage(idx) + + def close_table(self, name=None): + names = self.get_tab_name() if name is None else [name] + for name in names: + idx = self.tablenb.GetPageIndex(self.get_tab_win(name)) + self.remove_tab(self.get_tab_win(name).table) + self.remove_tab_win(self.get_tab_win(name)) + self.tablenb.DeletePage(idx) + + def record_macros(self, cmd): + obj = self.manager('widget').get(name='Macros Recorder') + if obj is None or not obj.IsShown(): return + wx.CallAfter(obj.write, cmd) + + def run_macros(self, cmd, callafter=None): + cmds = [i for i in cmd] + def one(cmds, after): + cmd = cmds.pop(0) + title, para = cmd.split('>') + print(title, para) + plg = self.manager('plugin').get(name=title)() + after = lambda cmds=cmds: one(cmds, one) + if len(cmds)==0: after = callafter + wx.CallAfter(plg.start, self, eval(para), after) + one(cmds, None) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def _alert(self, info, title='ImagePy'): + lang = ConfigManager.get('language') + dics = DictManager.gets(tag=lang) + dialog = wx.MessageDialog(self, info, title, wx.OK) + self.translate([i[1] for i in dics])(dialog) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + + def alert(self, info, title='ImagePy'): + wx.CallAfter(self._alert, info, title) + + + def yes_no(self, info, title='ImagePy'): + dialog = wx.MessageDialog(self, info, title, wx.YES_NO | wx.CANCEL) + rst = dialog.ShowModal() + dialog.Destroy() + dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} + return dic[rst] + + def get_path(self, title, filt, io, name=''): + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + dialog = wx.FileDialog(self, title, '', name, filt, dic[io]) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + + def show_para(self, title, para, view, on_handle=None, on_ok=None, + on_cancel=None, on_help=None, preview=False, modal=True): + lang = ConfigManager.get('language') + dic = DictManager.get(name=title, tag=lang) + doc = DocumentManager.get(title, tag=lang) + doc = doc or DocumentManager.get(title, tag='English') + on_help = lambda x=doc:self.show_md(x or 'No Document!', title) + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + self.translate(dic)(dialog) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + dialog.Bind('help', on_help) + return dialog.show() + + def translate(self, dic): + dic = dic or {} + if isinstance(dic, list): + dic = dict(j for i in dic for j in i.items()) + def lang(x): return dic[x] if x in dic else x + def trans(frame): + if hasattr(frame, 'GetChildren'): + for i in frame.GetChildren(): trans(i) + if isinstance(frame, wx.MenuBar): + for i in frame.GetMenus(): trans(i[0]) + for i in range(frame.GetMenuCount()): + frame.SetMenuLabel(i, lang(frame.GetMenuLabel(i))) + return 'not set title' + if isinstance(frame, wx.Menu): + for i in frame.GetMenuItems(): trans(i) + return 'not set title' + if isinstance(frame, wx.MenuItem): + frame.SetItemLabel(lang(frame.GetItemLabel())) + trans(frame.GetSubMenu()) + if isinstance(frame, wx.Button): + frame.SetLabel(lang(frame.GetLabel())) + if isinstance(frame, wx.CheckBox): + frame.SetLabel(lang(frame.GetLabel())) + if isinstance(frame, wx.StaticText): + frame.SetLabel(lang(frame.GetLabel())) + if hasattr(frame, 'SetTitle'): + frame.SetTitle(lang(frame.GetTitle())) + if isinstance(frame, wx.MessageDialog): + frame.SetMessage(lang(frame.GetMessage())) + if isinstance(frame, wx.Notebook): + for i in range(frame.GetPageCount()): + frame.SetPageText(i, lang(frame.GetPageText(i))) + if hasattr(frame, 'Layout'): frame.Layout() + return trans + +if __name__ == '__main__': + import numpy as np + import pandas as pd + + app = wx.App(False) + frame = ImageJ(None) + frame.Show() + frame.show_img([np.zeros((512, 512), dtype=np.uint8)], 'zeros') + #frame.show_img(None) + frame.show_table(pd.DataFrame(np.arange(100).reshape((10,10))), 'title') + ''' + frame.show_md('abcdefg', 'md') + frame.show_md('ddddddd', 'md') + frame.show_txt('abcdefg', 'txt') + frame.show_txt('ddddddd', 'txt') + ''' + app.MainLoop() \ No newline at end of file diff --git a/imagepy/app/imagepy.py b/imagepy/app/imagepy.py new file mode 100644 index 00000000..8fe15ae8 --- /dev/null +++ b/imagepy/app/imagepy.py @@ -0,0 +1,607 @@ +import wx, os, sys +import time, threading +sys.path.append('../../../') +import wx.lib.agw.aui as aui +from sciwx.widgets import MenuBar, RibbonBar, ToolBar, ChoiceBook, ParaDialog, WorkFlowPanel, ProgressBar +from sciwx.canvas import CanvasNoteBook +from sciwx.grid import GridNoteBook +from sciwx.mesh import Canvas3DNoteBook +from sciwx.text import MDNoteBook, TextNoteBook +from sciwx.plot import PlotFrame +from skimage.data import camera +from sciapp import App, Source +from sciapp.object import Image, Table, Scene, Mesh +from imagepy import root_dir +from .startup import load_plugins, load_tools, load_widgets, load_document, load_dictionary +from .manager import ConfigManager, DictManager, ShortcutManager, DocumentManager + +class ImagePy(wx.Frame, App): + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImagePy', + size = wx.Size(-1,-1), pos = wx.DefaultPosition, + style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + App.__init__(self) + self.auimgr = aui.AuiManager() + self.auimgr.SetManagedWindow( self ) + self.SetSizeHints( wx.Size(1024,768) ) + + logopath = os.path.join(root_dir, 'data/logo.ico') + self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) + + self.init_menu() + self.init_tool() + self.init_canvas() + self.init_table() + self.init_mesh() + self.init_widgets() + self.init_text() + self.init_status() + self._load_all() + + self.Layout() + self.auimgr.Update() + self.Fit() + self.Centre( wx.BOTH ) + + self.Bind(wx.EVT_CLOSE, self.on_close) + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_pan_close) + self.source() + + def source(self): + self.manager('color').add('front', (255, 255, 255)) + self.manager('color').add('back', (0, 0, 0)) + + def init_status(self): + self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizersta = wx.BoxSizer( wx.HORIZONTAL ) + self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImagePy v0.2", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.txt_info.Wrap( -1 ) + sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) + #self.pro_bar = wx.Gauge( stapanel, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size( 100,15 ), wx.GA_HORIZONTAL ) + self.pro_bar = ProgressBar(stapanel) + sizersta.Add( self.pro_bar, 0, wx.ALL, 2 ) + stapanel.SetSizer(sizersta) + class OpenDrop(wx.FileDropTarget): + def __init__(self, app): + wx.FileDropTarget.__init__(self) + self.app = app + def OnDropFiles(self, x, y, path): + self.app.run_macros(["Open>{'path':'%s'}"%i.replace('\\', '/') for i in path]) + stapanel.SetDropTarget(OpenDrop(self)) + self.auimgr.AddPane( stapanel, aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ) + .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) + . MinSize(wx.Size(-1, 20)). MaxSize(wx.Size(-1, 20)).Layer( 10 ) ) + + def flatten(self, plgs, lst=None): + if lst is None: lst = [] + if isinstance(plgs, tuple): + if callable(plgs[1]): lst.append((plgs)) + else: self.flatten(plgs[1], lst) + if isinstance(plgs, list): + for i in plgs: self.flatten(i, lst) + return lst + + def _load_all(self): + lang = ConfigManager.get('language') + dic = DictManager.get('common', tag=lang) or {} + self.auimgr.GetPane(self.widgets).Caption('Widgets') + self.auimgr.GetPane(self.tablenbwrap).Caption('Table') + for i in self.auimgr.GetAllPanes(): + i.Caption(dic[i.caption] if i.caption in dic else i.caption) + self.auimgr.Update() + plgs, errplg = load_plugins() + self.plugin_manager.remove() + for name, plg in self.flatten(plgs): + self.add_plugin(name, plg, 'plugin') + self.load_menu(plgs) + tols, errtol = load_tools() + for name, plg in self.flatten(tols): + self.add_plugin(plg.title, plg, 'tool') + self.load_tool(tols, 'Transform') + wgts, errwgt = load_widgets() + for name, plg in self.flatten(wgts): + self.add_plugin(name, plg, 'widget') + self.load_widget(wgts) + err = errplg + errtol + errwgt + if len(err)>0: + err = [('File', 'Name', 'Error')] + err + cont = '\n'.join(['%-30s\t%-20s\t%s'%i for i in err]) + self.show_txt(cont, 'loading error log') + + def load_all(self): wx.CallAfter(self._load_all) + + def load_menu(self, data): + self.menubar.clear() + lang = ConfigManager.get('language') + ls = DictManager.gets(tag=lang) + short = ShortcutManager.gets() + + acc = self.menubar.load(data, dict([i[:2] for i in short])) + self.translate(dict([(i,j[i]) for i,j,_ in ls]))(self.menubar) + self.SetAcceleratorTable(acc) + + def load_tool(self, data, default=None): + self.toolbar.clear() + lang = ConfigManager.get('language') + ls = DictManager.gets(tag=lang) + dic = dict([(i,j[i]) for i,j,_ in ls]) + for i, (name, tols) in enumerate(data[1]): + name = dic[name] if name in dic else name + self.toolbar.add_tools(name, tols, i==0) + default = dic[default] if default in dic else default + if not default is None: self.toolbar.add_pop(os.path.join(root_dir, 'tools/drop.gif'), default) + self.toolbar.Layout() + + def load_widget(self, data): + self.widgets.clear() + lang = ConfigManager.get('language') + self.widgets.load(data) + for cbk in self.widgets.GetChildren(): + for i in range(cbk.GetPageCount()): + dic = DictManager.get(cbk.GetPageText(i), tag=lang) or {} + translate = self.translate(dic) + title = cbk.GetPageText(i) + cbk.SetPageText(i, dic[title] if title in dic else title) + self.translate(dic)(cbk.GetPage(i)) + # self.translate(self.widgets) + + def init_menu(self): + self.menubar = MenuBar(self) + # self.menubar = RibbonBar(self) + # self.auimgr.AddPane( self.menubar, aui.AuiPaneInfo() .CaptionVisible(False) .Top() .PinButton( True ).Dock().Resizable().MinSize(wx.Size(1000, 130)).FloatingSize( wx.DefaultSize ).Layer(5) ) + + def init_tool(self): + sizer = wx.BoxSizer(wx.VERTICAL) + self.toolbar = ToolBar(self, True) + def on_help(evt, tol): + lang = ConfigManager.get('language') + doc = DocumentManager.get(tol.title, tag=lang) + doc = doc or DocumentManager.get(tol.title, tag='English') + self.show_md(doc or 'No Document!', tol.title) + self.toolbar.on_help = on_help + self.toolbar.Fit() + + self.auimgr.AddPane(self.toolbar, aui.AuiPaneInfo() .Left() .PinButton( True ) + .CaptionVisible( True ).Dock().Resizable().FloatingSize( wx.DefaultSize ).MaxSize(wx.Size( 32,-1 )) + . BottomDockable( True ).TopDockable( False ).Layer( 10 ) ) + + def set_background(self, img): + class ImgArtProvider(aui.AuiDefaultDockArt): + def __init__(self, img): + aui.AuiDefaultDockArt.__init__(self) + self.bitmap = wx.Bitmap(img, wx.BITMAP_TYPE_PNG) + + def DrawBackground(self, dc, window, orient, rect): + aui.AuiDefaultDockArt.DrawBackground(self, dc, window, orient, rect) + + memDC = wx.MemoryDC() + memDC.SelectObject(self.bitmap) + w, h = self.bitmap.GetWidth(), self.bitmap.GetHeight() + dc.Blit((rect[2]-w)//2, (rect[3]-h)//2, w, h, memDC, 0, 0, wx.COPY, True) + self.canvasnb.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def init_canvas(self): + self.canvasnbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.canvasnb = CanvasNoteBook(self.canvasnbwrap) + self.set_background(root_dir+'/data/watermark.png') + + sizer.Add( self.canvasnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.canvasnbwrap.SetSizer( sizer ) + self.canvasnbwrap.Layout() + self.auimgr.AddPane( self.canvasnbwrap, aui.AuiPaneInfo() .Center() .CaptionVisible( False ).PinButton( True ).Dock() + .PaneBorder( False ).Resizable().FloatingSize( wx.DefaultSize ). BottomDockable( True ).TopDockable( False ) + .LeftDockable( True ).RightDockable( True ). Caption('Image') ) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_img) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_img) + + def init_table(self): + self.tablenbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.tablenb = GridNoteBook( self.tablenbwrap) + sizer.Add( self.tablenb, 1, wx.EXPAND |wx.ALL, 0 ) + self.tablenbwrap.SetSizer( sizer ) + self.tablenbwrap.Layout() + + self.auimgr.AddPane( self.tablenbwrap, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Dock(). Hide() + .MaximizeButton( True ).Resizable().FloatingSize((800, 600)).BestSize(( 120,120 )). Caption('Table') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_table) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_table) + + def init_mesh(self): + self.meshnbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.meshnb = Canvas3DNoteBook( self.meshnbwrap) + sizer.Add( self.meshnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.meshnbwrap.SetSizer( sizer ) + self.meshnbwrap.Layout() + + self.auimgr.AddPane( self.meshnbwrap, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Float().Hide() + .MaximizeButton( True ).Resizable().FloatingSize((800, 600)).BestSize(( 120,120 )). Caption('Mesh') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + self.meshnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_mesh) + self.meshnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_mesh) + + def add_task(self, task): + self.task_manager.add(task.title, task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def remove_task(self, task): + self.task_manager.remove(obj=task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def init_widgets(self): + lang = ConfigManager.get('language') + dic = DictManager.get('common', tag=lang) + self.widgets = ChoiceBook(self) + self.auimgr.AddPane( self.widgets, aui.AuiPaneInfo() .Right().Caption('Widgets') .PinButton( True ) + .Dock().Resizable().FloatingSize( wx.DefaultSize ).MinSize( wx.Size( 266,-1 ) ).Layer( 10 ) ) + + def init_text(self): + self.mdwarp = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.mdnb = MDNoteBook( self.mdwarp) + sizer.Add( self.mdnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.mdwarp.SetSizer( sizer ) + self.mdwarp.Layout() + + self.auimgr.AddPane( self.mdwarp, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Float().Hide() + .MaximizeButton( True ).Resizable().FloatingSize((400, 400)).BestSize(( 120,120 )). Caption('MarkDown') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + + self.txtwarp = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.txtnb = TextNoteBook( self.txtwarp) + sizer.Add( self.txtnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.txtwarp.SetSizer( sizer ) + self.txtwarp.Layout() + + self.auimgr.AddPane( self.txtwarp, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Float().Hide() + .MaximizeButton( True ).Resizable().FloatingSize((400, 400)).BestSize(( 120,120 )). Caption('TextPanel') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + + def on_pan_close(self, event): + if event.GetPane().window in [self.toolbar, self.widgets]: + event.Veto() + if hasattr(event.GetPane().window, 'close'): + event.GetPane().window.close() + + def on_active_img(self, event): + self.active_img(self.canvasnb.canvas().image.name) + #self.add_img_win(self.canvasnb.canvas()) + + def on_close_img(self, event): + canvas = event.GetEventObject().GetPage(event.GetSelection()) + #self.remove_img_win(canvas) + App.close_img(self, canvas.image.title) + + def on_active_table(self, event): + self.active_table(self.tablenb.grid().table.title) + + def on_close_table(self, event): + grid = event.GetEventObject().GetPage(event.GetSelection()) + App.close_table(self, grid.table.title) + + def on_active_mesh(self, event): + self.active_mesh(self.meshnb.canvas().scene3d.name) + # self.add_mesh_win(self.meshnb.canvas()) + + def on_close_mesh(self, event): + # canvas3d = event.GetEventObject().GetPage(event.GetSelection()) + App.close_mesh(self, self.meshnb.canvas().scene3d.name) + event.Skip() + # self.remove_mesh_win(canvas3d) + + def info(self, value): + lang = ConfigManager.get('language') + dics = DictManager.gets(tag=lang) + dic = dict(j for i in dics for j in i[1].items()) + value = dic[value] if value in dic else value + wx.CallAfter(self.txt_info.SetLabel, value) + + def set_progress(self, value): + v = max(min(value, 100), 0) + self.pro_bar.SetValue(v) + if value==-1: + self.pro_bar.Hide() + elif not self.pro_bar.IsShown(): + self.pro_bar.Show() + self.stapanel.GetSizer().Layout() + self.pro_bar.Update() + + def on_close(self, event): + print('close') + #ConfigManager.write() + self.auimgr.UnInit() + del self.auimgr + self.Destroy() + ConfigManager.write() + sys.exit() + + def _show_img(self, img, title=None): + canvas = self.canvasnb.add_canvas() + if not isinstance(img, Image): + img = Image(img, title) + App.show_img(self, img, img.title) + canvas.set_img(img) + + def show_img(self, img, title=None): + wx.CallAfter(self._show_img, img, title) + + def _show_table(self, tab, title): + grid = self.tablenb.add_grid() + if not isinstance(tab, Table): + tab = Table(tab, title) + App.show_table(self, tab, tab.title) + grid.set_data(tab) + info = self.auimgr.GetPane(self.tablenbwrap) + info.Show(True) + self.auimgr.Update() + + def show_table(self, tab, title=None): + wx.CallAfter(self._show_table, tab, title) + + def show_plot(self, title): + fig = PlotFrame(self) + fig.SetIcon(self.GetIcon()) + fig.figure.title = title + return fig + + def _show_md(self, cont, title='ImagePy'): + page = self.mdnb.add_page() + page.set_cont(cont) + page.title = title + info = self.auimgr.GetPane(self.mdwarp) + info.Show(True) + self.auimgr.Update() + + def show_md(self, cont, title='ImagePy'): + wx.CallAfter(self._show_md, cont, title) + + def _show_workflow(self, cont, title='ImagePy'): + pan = WorkFlowPanel(self) + pan.SetValue(cont) + info = aui.AuiPaneInfo(). DestroyOnClose(True). Left(). Caption(title) .PinButton( True ) \ + .Resizable().FloatingSize( wx.DefaultSize ).Dockable(True).Dock().Top().Layer( 5 ) + pan.Bind(None, lambda x:self.run_macros(['%s>None'%x])) + self.auimgr.AddPane(pan, info) + self.auimgr.Update() + + def show_workflow(self, cont, title='ImagePy'): + wx.CallAfter(self._show_workflow, cont, title) + + def _show_txt(self, cont, title='ImagePy'): + page = self.txtnb.add_page() + page.set_cont(cont) + page.title = title + info = self.auimgr.GetPane(self.txtwarp) + info.Show(True) + self.auimgr.Update() + + def show_txt(self, cont, title='ImagePy'): + wx.CallAfter(self._show_txt, cont, title) + + def _show_mesh(self, obj=None, title='Scene'): + # show a scence or create a new scene + if isinstance(obj, Scene) or obj is None: + canvas = self.meshnb.add_canvas(obj) + App.show_mesh(self, canvas.scene3d, title) + else: + if self.get_mesh() is None: + canvas = self.meshnb.add_canvas(None) + App.show_mesh(self, canvas.scene3d, 'Scene') + scene = self.get_mesh() + scene.add_obj(title, obj) + info = self.auimgr.GetPane(self.meshnbwrap) + info.Show(True) + self.auimgr.Update() + return + + + if mesh is None: + scene = self.get_mesh() + canvas = self.meshnb.add_canvas() + canvas.scene3d.name = 'Surface' + elif hasattr(mesh, 'vts'): + canvas = self.get_mesh_win() + if canvas is None: + canvas = self.meshnb.add_canvas() + canvas.mesh.name = 'Surface' + canvas.add_surf(title, mesh) + else: + canvas = self.meshnb.add_canvas() + canvas.set_mesh(mesh) + # self.add_mesh(canvas.mesh) + # self.add_mesh_win(canvas) + + info = self.auimgr.GetPane(self.meshnbwrap) + info.Show(True) + self.auimgr.Update() + + def show_mesh(self, mesh=None, title='Scene'): + wx.CallAfter(self._show_mesh, mesh, title) + + def show_widget(self, panel, title='Widgets'): + obj = self.manager('widget').get(panel.title) + if obj is None: + obj = panel(self, self) + self.manager('widget').add(panel.title, obj) + self.auimgr.AddPane(obj, aui.AuiPaneInfo().Caption(title).Left().Layer( 15 ).PinButton( True ) + .Float().Resizable().FloatingSize( wx.DefaultSize ).Dockable(True)) #.DestroyOnClose()) + lang = ConfigManager.get('language') + dic = DictManager.get(obj.title, tag=lang) or {} + info = self.auimgr.GetPane(obj) + info.Show(True).Caption(dic[obj.title] if obj.title in dic else obj.title) + self.translate(dic)(obj) + + self.Layout() + self.auimgr.Update() + + def switch_widget(self, visible=None): + info = self.auimgr.GetPane(self.widgets) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def switch_toolbar(self, visible=None): + info = self.auimgr.GetPane(self.toolbar) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def switch_table(self, visible=None): + info = self.auimgr.GetPane(self.tablenbwrap) + info.Show(not info.IsShown() if visible is None else visible) + self.auimgr.Update() + + def close_img(self, name): + App.close_img(self, name) + for i in range(self.canvasnb.GetPageCount()): + if self.canvasnb.GetPageText(i)==name: + return self.canvasnb.DeletePage(i) + + def get_img_win(self, name=None): + name = name or self.get_img().name + for i in range(self.canvasnb.GetPageCount()): + if self.canvasnb.GetPageText(i)==name: + return self.canvasnb.GetPage(i) + + def close_table(self, name=None): + App.close_tab(self, name) + for i in range(self.tablenb.GetPageCount()): + if self.tablenb.GetPageText(i)==name: + return self.tablenb.DeletePage(i) + + def record_macros(self, cmd): + obj = self.manager('widget').get(name='Macros Recorder') + if obj is None or not obj.IsShown(): return + wx.CallAfter(obj.write, cmd) + + def run_macros(self, cmd, callafter=None): + cmds = [i for i in cmd] + def one(cmds, after): + cmd = cmds.pop(0) + title, para = cmd.split('>') + plg = self.manager('plugin').get(name=title)() + after = lambda cmds=cmds: one(cmds, one) + if len(cmds)==0: after = callafter + wx.CallAfter(plg.start, self, eval(para), after) + one(cmds, None) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def _alert(self, info, title='ImagePy'): + lang = ConfigManager.get('language') + dics = DictManager.gets(tag=lang) + dialog = wx.MessageDialog(self, info, title, wx.OK) + self.translate([i[1] for i in dics])(dialog) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + + def alert(self, info, title='ImagePy'): + wx.CallAfter(self._alert, info, title) + + def yes_no(self, info, title='ImagePy'): + dialog = wx.MessageDialog(self, info, title, wx.YES_NO | wx.CANCEL) + rst = dialog.ShowModal() + dialog.Destroy() + dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} + return dic[rst] + + def get_path(self, title, filt, io, name=''): + if isinstance(filt, str): filt = filt.split(',') + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + if io in {'open', 'save'}: + dialog = wx.FileDialog(self, title, '', name, filt, dic[io] | wx.FD_CHANGE_DIR) + else: dialog = wx.DirDialog(self, title, '', wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST | wx.FD_CHANGE_DIR) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + + def show_para(self, title, para, view, on_handle=None, on_ok=None, + on_cancel=None, on_help=None, preview=False, modal=True): + lang = ConfigManager.get('language') + dic = DictManager.get(name=title, tag=lang) + doc = DocumentManager.get(title, tag=lang) + doc = doc or DocumentManager.get(title, tag='English') + on_help = lambda x=doc:self.show_md(x or 'No Document!', title) + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + self.translate(dic)(dialog) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + dialog.Bind('help', on_help) + return dialog.show() + + def translate(self, dic): + dic = dic or {} + if isinstance(dic, list): + dic = dict(j for i in dic for j in i.items()) + def lang(x): return dic[x] if x in dic else x + def trans(frame): + if hasattr(frame, 'GetChildren'): + for i in frame.GetChildren(): trans(i) + if isinstance(frame, wx.MenuBar): + for i in frame.GetMenus(): trans(i[0]) + for i in range(frame.GetMenuCount()): + frame.SetMenuLabel(i, lang(frame.GetMenuLabel(i))) + return 'not set title' + if isinstance(frame, wx.Menu): + for i in frame.GetMenuItems(): trans(i) + return 'not set title' + if isinstance(frame, wx.MenuItem): + frame.SetItemLabel(lang(frame.GetItemLabel())) + trans(frame.GetSubMenu()) + if isinstance(frame, wx.Button): + frame.SetLabel(lang(frame.GetLabel())) + if isinstance(frame, wx.CheckBox): + frame.SetLabel(lang(frame.GetLabel())) + if isinstance(frame, wx.StaticText): + frame.SetLabel(lang(frame.GetLabel())) + if hasattr(frame, 'SetTitle'): + frame.SetTitle(lang(frame.GetTitle())) + if isinstance(frame, wx.MessageDialog): + frame.SetMessage(lang(frame.GetMessage())) + if hasattr(frame, 'SetPageText'): + for i in range(frame.GetPageCount()): + frame.SetPageText(i, lang(frame.GetPageText(i))) + if hasattr(frame, 'Layout'): frame.Layout() + return trans + +if __name__ == '__main__': + import numpy as np + import pandas as pd + + app = wx.App(False) + frame = ImagePy(None) + frame.Show() + frame.show_img([np.zeros((512, 512), dtype=np.uint8)], 'zeros') + #frame.show_img(None) + frame.show_table(pd.DataFrame(np.arange(100).reshape((10,10))), 'title') + ''' + frame.show_md('abcdefg', 'md') + frame.show_md('ddddddd', 'md') + frame.show_txt('abcdefg', 'txt') + frame.show_txt('ddddddd', 'txt') + ''' + app.MainLoop() \ No newline at end of file diff --git a/imagepy/core/loader/loader.py b/imagepy/app/loader.py similarity index 60% rename from imagepy/core/loader/loader.py rename to imagepy/app/loader.py index 22afe7a3..1a411e18 100644 --- a/imagepy/core/loader/loader.py +++ b/imagepy/app/loader.py @@ -4,13 +4,14 @@ @author: yxl """ -import os, sys -from ..engine import Macros, MkDown, Widget -from ..manager import ToolsManager, PluginsManager, WidgetsManager -from ... import IPy, root_dir +import os, sys, os.path as osp +from glob import glob +from sciapp.action import Macros, Widget, Report +from .. import root_dir +from .manager import DocumentManager, DictManager from codecs import open -def getpath(root, path): +def get_path(root, path): for i in range(10,0,-1): if not '../'*i in path: continue s = root @@ -21,51 +22,35 @@ def getpath(root, path): def extend_plugins(path, lst, err): rst = [] for i in lst: - if isinstance(i, tuple) or i=='-': - rst.append(i) - - elif i[-3:] == '.mc': + if isinstance(i, tuple) or i=='-': rst.append(i) + elif i[-3:] == 'rpt': pt = os.path.join(root_dir,path) - f = open(pt+'/'+i, 'r', 'utf-8') - cmds = f.readlines() - f.close() - rst.append(Macros(i[:-3], [getpath(pt, i) for i in cmds])) - PluginsManager.add(rst[-1]) - elif i[-3:] == '.md': - f = open(os.path.join(root_dir,path)+'/'+i, 'r', 'utf-8') - cont = f.read() - f.close() - rst.append(MkDown(i[:-3], cont)) - PluginsManager.add(rst[-1]) + rst.append(Report(i[:-4], pt+'/'+i)) + elif i[-3:] in {'.md', '.mc', '.wf'}: + p = os.path.join(os.path.join(root_dir, path), i).replace('\\','/') + rst.append(Macros(i[:-3], ['Open>{"path":"%s"}'%p])) elif i[-6:] in ['wgt.py', 'gts.py']: try: rpath = path.replace('/', '.').replace('\\','.') - #rpath = rpath[rpath.index('imagepy.'):] plg = __import__('imagepy.'+ rpath+'.'+i[:-3],'','',['']) if hasattr(plg, 'wgts'): rst.extend([j if j=='-' else Widget(j) for j in plg.wgts]) - for p in plg.wgts: - if not isinstance(p, str):WidgetsManager.add(p) else: rst.append(Widget(plg.Plugin)) - WidgetsManager.add(plg.Plugin) except Exception as e: err.append((path, i, sys.exc_info()[1])) else: try: rpath = path.replace('/', '.').replace('\\','.') - #rpath = rpath[rpath.index('imagepy.'):] plg = __import__('imagepy.'+ rpath+'.'+i[:-3],'','',['']) if hasattr(plg, 'plgs'): rst.extend([j for j in plg.plgs]) for p in plg.plgs: - if not isinstance(p, str):PluginsManager.add(p) + if not isinstance(p, str): pass else: rst.append(plg.Plugin) - PluginsManager.add(plg.Plugin) except Exception as e: err.append((path, i, sys.exc_info()[1])) - return rst def sort_plugins(catlog, lst): @@ -73,56 +58,47 @@ def sort_plugins(catlog, lst): for i in catlog: if i=='-':rst.append('-') for j in lst: - if j[:-3]==i or j[0].title==i: + if j[:-3]==i or j[:-4]==i or j[0].title==i: lst.remove(j) rst.append(j) rst.extend(lst) return rst -def build_plugins(path, err=False): - root = err in (True, False) - if root: sta, err = err, [] +def build_plugins(path, err='root'): + root = err=='root' + if root: err=[] subtree = [] - cont = os.listdir(os.path.join(root_dir, path)) + cont = os.listdir(path) for i in cont: subp = os.path.join(path,i) - if os.path.isdir(os.path.join(root_dir, subp)): + if os.path.isdir(subp): sub = build_plugins(subp, err) - if len(sub)!=0:subtree.append(sub) + if len(sub)!=0:subtree.append(sub[:2]) elif i[-6:] in ('plg.py', 'lgs.py', 'wgt.py', 'gts.py'): subtree.append(i) - elif i[-3:] in ('.mc', '.md'): + elif i[-3:] in ('.mc', '.md', '.wf', 'rpt'): subtree.append(i) if len(subtree)==0:return [] + path = path[path.index(root_dir)+len(root_dir)+1:] rpath = path.replace('/', '.').replace('\\','.') - #rpath = rpath[rpath.index('imagepy.'):] pg = __import__('imagepy.'+rpath,'','',['']) pg.title = os.path.basename(path) if hasattr(pg, 'catlog'): subtree = sort_plugins(pg.catlog, subtree) subtree = extend_plugins(path, subtree, err) - - if root and sta and len(err)>0: - IPy.write('some plugin may be not loaded, but not affect otheres!') - for i in err: IPy.write('>>> %-50s%-20s%s'%i) - return (pg, subtree) + return pg, subtree, err def extend_tools(path, lst, err): rst = [] for i in lst: - if i[-3:] in ('.mc', '.md'): - pt = os.path.join(root_dir, path) - f = open(pt+'/'+i) - cmds = f.readlines() - f.close() - rst.append((Macros(i[:-3], [getpath(pt, i) for i in cmds]), + if i[-3:] in ('.mc', '.md', '.wf', 'rpt'): + p = os.path.join(os.path.join(root_dir,path), i).replace('\\','/') + rst.append((Macros(i[:-3], ['Open>{"path":"%s"}'%p]), os.path.join(root_dir, path)+'/'+i[:-3]+'.gif')) else: try: rpath = path.replace('/', '.').replace('\\','.') - #rpath = rpath[rpath.index('imagepy.'):] - plg = __import__('imagepy.'+rpath+'.'+i,'','',['']) if hasattr(plg, 'plgs'): for i,j in plg.plgs: rst.append((i, path+'/'+j)) @@ -130,7 +106,6 @@ def extend_tools(path, lst, err): os.path.join(root_dir, path)+'/'+i.split('_')[0]+'.gif')) except Exception as e: err.append((path, i, sys.exc_info()[1])) - for i in rst:ToolsManager.add(i[0]) return rst def sort_tools(catlog, lst): @@ -143,10 +118,10 @@ def sort_tools(catlog, lst): rst.append(j) rst.extend(lst) return rst - -def build_tools(path, err=False): - root = err in (True, False) - if root: sta, err = err, [] + +def build_tools(path, err='root'): + root = err=='root' + if root: err=[] subtree = [] cont = os.listdir(os.path.join(root_dir, path)) @@ -154,11 +129,11 @@ def build_tools(path, err=False): subp = os.path.join(path,i) if root and os.path.isdir(os.path.join(root_dir, subp)): sub = build_tools(subp, err) - if len(sub)!=0:subtree.append(sub) + if len(sub)!=0:subtree.append(sub[:2]) elif not root: if i[len(i)-7:] in ('_tol.py', 'tols.py'): subtree.append(i[:-3]) - elif i[-3:] in ('.mc', '.md'): + elif i[-3:] in ('.mc', '.md', '.wf', 'rpt'): subtree.append(i) if len(subtree)==0:return [] rpath = path.replace('/', '.').replace('\\','.') @@ -168,22 +143,17 @@ def build_tools(path, err=False): if hasattr(pg, 'catlog'): subtree = sort_tools(pg.catlog, subtree) if not root:subtree = extend_tools(path, subtree, err) - elif sta and len(err)>0: - IPy.write('tools not loaded:') - for i in err: IPy.write('>>> %-50s%-20s%s'%i) - return (pg, subtree) + return pg, subtree, err def extend_widgets(path, lst, err): rst = [] for i in lst: try: rpath = path.replace('/', '.').replace('\\','.') - #rpath = rpath[rpath.index('imagepy.'):] plg = __import__('imagepy.'+rpath+'.'+i,'','',['']) rst.append(plg.Plugin) except Exception as e: err.append((path, i, sys.exc_info()[1])) - for i in rst:WidgetsManager.add(i) return rst def sort_widgets(catlog, lst): @@ -197,16 +167,16 @@ def sort_widgets(catlog, lst): rst.extend(lst) return rst -def build_widgets(path, err=False): - root = err in (True, False) - if root: sta, err = err, [] +def build_widgets(path, err='root'): + root = err=='root' + if root: err=[] subtree = [] cont = os.listdir(os.path.join(root_dir, path)) for i in cont: subp = os.path.join(path,i) if root and os.path.isdir(os.path.join(root_dir, subp)): sub = build_widgets(subp, err) - if len(sub)!=0:subtree.append(sub) + if len(sub)!=0:subtree.append(sub[:2]) elif not root: if i[len(i)-7:] in ('_wgt.py', 'wgts.py'): subtree.append(i[:-3]) @@ -218,14 +188,48 @@ def build_widgets(path, err=False): pg.title = os.path.basename(path) if hasattr(pg, 'catlog'): subtree = sort_widgets(pg.catlog, subtree) - if not root: - subtree = extend_widgets(path, subtree, err) - elif sta and len(err)>0: - IPy.write('widgets not loaded:') - for i in err: IPy.write('>>> %-50s%-20s%s'%i) - return (pg, subtree) + if not root: subtree = extend_widgets(path, subtree, err) + return pg, subtree, err + +def build_document(path): + docs = [] + for lang in [osp.split(i)[1] for i in glob(path+'/*') if osp.isdir(i)]: + for dirpath, dirnames, filenames in os.walk(path+'/'+lang): + for filename in filenames: + if filename[-3:] != '.md': continue + docs.append(os.path.join(dirpath, filename)) + with open(docs[-1], encoding='utf-8') as f: + DocumentManager.add(filename[:-3], f.read(), lang) + return docs + +def build_dictionary(path): + for lang in [osp.split(i)[1] for i in glob(path+'/*') if osp.isdir(i)]: + for dirpath, dirnames, filenames in os.walk(path+'/'+lang): + for filename in filenames: + if filename[-3:] != 'dic': continue + with open(os.path.join(dirpath, filename), encoding='utf-8') as f: + lines = f.read().replace('\r','').split('\n') + dic = [] + for line in lines: + if line == '': + dic[-1] = (dic[-1][0][0], dict(dic[-1])) + elif line[0] == '\t': + dic[-1].append(line[1:].split('::')) + else: + dic.append([line.split('::')]) + if isinstance(dic[-1], list): + dic[-1] = (dic[-1][0][0], dict(dic[-1])) + dic = dict(dic) + for i in dic: + obj = DictManager.get(i, tag=lang) + if not obj is None: obj.update(dic[i]) + else: DictManager.add(i, dic[i], lang) + common = DictManager.get('common', tag=lang) + if common is None: return + objs = DictManager.gets(tag=lang) + for i in objs: i[1].update(common) if __name__ == "__main__": print (os.getcwd()) os.chdir('../../') - data = build_tools('tools') + data = build_tools('tools') \ No newline at end of file diff --git a/imagepy/app/manager.py b/imagepy/app/manager.py new file mode 100644 index 00000000..48b77946 --- /dev/null +++ b/imagepy/app/manager.py @@ -0,0 +1,44 @@ +from sciapp import Manager +from imagepy import root_dir +import os.path as osp +from glob import glob +import numpy as np + +ConfigManager = Manager().read(osp.join(root_dir, 'data/config.json')) +ShortcutManager = Manager().read(osp.join(root_dir, 'data/shortcut.json')) +ColorManager = Manager() +DictManager = Manager() +DocumentManager = Manager() + +if ConfigManager.get('language') is None: + ConfigManager.add('language', 'english') + +from sciapp.object import Shape +mark_style = ConfigManager.get('mark_style') +if not mark_style is None: + for i in mark_style: Shape.default[i] = mark_style[i] + +from sciapp.object import ROI +mark_style = ConfigManager.get('roi_style') +if not mark_style is None: + for i in mark_style: ROI.default[i] = mark_style[i] + +from sciapp.action import Measure +mark_style = ConfigManager.get('mea_style') +if not mark_style is None: + for i in mark_style: Measure.default[i] = mark_style[i] + +filenames = glob(osp.join(root_dir,'data/luts/*/*.lut')) +keys = [osp.split(filename)[-1][:-4] for filename in filenames] +values = [np.fromfile(i, dtype=np.uint8).reshape((3,256)).T.copy() for i in filenames] +for k,v in zip(keys[::-1], values[::-1]): ColorManager.add(k, v, 'adv') + +filenames = glob(osp.join(root_dir, 'data/luts/*.lut')) +keys = [osp.split(filename)[-1][:-4] for filename in filenames] +values = [np.fromfile(i, dtype=np.uint8).reshape((3,256)).T.copy() for i in filenames] +for k,v in zip(keys[::-1], values[::-1]): ColorManager.add(k, v, 'base') +ColorManager.add('Grays', ColorManager.get('Grays'), 'base') + +from sciwx import ColorManager as ColorMap +ColorMap.remove() +ColorMap.adds(ColorManager.gets(tag='base')[::-1]) diff --git a/imagepy/app/startup.py b/imagepy/app/startup.py new file mode 100644 index 00000000..9d36f0b3 --- /dev/null +++ b/imagepy/app/startup.py @@ -0,0 +1,97 @@ +import wx, sys, os +from .manager import * +from . import loader +from imagepy import root_dir +from .manager import ConfigManager, DictManager + +def extend_plgs(plg): + if isinstance(plg, tuple): + return (plg[0].title, extend_plgs(plg[1])) + elif isinstance(plg, list): + return [extend_plgs(i) for i in plg] + elif isinstance(plg, str): return plg + else: return (plg.title, plg) + +def extend_tols(tol): + if isinstance(tol, tuple) and isinstance(tol[1], list): + return (tol[0].title, extend_tols(tol[1])) + elif isinstance(tol, tuple) and isinstance(tol[1], str): + return (tol[1], tol[0]) + elif isinstance(tol, list): return [extend_tols(i) for i in tol] + +def extend_wgts(wgt): + if isinstance(wgt, tuple) and isinstance(wgt[1], list): + return (wgt[0].title, extend_wgts(wgt[1])) + elif isinstance(wgt, list): return [extend_wgts(i) for i in wgt] + else: return (wgt.title, wgt) + +def load_plugins(): + data = loader.build_plugins(root_dir+'/menus') + extends = glob(root_dir+'/plugins/*/menus') + keydata = {} + for i in data[1]: + if isinstance(i, tuple): keydata[i[0].title] = i[1] + for i in extends: + plgs = loader.build_plugins(i) + data[2].extend(plgs[2]) + for j in plgs[1]: + if not isinstance(j, tuple): continue + name = j[0].title + if name in keydata: keydata[name].extend(j[1]) + else: data[1].append(j) + return extend_plgs(data[:2]), data[2] + +def load_tools(): + data = loader.build_tools('tools') + extends = glob('plugins/*/tools') + default = 'Transform' + for i in extends: + tols = loader.build_tools(i) + #if len(tols)!=0: + data[1].extend(tols[1]) + data[2].extend(tols[2]) + return extend_tols(data[:2]), data[2] + +def load_widgets(): + data = loader.build_widgets('widgets') + extends = glob('plugins/*/widgets') + for i in extends: + wgts = loader.build_widgets(i) + #if len(wgts)!=0: + data[1].extend(wgts[1]) + data[2].extend(wgts[2]) + return extend_wgts(data[:2]), data[2] + +def load_document(): + docs = [root_dir+'/doc'] + docs += glob(root_dir+'/plugins/*/doc') + for i in docs:loader.build_document(i) + +def load_dictionary(): + lans = glob(root_dir+'/lang/*') + lans += glob(root_dir+'/plugins/*/lang/*') + lans = [i for i in lans if os.path.isdir(i)] + lans = [os.path.split(i) for i in lans] + lan = sorted(set([i[1] for i in lans])) + DictManager.add('language', lan) + lans = sorted(set([i[0] for i in lans])) + for i in lans: loader.build_dictionary(i) + +def start(): + from . import ImagePy, ImageJ + import wx.lib.agw.advancedsplash as AS + app = wx.App(False) + bitmap = wx.Bitmap(root_dir+'/data/logolong.png', wx.BITMAP_TYPE_PNG) + shadow = wx.Colour(255,255,255) + asp = AS.AdvancedSplash(None, bitmap=bitmap, timeout=1000, + agwStyle=AS.AS_TIMEOUT | + AS.AS_CENTER_ON_PARENT | + AS.AS_SHADOW_BITMAP, + shadowcolour=shadow) + asp.Update() + load_document() + load_dictionary() + uistyle = ConfigManager.get('uistyle') or 'imagepy' + frame = ImageJ(None) if uistyle == 'imagej' else ImagePy(None) + frame.Show() + app.MainLoop() \ No newline at end of file diff --git a/imagepy/core/__init__.py b/imagepy/core/__init__.py deleted file mode 100644 index 8498a1b0..00000000 --- a/imagepy/core/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . wraper.imageplus import ImagePlus -from . wraper.tableplus import TablePlus \ No newline at end of file diff --git a/imagepy/core/draw/__init__.py b/imagepy/core/draw/__init__.py deleted file mode 100644 index 4287ca86..00000000 --- a/imagepy/core/draw/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# \ No newline at end of file diff --git a/imagepy/core/draw/fill.py b/imagepy/core/draw/fill.py deleted file mode 100644 index ce099aa5..00000000 --- a/imagepy/core/draw/fill.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from scipy.ndimage import label, generate_binary_structure - -def floodfill(img, x, y, thr, con): - color = img[int(y), int(x)] - buf = np.subtract(img, color, dtype=np.int16) - msk = np.abs(buf)<=thr - if buf.ndim==3: - msk = np.min(msk, axis=2) - buf = buf[:,:,0] - strc = generate_binary_structure(2, con+1) - label(msk, strc, output = buf) - msk = buf == buf[int(y), int(x)] - #msk[[0,-1],:], msk[:,[0,-1]] = 0, 0 - - #imsave('test.png', msk.astype(np.uint8)) - - return msk \ No newline at end of file diff --git a/imagepy/core/draw/paint.py b/imagepy/core/draw/paint.py deleted file mode 100644 index b6832a55..00000000 --- a/imagepy/core/draw/paint.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 15:03:15 2016 -@author: yxl -""" -from __future__ import absolute_import -import numpy as np -from ..draw import polygonfill -from ..manager import ColorManager - -def match_color(img, color): - if hasattr(color, '__iter__') and len(img.shape)==2: - return np.mean(color) - return color - -class Paint: - def __init__(self, width=1): - self.width = 1 - self.curpt = (0,0) - - def set_curpt(self, x,y): - self.curpt = x,y - - def draw_pixs(self, img, xs, ys, color=None): - mskx = (xs>=0) * (xs=0) * (ys=shape[1] or y>=shape[0]: return - if color == None:color = ColorManager.get_front() - color = match_color(img, color) - if r==1: img[y,x] = color - n = int(r) - xs,ys = np.mgrid[-n:n+1,-n:n+1] - msk = np.sqrt(xs**2+ys**2) abs(p2[1]-y):p1,p2 = p2,p1 - k =1.0* (p1[1]-y)/(y-p2[1]) - return round((p1[0]+k*p2[0])/(1+k),4) - -def scan(polys, idx, ys, st, y, cur, buf): - while cur= shape[1] or x2 < 0: continue - #rst.extend([(x,y) for x in range(max(x1,o[0]), min(x2, shape[2]))]) - img[y,x1:x2] = color - - return np.array(rst).T - -if __name__ == '__main__': - import matplotlib.pyplot as plt - from time import time - # pg.shape = (1,4,2) - pg = np.array([[(-300,-100),(1100,100),(400,1300),(100,100)]]) - # img.shape = (1000, 500) - img = np.zeros((1000, 500)) - a = time() - rc= fill(pg, img) - print(time() - a) - plt.imshow(img, interpolation='nearest',cmap='gray') - plt.show() - print("Done!") diff --git a/imagepy/core/engine/filter.py b/imagepy/core/engine/filter.py deleted file mode 100644 index d73f191e..00000000 --- a/imagepy/core/engine/filter.py +++ /dev/null @@ -1,208 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Dec 2 23:48:33 2016 -@author: yxl -""" - -import wx -import threading -import numpy as np - -from ... import IPy -from ...ui.panelconfig import ParaDialog -from ...core.manager import TextLogManager, ImageManager, \ -WindowsManager, TaskManager, WidgetsManager -from time import time - -def process_channels(plg, ips, src, des, para): - if ips.channels>1 and not 'not_channel' in plg.note: - for i in range(ips.channels): - rst = plg.run(ips, src if src is None else src[:,:,i], des[:,:,i], para) - if not rst is des and not rst is None: - des[:,:,i] = rst - else: - rst = plg.run(ips, src, des, para) - if not rst is des and not rst is None: - des[:] = rst - return des - -def process_one(plg, ips, src, img, para, callafter=None): - TaskManager.add(plg) - start = time() - transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) - transfloat = '2float' in plg.note and not ips.dtype in (np.float32, np.float64) - if transint: - buf = img.astype(np.int32) - src = src.astype(np.int32) - if transfloat: - buf = img.astype(np.float32) - src = src.astype(np.float32) - rst = process_channels(plg, ips, src, buf if transint or transfloat else img, para) - if not img is rst and not rst is None: - imgrange = {np.uint8:(0,255), np.uint16:(0,65535)}[img.dtype.type] - np.clip(rst, imgrange[0], imgrange[1], out=img) - if 'auto_msk' in plg.note and not ips.get_msk() is None: - msk = True ^ ips.get_msk() - img[msk] = src[msk] - IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start)) - ips.update = 'pix' - TaskManager.remove(plg) - if not callafter is None:callafter() - -def process_stack(plg, ips, src, imgs, para, callafter=None): - TaskManager.add(plg) - start = time() - transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) - transfloat = '2float' in plg.note and not ips.dtype in (np.float32, np.float64) - if transint: - buf = imgs[0].astype(np.int32) - src = src.astype(np.int32) - elif transfloat: - buf = imgs[0].astype(np.float32) - src = src.astype(np.float32) - else: src = src * 1 - - for i,n in zip(imgs,list(range(len(imgs)))): - #sleep(0.5) - plg.progress(n, len(imgs)) - if 'auto_snap' in plg.note : src[:] = i - if transint or transfloat: buf[:] = i - rst = process_channels(plg, ips, src, buf if transint or transfloat else i, para) - if not i is rst and not rst is None: - imgrange = {np.uint8:(0,255), np.uint16:(0,65535)}[i.dtype.type] - np.clip(rst, imgrange[0], imgrange[1], out=i) - if 'auto_msk' in plg.note and not ips.get_msk() is None: - msk = True ^ ips.get_msk() - i[msk] = src[msk] - IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start)) - ips.update = 'pix' - TaskManager.remove(plg) - if not callafter is None:callafter() - - -class Filter: - title = 'Filter' - modal = True - note = [] - 'all, 8-bit, 16-bit, int, rgb, float, not_channel, not_slice, req_roi, auto_snap, auto_msk, preview, 2int, 2float' - para = None - view = None - prgs = (None, 1) - - def __init__(self, ips=None): - if ips==None:ips = IPy.get_ips() - self.dlg = None - self.ips = ips - - def progress(self, i, n): - self.prgs = (i, n) - - def show(self, temp=ParaDialog): - self.dlg = temp(WindowsManager.get(), self.title) - self.dlg.init_view(self.view, self.para, 'preview' in self.note, modal=self.modal) - self.dlg.set_handle(lambda x:self.preview(self.ips, self.para)) - if self.modal: return self.dlg.ShowModal() - self.dlg.on_ok = lambda : self.ok(self.ips) - self.dlg.on_cancel = lambda : self.cancel(self.ips) - self.dlg.Show() - - def run(self, ips, snap, img, para = None): - return 255-img - - def check(self, ips): - note = self.note - if ips == None: - IPy.alert('no image opened!') - return False - elif 'req_roi' in note and ips.roi == None: - IPy.alert('no Roi found!') - return False - elif not 'all' in note: - if ips.get_imgtype()=='rgb' and not 'rgb' in note: - IPy.alert('do not surport rgb image') - return False - elif ips.get_imgtype()=='8-bit' and not '8-bit' in note: - IPy.alert('do not surport 8-bit image') - return False - elif ips.get_imgtype()=='16-bit' and not '16-bit' in note: - IPy.alert('do not surport 16-bit uint image') - return False - elif ips.get_imgtype()=='32-int' and not 'int' in note: - IPy.alert('do not surport 32-bit int uint image') - return False - elif 'float' in ips.get_imgtype() and not 'float' in note: - IPy.alert('do not surport float image') - return False - return True - - def preview(self, ips, para): - process_one(self, ips, ips.snap, ips.img, para, None) - - def load(self, ips):return True - - def ok(self, ips, para=None, callafter=None): - if para == None: - para = self.para - if not 'not_slice' in self.note and ips.get_nslices()>1: - if para == None:para = {} - if para!=None and 'stack' in para:del para['stack'] - win = WidgetsManager.getref('Macros Recorder') - if ips.get_nslices()==1 or 'not_slice' in self.note: - # process_one(self, ips, ips.snap, ips.img, para) - print(111) - if IPy.uimode() == 'no': - process_one(self, ips, ips.snap, ips.img, para, callafter) - else: threading.Thread(target = process_one, args = - (self, ips, ips.snap, ips.img, para, callafter)).start() - if win!=None: win.write('{}>{}'.format(self.title, para)) - elif ips.get_nslices()>1: - has, rst = 'stack' in para, None - if not has: - rst = IPy.yes_no('run every slice in current stacks?') - if 'auto_snap' in self.note and self.modal:ips.reset() - if has and para['stack'] or rst == 'yes': - para['stack'] = True - #process_stack(self, ips, ips.snap, ips.imgs, para) - print(222) - if IPy.uimode() == 'no': - process_stack(self, ips, ips.snap, ips.imgs, para, callafter) - else: threading.Thread(target = process_stack, args = - (self, ips, ips.snap, ips.imgs, para, callafter)).start() - if win!=None: win.write('{}>{}'.format(self.title, para)) - elif has and not para['stack'] or rst == 'no': - para['stack'] = False - #process_one(self, ips, ips.snap, ips.img, para) - print(333) - if IPy.uimode() == 'no': - process_one(self, ips, ips.snap, ips.img, para, callafter) - else: threading.Thread(target = process_one, args = - (self, ips, ips.snap, ips.img, para, callafter)).start() - if win!=None: win.write('{}>{}'.format(self.title, para)) - elif rst == 'cancel': pass - #ips.update = 'pix' - - def cancel(self, ips): - if 'auto_snap' in self.note: - ips.swap() - ips.update = 'pix' - - def start(self, para=None, callafter=None): - ips = self.ips - print('who am i', ips) - if not self.check(ips):return - if not self.load(ips):return - if 'auto_snap' in self.note:ips.snapshot() - - if para!=None: - self.ok(self.ips, para, callafter) - elif self.view==None: - if not self.__class__.show is Filter.show: - if self.show() == wx.ID_OK: - self.ok(self.ips, para, callafter) - else: self.ok(self.ips, para, callafter) - elif self.modal: - if self.show() == wx.ID_OK: - self.ok(ips, None, callafter) - else:self.cancel(ips) - self.dlg.Destroy() - else: self.show() \ No newline at end of file diff --git a/imagepy/core/engine/free.py b/imagepy/core/engine/free.py deleted file mode 100644 index 51d3eae8..00000000 --- a/imagepy/core/engine/free.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 3 03:57:53 2016 -@author: yxl -""" -import threading, wx - -from ... import IPy -from ...ui.panelconfig import ParaDialog -from ...core.manager import WindowsManager, TextLogManager, TaskManager, WidgetsManager -from time import time - -class Free: - title = 'Free' - view = None - para = None - prgs = (None, 1) - asyn = True - - def progress(self, i, n): - self.prgs = (i, n) - - def run(self, para=None): - print('this is a plugin') - - def runasyn(self, para, callback=None): - TaskManager.add(self) - start = time() - self.run(para) - IPy.set_info('%s: cost %.3fs'%(self.title, time()-start)) - TaskManager.remove(self) - if callback!=None:callback() - - def load(self):return True - - def show(self): - if self.view==None:return wx.ID_OK - self.dialog = ParaDialog(WindowsManager.get(), self.title) - self.dialog.init_view(self.view, self.para, False, True) - return self.dialog.ShowModal() - - def start(self, para=None, callback=None): - print('xxxxxxxxxxxxxxxxxx') - if not self.load():return - if para!=None or self.show() == wx.ID_OK: - if para==None:para = self.para - win = WidgetsManager.getref('Macros Recorder') - if win!=None: - win.write('{}>{}'.format(self.title, para)) - if self.asyn and IPy.uimode()!='no': - threading.Thread(target = self.runasyn, args = (para, callback)).start() - else: - self.runasyn(para, callback) - ''' - self.run(para) - if not callback is None: - callback() - ''' - #if not thd:t.join() - - #self.run(para) - ''' - run = lambda p=para:self.run(p) - - print( thd, '--------------------new thread') - thread = threading.Thread(None, run, ()) - thread.start() - if not thd: thread.join() - ''' \ No newline at end of file diff --git a/imagepy/core/engine/macros.py b/imagepy/core/engine/macros.py deleted file mode 100644 index f683b169..00000000 --- a/imagepy/core/engine/macros.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 29 01:48:23 2016 -@author: yxl -""" -import wx -from ... import IPy -from ...core.manager import PluginsManager -from wx.lib.pubsub import pub -from imagepy import IPy - -def stepmacros(plg, callafter=None): - plg._next(callafter) -pub.subscribe(stepmacros, 'stepmacros') - -class Macros: - def __init__(self, title, cmds): - self.title = title - self.cmds = cmds - - def _next(self, callafter=None): - if self.cur==len(self.cmds): - if self.callafter!=None: - self.callafter() - return - if len(self.cmds[self.cur])<3 or self.cmds[self.cur][0] == '#': - self.cur += 1 - return self._next(callafter) - title, para = self.cmds[self.cur].split('>') - self.cur += 1 - plg = PluginsManager.get(title)() - plg.start(eval(para), self.next) - - def next(self): - if IPy.uimode() == 'no': - self._next(self) - else: wx.CallAfter(pub.sendMessage, 'stepmacros', plg=self) - - def run(self):self.next() - #IPy.run_macros(self.cmds) - - def __call__(self): - return self - - def start(self, para=None, callafter=None): - self.callafter = callafter - self.cur = 0 - self.run() \ No newline at end of file diff --git a/imagepy/core/engine/mkdown.py b/imagepy/core/engine/mkdown.py deleted file mode 100644 index b972c316..00000000 --- a/imagepy/core/engine/mkdown.py +++ /dev/null @@ -1,17 +0,0 @@ -from imagepy import IPy - -class MkDown: - def __init__(self, title, cont, url=''): - self.title = title - self.cont = cont - - def __call__(self): return self - - def run(self): IPy.show_md(self.title, self.cont) - - def start(self, para=None, callafter=None): self.run() - -if __name__ == '__main__': - app = wx.App() - show_help('title', 'abc', '') - app.MainLoop() \ No newline at end of file diff --git a/imagepy/core/engine/simple.py b/imagepy/core/engine/simple.py deleted file mode 100644 index b989ea09..00000000 --- a/imagepy/core/engine/simple.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 3 03:32:05 2016 -@author: yxl -""" -import wx -import threading - -from ... import IPy -from ...ui.panelconfig import ParaDialog -from ..manager import TextLogManager, TaskManager, WidgetsManager -from time import time - -class Simple: - title = 'SimpleFilter' - note = [] - para = None - 'all, 8-bit, 16-bit, rgb, float, req_roi, stack, stack2d, stack3d, preview' - view = None - prgs = (None, 1) - modal = True - - def __init__(self, ips=None): - print('simple start') - self.ips = IPy.get_ips() if ips==None else ips - self.dialog = None - - def progress(self, i, n): - self.prgs = (i, n) - - def load(self, ips):return True - - def preview(self, ips, para):pass - - def show(self, temp=ParaDialog): - if self.view==None:return wx.ID_OK - self.dialog = temp(IPy.get_window(), self.title) - self.dialog.init_view(self.view, self.para, 'preview' in self.note, modal=self.modal) - self.dialog.set_handle(lambda x:self.preview(self.ips, self.para)) - if self.modal: return self.dialog.ShowModal() - self.dialog.on_ok = lambda : self.ok(self.ips) - self.dialog.on_cancel = lambda : self.cancel(self.ips) - self.dialog.Show() - - def run(self, ips, imgs, para = None):pass - - def cancel(self, ips):pass - - def ok(self, ips, para=None, callafter=None): - if para == None: para = self.para - if IPy.uimode() == 'no': - self.runasyn(ips, ips.imgs, para, callafter) - else: threading.Thread(target = self.runasyn, - args = (ips, ips.imgs, para, callafter)).start() - win = WidgetsManager.getref('Macros Recorder') - if win!=None: win.write('{}>{}'.format(self.title, para)) - - def runasyn(self, ips, imgs, para = None, callback = None): - TaskManager.add(self) - start = time() - self.run(ips, imgs, para) - IPy.set_info('%s: cost %.3fs'%(ips.title, time()-start)) - ips.update = 'pix' - TaskManager.remove(self) - if callback!=None:callback() - - def check(self, ips): - note = self.note - if ips == None: - IPy.alert('no image opened!') - return False - if 'req_roi' in note and ips.roi == None: - IPy.alert('no Roi found!') - return False - if not 'all' in note: - if ips.get_imgtype()=='rgb' and not 'rgb' in note: - IPy.alert('do not surport rgb image') - return False - elif ips.get_imgtype()=='8-bit' and not '8-bit' in note: - IPy.alert('do not surport 8-bit image') - return False - elif ips.get_imgtype()=='16-bit' and not '16-bit' in note: - IPy.alert('do not surport 16-bit uint image') - return False - elif ips.get_imgtype()=='32-int' and not 'int' in note: - IPy.alert('do not surport 32-bit int uint image') - return False - elif 'float' in ips.get_imgtype() and not 'float' in note: - IPy.alert('do not surport float image') - return False - if sum([i in note for i in ('stack','stack2d','stack3d')])>0: - if ips.get_nslices()==1: - IPy.alert('stack required!') - return False - elif 'stack2d' in note and ips.is3d: - IPy.alert('stack2d required!') - return False - elif 'stack3d' in note and not ips.is3d: - IPy.alert('stack3d required!') - return False - return True - - def start(self, para=None, callback=None): - #print self.title, para - if not self.check(self.ips):return - if not self.load(self.ips):return - - - if para!=None: - self.ok(self.ips, para, callback) - elif self.view==None: - if not self.__class__.show is Simple.show: - if self.show() == wx.ID_OK: - self.ok(self.ips, para, callback) - else: self.ok(self.ips, para, callback) - elif self.modal: - if self.show() == wx.ID_OK: - self.ok(self.ips, para, callback) - else:self.cancel(self.ips) - if not self.dialog is None: self.dialog.Destroy() - else: self.show() \ No newline at end of file diff --git a/imagepy/core/engine/table.py b/imagepy/core/engine/table.py deleted file mode 100644 index 106daf33..00000000 --- a/imagepy/core/engine/table.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 3 03:32:05 2016 -@author: yxl -""" -import wx -import threading - -from ... import IPy -from ...ui.panelconfig import ParaDialog -from ..manager import TextLogManager, TaskManager, WidgetsManager -from time import time - -class Table: - title = 'TableFilter' - note = [] - para = None - 'req_sel, req_row, req_col, snap, row_not, row_msk, col_msk, col_not, num_only, preview' - view = None - prgs = (None, 1) - modal = True - - def __init__(self, tps=None): - print('simple start') - self.tps = IPy.get_tps() if tps==None else tps - self.dialog = None - - def progress(self, i, n): - self.prgs = (i, n) - - def load(self, ips):return True - - def preview(self, tps, para): - self.run(tps, tps.data, tps.snap, para) - tps.update = True - - def show(self): - if self.view==None:return wx.ID_OK - self.dialog = ParaDialog(IPy.get_twindow(), self.title) - self.dialog.init_view(self.view, self.para, 'preview' in self.note, modal=self.modal) - self.dialog.set_handle(lambda x:self.preview(self.tps, self.para)) - if self.modal: return self.dialog.ShowModal() - self.dialog.on_ok = lambda : self.ok(self.tps) - self.dialog.on_cancel = lambda : self.cancel(self.tps) - self.dialog.Show() - - def run(self, tps, data, snap, para = None):pass - - def cancel(self, tps): - if 'snap' in self.note: - tps.data[tps.snap.columns] = tps.snap - tps.update = True - - def ok(self, tps, para=None, callafter=None): - if para == None: para = self.para - if IPy.uimode() == 'no': - self.runasyn(tps, tps.data, tps.snap, para, callafter) - else: threading.Thread(target = self.runasyn, - args = (tps, tps.data, tps.snap, para, callafter)).start() - win = WidgetsManager.getref('Macros Recorder') - if win!=None: win.write('{}>{}'.format(self.title, para)) - - def runasyn(self, tps, data, snap, para = None, callback = None): - TaskManager.add(self) - start = time() - self.run(tps, data, snap, para) - IPy.set_info('%s: cost %.3fs'%(tps.title, time()-start)) - tps.update = 'shp' - TaskManager.remove(self) - if callback!=None:callback() - - def check(self, tps): - print(self.note) - if tps == None: - IPy.alert('no table opened!') - return False - if 'req_sel' in self.note: - print(tps.rowmsk, tps.colmsk) - if isinstance(tps.rowmsk, slice) and\ - isinstance(tps.colmsk, slice): - IPy.alert('no selection!') - return False - if 'req_row' in self.note: - print(tps.rowmsk, tps.colmsk) - if isinstance(tps.rowmsk, slice): - IPy.alert('need row selection!') - return False - if 'req_col' in self.note: - print(tps.rowmsk, tps.colmsk) - if isinstance(tps.colmsk, slice): - IPy.alert('need col selection!') - return False - return True - - def snapshot(self, tps): - note = self.note - if not 'snap' in note: return None - if 'row_msk' in note: - rmsk = True - elif 'row_not' in note: - rmsk = False - else: rmsk = None - if 'col_msk' in note: - cmsk = True - elif 'col_not' in note: - cmsk = False - else: cmsk = None - only = 'num_only' in note - tps.snapshot(rmsk, cmsk, only) - - def start(self, para=None, callback=None): - #print self.title, para - if not self.check(self.tps):return - if not self.load(self.tps):return - if 'snap' in self.note:self.snapshot(self.tps) - if para!=None: - self.ok(self.tps, para, callback) - elif self.view==None: - if not self.__class__.show is Table.show: - if self.show() == wx.ID_OK: - self.ok(self.tps, para, callback) - else: self.ok(self.tps, para, callback) - elif self.modal: - if self.show() == wx.ID_OK: - self.ok(self.tps, para, callback) - else:self.cancel(self.tps) - if not self.dialog is None: self.dialog.Destroy() - else: self.show() \ No newline at end of file diff --git a/imagepy/core/engine/tool.py b/imagepy/core/engine/tool.py deleted file mode 100644 index ec7054e6..00000000 --- a/imagepy/core/engine/tool.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 3 03:55:51 2016 -@author: yxl -""" -from ... import IPy -from ...core.manager import ToolsManager - -class Tool: - title = 'Tool' - view, para = None, None - - def show(self): - if self.view == None:return - rst = IPy.get_para(self.title, self.view, self.para) - if rst!=None : self.config() - - def config(self):pass - def load(self):pass - def switch(self):pass - - def start(self): - ips = IPy.get_ips() - if not ips is None and not ips.tool is None: - ips.tool = None - ips.update = True - ToolsManager.set(self) - - def mouse_down(self, ips, x, y, btn, **key): pass - def mouse_up(self, ips, x, y, btn, **key): pass - def mouse_move(self, ips, x, y, btn, **key): pass - def mouse_wheel(self, ips, x, y, d, **key): pass \ No newline at end of file diff --git a/imagepy/core/engine/widget.py b/imagepy/core/engine/widget.py deleted file mode 100644 index 600bf648..00000000 --- a/imagepy/core/engine/widget.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Dec 3 03:57:53 2016 -@author: yxl -""" -import threading, wx, os -from imagepy import IPy, root_dir -from ..manager import WidgetsManager - -class Widget(): - def __init__(self, panel): - self.pan = panel - self.title = panel.title - - def __call__(self):return self - - def start(self): - if not WidgetsManager.getref(self.title) is None: return - pan = self.pan(IPy.curapp) - WidgetsManager.addref(pan) - IPy.curapp.auimgr.AddPane(pan, wx.aui.AuiPaneInfo(). DestroyOnClose(True). Left(). Caption(self.title) .PinButton( True ) - .Float().Resizable().FloatingSize( wx.DefaultSize ).Dockable(IPy.uimode()=='ipy').Layer( 15 ) ) - IPy.curapp.Layout() - IPy.curapp.auimgr.Update() - ''' - frame = wx.Frame(IPy.curapp) - frame.SetTitle(self.title) - logopath = os.path.join(root_dir, 'data/logo.ico') - frame.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - self.pan(frame) - frame.Fit() - frame.Show() - ''' diff --git a/imagepy/core/loader/__init__.py b/imagepy/core/loader/__init__.py deleted file mode 100644 index 4287ca86..00000000 --- a/imagepy/core/loader/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# \ No newline at end of file diff --git a/imagepy/core/manager/__init__.py b/imagepy/core/manager/__init__.py deleted file mode 100644 index a5f8cc33..00000000 --- a/imagepy/core/manager/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .colormanager import * -from .pluginmanager import * -from .windowmanager import * -from .roimanager import * -from .clipbdmanager import * -from .configmanager import * -from .shotcutmanager import * -from .taskmanager import * -from .iomanager import * -from .languagemanager import * \ No newline at end of file diff --git a/imagepy/core/manager/clipbdmanager.py b/imagepy/core/manager/clipbdmanager.py deleted file mode 100644 index e197b6c5..00000000 --- a/imagepy/core/manager/clipbdmanager.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Jan 15 01:10:37 2017 -@author: yxl -""" -class ClipBoardManager: - roi = None - img = None \ No newline at end of file diff --git a/imagepy/core/manager/colormanager.py b/imagepy/core/manager/colormanager.py deleted file mode 100644 index 76cca0a8..00000000 --- a/imagepy/core/manager/colormanager.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:24:32 2017 -@author: yxl -""" -#from __future__ import absolute_import -from ... import root_dir - -import numpy as np -import os, wx -import sys -from glob import glob - - - - -# read from the lut binarycode -# glob: return a list ot paths matching a pathname pattern. -filenames = glob(os.path.join(root_dir,'data/luts/*.lut')) -keys = [os.path.split(filename)[-1][:-4] for filename in filenames] -values = [np.fromfile(filename, dtype=np.uint8).reshape((3,256)).T.copy() for filename in filenames] - -class ColorManager: - luts = dict(zip(keys, values)) - frontcolor = (255,255,0) - backcolor = (0,0,0) - wr, wg, wb = 1.0/3, 1.0/3, 1.0/3 - - @classmethod - def get_color(cls, app=None): - rst = None - dlg = wx.ColourDialog(app) - dlg.GetColourData().SetChooseFull(True) - if dlg.ShowModal() == wx.ID_OK: - rst = dlg.GetColourData().GetColour() - dlg.Destroy() - return rst - - @classmethod - def set_front(cls, color): - if not hasattr(color, '__len__'): - color = (color, color, color) - cls.frontcolor=tuple(color) - - @classmethod - def set_back(cls, color): - if not hasattr(color, '__len__'): - color = (color, color, color) - cls.backcolor=tuple(color) - - @classmethod - def get_front(cls, one=False): - if not one:return cls.frontcolor - return np.dot((cls.wr,cls.wg,cls.wb), cls.frontcolor) - - @classmethod - def get_back(cls, one): - if not one:return cls.backcolor - return np.dot((cls.wr,cls.wg,cls.wb), cls.backcolor) - - @classmethod - def get_lut(cls, name='grays'): - if name=='grays': - lut = np.arange(256).reshape((-1,1)) - return (lut*np.ones((1,3))).astype(np.uint8) - else: return cls.luts[name].copy() \ No newline at end of file diff --git a/imagepy/core/manager/configmanager.py b/imagepy/core/manager/configmanager.py deleted file mode 100644 index 775280ee..00000000 --- a/imagepy/core/manager/configmanager.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:23:30 2017 -@author: yxl -""" -import pickle, os -from ... import root_dir -class ConfigManager: - cfg = {} - filename = os.path.join(root_dir, "preference.cfg") - #filename = os.path.join("/home/auss/Programs/Python/ImagePy/ImagePy3", "preference.cfg") - - @classmethod - def read(cls): - """Read from the congigure file: preference.cfg """ - if os.path.exists(cls.filename): - pkl_file = open(cls.filename,'r') - cls.cfg = eval(pkl_file.read().replace("\n","").encode('utf8')) - pkl_file.close() - - @classmethod - def write(cls): - pkl_file = open(cls.filename, 'w') - pkl_file.write(str(cls.cfg)) - pkl_file.close() - - @classmethod - def get(cls, key): - """Get the congigure item """ - return cls.cfg[key] if key in cls.cfg else None - - @classmethod - def set(cls, key, value): - """Set the congigure item """ - cls.cfg[key] = value - -# call the read function so that initial the ConfigManager -ConfigManager.read() - -if __name__ == '__main__': - ConfigManager.set('b',[1,2,3]) - print(ConfigManager.cfg) - ConfigManager.write() diff --git a/imagepy/core/manager/iomanager.py b/imagepy/core/manager/iomanager.py deleted file mode 100644 index a7027abc..00000000 --- a/imagepy/core/manager/iomanager.py +++ /dev/null @@ -1,56 +0,0 @@ -class ReaderManager: - reader = {} - - @classmethod - def add(cls, ext, read, tag='img'): - if not tag in cls.reader: cls.reader[tag] = {} - if isinstance(ext, str): - cls.reader[tag][ext.lower()] = read - return - for i in ext: - cls.reader[tag][i.lower()] = read - - @classmethod - def get(cls, ext=None, tag='img'): - if ext is None and tag is None: - ls = [cls.reader[i].keys() for i in cls.reader.keys()] - return sorted([x for j in ls for x in j]) - elif ext is None and not tag is None: - return sorted(cls.reader[tag].keys()) - elif not ext is None and tag is None: - for i in cls.reader.values(): - if ext.lower() in i: return i[ext.lower()] - elif not tag is None and not ext is None: - if ext.lower() in cls.reader[tag]: - return cls.reader[tag][ext.lower()] - -class WriterManager: - writer = {} - - @classmethod - def add(cls, ext, write, tag='img'): - if not tag in cls.writer: cls.writer[tag] = {} - if isinstance(ext, str): - cls.writer[tag][ext.lower()] = write - return - for i in ext: - cls.writer[tag][i.lower()] = write - - @classmethod - def get(cls, ext, tag='img'): - if ext is None: return sorted(cls.writer[tag].keys()) - if not ext.lower() in cls.writer[tag]: - return None - return cls.writer[tag][ext.lower()] - -class ViewerManager: - viewer = {} - - @classmethod - def add(cls, ext, view):cls.viewer[ext.lower()] = view - - @classmethod - def get(cls, ext='img'): - for i in ReaderManager.reader: - if ext.lower() in ReaderManager.reader[i]: - return cls.viewer[i] \ No newline at end of file diff --git a/imagepy/core/manager/languagemanager.py b/imagepy/core/manager/languagemanager.py deleted file mode 100644 index 4c997ebf..00000000 --- a/imagepy/core/manager/languagemanager.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -from ... import root_dir -from glob import glob -from .configmanager import ConfigManager -from codecs import open - -class LanguageManager: - plgs = [] - langs = {} - cur = None - filename = os.path.join(root_dir,'data/language/*.dic') - - @classmethod - def set(cls, cur): - cls.cur = None if cur=='English' else cls.langs[cur] - ConfigManager.set('language', cur) - - @classmethod - def read(cls): - path = os.path.join(root_dir,'data/language/*.dic') - for name in glob(path): - pkl_file = open(name, 'r', 'utf-8') - fp, fn = os.path.split(name) - fn, fe = os.path.splitext(fn) - cls.langs[fn] = {} - for line in pkl_file.readlines(): - k,v = line.replace('\n', '').replace('\r', '').split(':') - cls.langs[fn][k] = v - pkl_file.close() - - cur = ConfigManager.get('language') - if cur is None: return - if cur in cls.langs: cls.cur = cls.langs[cur] - - @classmethod - def write(cls): - for key in cls.langs: - dic = cls.langs[key] - titles = sorted(dic.keys()) - pkl_file = open(os.path.join(root_dir,'data/language/%s.dic'%key), 'w', 'utf-8') - for i in titles: - pkl_file.write('%s:%s\n'%(i,dic[i])) - pkl_file.close() - - @classmethod - def add(cls, key=None): - if not key is None and not ':' in key: - if not key in cls.plgs:cls.plgs.append(key) - return - titles = cls.plgs - for key in cls.langs: - dic = cls.langs[key] - for i in titles: - if not ':' in i and not i in dic: dic[i] = '--' - cls.write() - - @classmethod - def rm(cls): - titles = cls.plgs - for key in cls.langs: - dic = cls.langs[key] - for i in list(dic.keys()): - if not i in titles: del dic[i] - cls.write() - - @classmethod - def newdic(cls, key): - cls.langs[key] = {} - for i in cls.plgs: - if not ':' in i: cls.langs[key][i] = '--' - - @classmethod - def get(cls, key): - if not cls.cur is None and key in cls.cur: - if cls.cur[key]!='--': - return cls.cur[key] - return key - -LanguageManager.read() - -if __name__ == '__main__': - #ShotcutManager.set('c',[1,2,3]) - ShotcutManager.rm('c') - print(ShotcutManager.shotcuts) - ShotcutManager.write() - \ No newline at end of file diff --git a/imagepy/core/manager/pluginmanager.py b/imagepy/core/manager/pluginmanager.py deleted file mode 100644 index b526023f..00000000 --- a/imagepy/core/manager/pluginmanager.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:23:30 2017 -@author: yxl -""" -import weakref - -class ToolsManager: - tools = {} - curtool = None - - @classmethod - def set(cls, tool): - if tool.__class__ == cls.curtool.__class__:return - if cls.curtool!=None: cls.curtool.switch() - cls.curtool = tool - - @classmethod - def add(cls, tol):cls.tools[tol.title] = tol - - @classmethod - def get(cls, name=None): - if name==None:return cls.curtool - return cls.tools[name] - -class PluginsManager: - plgs = {} - - @classmethod - def add(cls, plg):cls.plgs[plg.title] = plg - - @classmethod - def get(cls, name):return cls.plgs[name] - -class WidgetsManager: - wgts, insts = {}, {} - - @classmethod - def add(cls, wgt): cls.wgts[wgt.title] = wgt - - @classmethod - def addref(cls, obj): - cls.insts[obj.title] = weakref.ref(obj) - - @classmethod - def get(cls, name):return cls.wgts[name] - - @classmethod - def getref(cls, name): - if not name in cls.insts: return None - if cls.insts[name] is None: return None - return cls.insts[name]() \ No newline at end of file diff --git a/imagepy/core/manager/roimanager.py b/imagepy/core/manager/roimanager.py deleted file mode 100644 index aaab70c5..00000000 --- a/imagepy/core/manager/roimanager.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:26:14 2017 -@author: yxl -""" -from .configmanager import ConfigManager - -class RoiManager: - rois = {} - @classmethod - def add(cls, name, roi): - cls.rois[name] = roi - - @classmethod - def get(cls, name): - if name not in cls.rois: - return None - return cls.rois[name] - - @classmethod - def get_color(cls): - color = ConfigManager.get('roicolor') - if color is None:color = (255,255,0) - return color - - @classmethod - def set_color(cls, color): - ConfigManager.set('roicolor', color) - - @classmethod - def get_lw(cls): - lw = ConfigManager.get('roilw') - if lw is None:lw = 1 - return lw - - @classmethod - def set_lw(cls, lw): - ConfigManager.set('roilw', lw) \ No newline at end of file diff --git a/imagepy/core/manager/shotcutmanager.py b/imagepy/core/manager/shotcutmanager.py deleted file mode 100644 index 441e6dbf..00000000 --- a/imagepy/core/manager/shotcutmanager.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -from ... import root_dir - -class ShotcutManager: - shotcuts = {} - filename = os.path.join(root_dir,'data/shotcut.cfg') - @classmethod - def read(cls): - if os.path.exists(cls.filename): - pkl_file = open(cls.filename,'r') - cls.shotcuts = eval(pkl_file.read().replace("\n","").encode('utf8')) - pkl_file.close() - - @classmethod - def write(cls): - pkl_file = open(cls.filename, 'w') - pkl_file.write(str(cls.shotcuts)) - pkl_file.close() - - @classmethod - def get(cls, key): - if key in cls.shotcuts: - return cls.shotcuts[key] - return None - - @classmethod - def set(cls, key, value): - cls.shotcuts[key] = value - - @classmethod - def rm(cls, key): - if key in cls.shotcuts: - cls.shotcuts.pop(key) - -ShotcutManager.read() - -if __name__ == '__main__': - #ShotcutManager.set('c',[1,2,3]) - ShotcutManager.rm('c') - print(ShotcutManager.shotcuts) - ShotcutManager.write() - \ No newline at end of file diff --git a/imagepy/core/manager/taskmanager.py b/imagepy/core/manager/taskmanager.py deleted file mode 100644 index 6a7953a5..00000000 --- a/imagepy/core/manager/taskmanager.py +++ /dev/null @@ -1,15 +0,0 @@ -class TaskManager: - tasks = {} - - @classmethod - def add(cls, key): - cls.tasks[key] = 1 - - @classmethod - def remove(cls, key): - cls.tasks.pop(key) - - @classmethod - def get(cls, key=None): - if key==None:return cls.tasks - return cls.tasks[key] \ No newline at end of file diff --git a/imagepy/core/manager/windowmanager.py b/imagepy/core/manager/windowmanager.py deleted file mode 100644 index f12ae52a..00000000 --- a/imagepy/core/manager/windowmanager.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:22:53 2017 -@author: yxl -""" -import weakref - -class WindowsManager: - wins = [] - - @classmethod - def add(cls, win): - if not win in cls.wins:cls.wins.append(win) - - @classmethod - def get(cls, title=None): - if len(cls.wins)==0:return None - if title==None:return cls.wins[0] - titles = [i.canvas.ips.title for i in cls.wins] - if not title in titles:return None - return cls.wins[titles.index(title)] - - @classmethod - def remove(cls, win): - for i in cls.wins: - if i == win: - cls.wins.remove(i) - print('remove', i.canvas.ips.title) - -class ImageManager: - imgs = [] - - @classmethod - def add(cls, ips): - print(ips) - cls.remove(ips) - callback = lambda a: cls.remove(a()) - def callback(a): - print('image removed') - cls.remove(a()) - print('image add!') - cls.imgs.insert(0, weakref.ref(ips, callback)) - - @classmethod - def remove(cls, ips): - for i in cls.imgs: - if i() == ips: cls.imgs.remove(i) - - @classmethod - def get(cls, title=None): - if len(cls.imgs)==0:return None - if title==None:return cls.imgs[0]() - titles = [i().title for i in cls.imgs] - if not title in titles:return None - return cls.imgs[titles.index(title)]() - - @classmethod - def get_titles(cls): - return [i().title for i in cls.imgs] - - @classmethod - def name(cls, name): - if name==None:name='Undefined' - titles = [i().title for i in cls.imgs] - if not name in titles : - return name - for i in range(1, 100) : - title = "{}-{}".format(name,i) - if not title in titles: - return title - ''' - @classmethod - def close(cls, name): - win = cls.get(name) - if win==None:return - cls.remove(win) - win.close() - ''' - -class TextLogManager: - windows = {} - - @classmethod - def name(cls, name): - print(list(cls.windows.keys()), name) - if name==None:name='Log' - if name not in cls.windows: - return name - for i in range(1, 100) : - title = "{}-{}".format(name,i) - if title not in cls.windows: - return title - - @classmethod - def add(cls, title, win): - cls.windows[title] = win - print(list(cls.windows.keys())) - - @classmethod - def remove(cls, name): - if name in cls.windows: - cls.windows.pop(name) - - @classmethod - def get(cls, title): - if title in cls.windows: - return cls.windows[title] - return None - - @classmethod - def get_titles(cls): - return list(cls.windows.keys()) - - @classmethod - def close(cls, name): - win = cls.get(name) - if win==None:return - cls.remove(name) - win.Close() - -class WTableManager: - wins = [] - - @classmethod - def add(cls, win): - if not win in cls.wins:cls.wins.append(win) - - @classmethod - def get(cls, title=None): - if len(cls.wins)==0:return None - if title==None:return cls.wins[0] - titles = [i.grid.tps.title for i in cls.wins] - if not title in titles:return None - return cls.wins[titles.index(title)] - - @classmethod - def remove(cls, win): - for i in cls.wins: - if i == win: - cls.wins.remove(i) - print('remove', i.grid.tps.title) - -class TableManager: - tabs = [] - - @classmethod - def add(cls, tps): - print(tps) - cls.remove(tps) - callback = lambda a: cls.remove(a()) - def callback(a): - print('table removed') - cls.remove(a()) - print('table add!') - cls.tabs.insert(0, weakref.ref(tps, callback)) - - @classmethod - def remove(cls, tps): - for i in cls.tabs: - if i() == tps: cls.tabs.remove(i) - - @classmethod - def get(cls, title=None): - if len(cls.tabs)==0:return None - if title==None:return cls.tabs[0]() - titles = [i().title for i in cls.tabs] - if not title in titles:return None - return cls.tabs[titles.index(title)]() - - @classmethod - def get_titles(cls): - return [i().title for i in cls.tabs] - - @classmethod - def name(cls, name): - if name==None:name='Table' - titles = [i().title for i in cls.tabs] - if not name in titles : - return name - for i in range(1, 100) : - title = "{}-{}".format(name,i) - if not title in titles: - return title - -class PlotManager: - windows = [] - - @classmethod - def add(cls, win): - print(win) - cls.remove(win) - callback = lambda a: cls.remove(a()) - print('windows add!') - cls.windows.insert(0, weakref.ref(win, callback)) - - @classmethod - def remove(cls, win): - for i in cls.windows: - if i() == win: cls.windows.remove(i) - - @classmethod - def get(cls, title=None): - if len(cls.windows)==0:return None - if title==None:return cls.windows[0]() - titles = [i().canvas.ips.title for i in cls.windows] - if not title in titles:return None - return cls.windows[titles.index(title)]() - - @classmethod - def get_titles(cls): - return [i().canvas.ips.title for i in cls.windows] - - @classmethod - def name(cls, name): - if name==None:name='Table' - titles = [i().canvas.ips.title for i in cls.windows] - if not name in titles : - return name - for i in range(1, 100) : - title = "{}-{}".format(name,i) - if not title in titles: - return title - - @classmethod - def close(cls, name): - win = cls.get(name) - if win==None:return - cls.remove(win) - win.close() \ No newline at end of file diff --git a/imagepy/core/myvi/__init__.py b/imagepy/core/myvi/__init__.py deleted file mode 100644 index afb5a156..00000000 --- a/imagepy/core/myvi/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -''' -As Mayavi is a little outdated, and not support wxphoenix -So I wrote a simple one, and remove two a, M(a)y(a)vi.self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0)self.color = cs if isinstance(cs, tuple) else (0,0,0) -''' -from .canvas3d import * -from .frame3d import * -from .manager import * -from .util import * \ No newline at end of file diff --git a/imagepy/core/myvi/canvas3d.py b/imagepy/core/myvi/canvas3d.py deleted file mode 100644 index b26ff358..00000000 --- a/imagepy/core/myvi/canvas3d.py +++ /dev/null @@ -1,287 +0,0 @@ -import sys -import moderngl -import numpy as np -import wx, math -import wx.glcanvas as glcanvas -from .manager import * -import os.path as osp -from wx.lib.pubsub import pub -from .util import build_surf2d, build_surf3d, build_ball, build_balls - -#---------------------------------------------------------------------- -from wx.glcanvas import WX_GL_DEPTH_SIZE -attribs=[WX_GL_DEPTH_SIZE,32,0,0]; - -class Canvas3D(glcanvas.GLCanvas): - def __init__(self, parent, manager=None): - attribList = attribs = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24) - glcanvas.GLCanvas.__init__(self, parent, -1, attribList=attribList) - self.init = False - self.context = glcanvas.GLContext(self) - self.manager = self.manager = Manager() if manager is None else manager - self.size = None - - self.SetBackgroundStyle(wx.BG_STYLE_PAINT) - - self.Bind(wx.EVT_SIZE, self.OnSize) - self.Bind(wx.EVT_PAINT, self.OnPaint) - self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) - self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp) - self.Bind(wx.EVT_MOTION, self.OnMouseMotion) - self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) - self.lastx, self.lasty = None, None - self.update = True - #print('init===========') - - def InitGL(self): - self.manager.on_ctx() - self.DoSetViewport() - self.manager.reset() - - def OnDraw(self): - self.manager.set_viewport(0, 0, self.Size.width, self.Size.height) - #self.manager.count_mvp() - self.manager.draw() - self.SwapBuffers() - - def OnSize(self, event): - wx.CallAfter(self.DoSetViewport) - event.Skip() - - def DoSetViewport(self): - size = self.size = self.GetClientSize() - self.SetCurrent(self.context) - if not self.manager is None and not self.manager.ctx is None: - self.manager.set_viewport(0, 0, self.Size.width, self.Size.height) - - def OnPaint(self, event): - dc = wx.PaintDC(self) - self.SetCurrent(self.context) - #print(self, '=====', self.init) - if not self.init: - self.InitGL() - self.init = True - self.OnDraw() - - def OnMouseDown(self, evt): - self.CaptureMouse() - self.lastx, self.lasty = evt.GetPosition() - - def OnMouseUp(self, evt): - self.ReleaseMouse() - - def OnMouseMotion(self, evt): - self.SetFocus() - if evt.Dragging() and evt.LeftIsDown(): - x, y = evt.GetPosition() - dx, dy = x-self.lastx, y-self.lasty - self.lastx, self.lasty = x, y - #self.manager.h -= dx/200 - angx = self.manager.angx - dx/200 - angy = self.manager.angy + dy/200 - #print('ang', angx, angy) - self.manager.set_pers(angx=angx, angy=angy) - self.Refresh(False) - - def save_bitmap(self, path): - context = wx.ClientDC( self ) - memory = wx.MemoryDC( ) - x, y = self.ClientSize - bitmap = wx.Bitmap( x, y, -1 ) - memory.SelectObject( bitmap ) - memory.Blit( 0, 0, x, y, context, 0, 0) - memory.SelectObject( wx.NullBitmap) - bitmap.SaveFile( path, wx.BITMAP_TYPE_PNG ) - - def OnMouseWheel(self, evt): - k = 0.9 if evt.GetWheelRotation()>0 else 1/0.9 - self.manager.set_pers(l=self.manager.l*k) - self.Refresh(False) - #self.update = True - -class Viewer3D(wx.Panel): - def __init__( self, parent, manager=None): - wx.Panel.__init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL ) - #self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) - sizer = wx.BoxSizer( wx.VERTICAL ) - self.canvas = Canvas3D(self, manager) - self.toolbar = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize) - tsizer = wx.BoxSizer( wx.HORIZONTAL ) - - root = osp.abspath(osp.dirname(__file__)) - - #self.SetIcon(wx.Icon('data/logo.ico', wx.BITMAP_TYPE_ICO)) - - self.btn_x = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap( osp.join(root, 'imgs/x-axis.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_x, 0, wx.ALL, 1 ) - self.btn_y = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap( osp.join(root, 'imgs/y-axis.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_y, 0, wx.ALL, 1 ) - self.btn_z = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap( osp.join(root, 'imgs/z-axis.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_z, 0, wx.ALL, 1 ) - tsizer.Add(wx.StaticLine( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL), 0, wx.ALL|wx.EXPAND, 2 ) - self.btn_pers = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap( osp.join(root, 'imgs/isometric.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_pers, 0, wx.ALL, 1 ) - self.btn_orth = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap( osp.join(root, 'imgs/parallel.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_orth, 0, wx.ALL, 1 ) - tsizer.Add(wx.StaticLine( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL), 0, wx.ALL|wx.EXPAND, 2 ) - self.btn_save = wx.BitmapButton( self.toolbar, wx.ID_ANY, wx.Bitmap(osp.join(root, 'imgs/save.png'), wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) - tsizer.Add( self.btn_save, 0, wx.ALL, 1 ) - - self.btn_color = wx.ColourPickerCtrl( self.toolbar, wx.ID_ANY, wx.Colour( 128, 128, 128 ), wx.DefaultPosition, wx.DefaultSize, wx.CLRP_DEFAULT_STYLE ) - tsizer.Add( self.btn_color, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) - self.toolbar.SetSizer( tsizer ) - - self.settingbar = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - ssizer = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText1 = wx.StaticText( self.settingbar, wx.ID_ANY, u"Object:", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText1.Wrap( -1 ) - ssizer.Add( self.m_staticText1, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) - - cho_objChoices = ['None'] - self.cho_obj = wx.Choice( self.settingbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_objChoices, 0 ) - self.cho_obj.SetSelection( 0 ) - ssizer.Add( self.cho_obj, 0, wx.ALL, 1 ) - - self.chk_visible = wx.CheckBox( self.settingbar, wx.ID_ANY, u"visible", wx.DefaultPosition, wx.DefaultSize, 0 ) - ssizer.Add( self.chk_visible, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) - - self.col_color = wx.ColourPickerCtrl( self.settingbar, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize, wx.CLRP_DEFAULT_STYLE ) - ssizer.Add( self.col_color, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) - - self.m_staticText2 = wx.StaticText( self.settingbar, wx.ID_ANY, u"Blend:", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText2.Wrap( -1 ) - ssizer.Add( self.m_staticText2, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) - - self.sli_blend = wx.Slider( self.settingbar, wx.ID_ANY, 10, 0, 10, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL ) - ssizer.Add( self.sli_blend, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) - self.settingbar.SetSizer(ssizer) - - self.m_staticText2 = wx.StaticText( self.settingbar, wx.ID_ANY, u"Mode:", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText2.Wrap( -1 ) - ssizer.Add( self.m_staticText2, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) - - cho_objChoices = ['mesh', 'grid'] - self.cho_mode = wx.Choice( self.settingbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_objChoices, 0 ) - self.cho_mode.SetSelection( 0 ) - ssizer.Add( self.cho_mode, 0, wx.ALL, 1 ) - - sizer.Add( self.toolbar, 0, wx.EXPAND |wx.ALL, 0 ) - sizer.Add( self.canvas, 1, wx.EXPAND |wx.ALL, 0) - sizer.Add( self.settingbar, 0, wx.EXPAND |wx.ALL, 0 ) - - self.SetSizer( sizer ) - self.Layout() - self.Centre( wx.BOTH ) - - self.btn_x.Bind( wx.EVT_BUTTON, self.view_x) - self.btn_y.Bind( wx.EVT_BUTTON, self.view_y) - self.btn_z.Bind( wx.EVT_BUTTON, self.view_z) - self.btn_save.Bind( wx.EVT_BUTTON, self.on_save) - self.btn_pers.Bind( wx.EVT_BUTTON, lambda evt, f=self.on_pers:f(True)) - self.btn_orth.Bind( wx.EVT_BUTTON, lambda evt, f=self.on_pers:f(False)) - self.btn_color.Bind( wx.EVT_COLOURPICKER_CHANGED, self.on_bgcolor ) - - self.cho_obj.Bind( wx.EVT_CHOICE, self.on_select ) - self.cho_mode.Bind( wx.EVT_CHOICE, self.on_mode ) - self.chk_visible.Bind( wx.EVT_CHECKBOX, self.on_visible) - self.sli_blend.Bind( wx.EVT_SCROLL, self.on_blend ) - self.col_color.Bind( wx.EVT_COLOURPICKER_CHANGED, self.on_color ) - - if manager!=None: self.cho_obj.Set(list(manager.objs.keys())) - pub.subscribe(self.add_surf, 'add_surf') - pub.subscribe(self.add_mark, 'add_mark') - - def view_x(self, evt): - self.canvas.manager.reset(angx=0) - self.canvas.Refresh(False) - - - def view_y(self, evt): - self.canvas.manager.reset(angx=pi/2) - self.canvas.Refresh(False) - - def view_z(self, evt): - self.canvas.manager.reset(angy=pi/2-1e-4) - self.canvas.Refresh(False) - - def on_pers(self, b): - self.canvas.manager.set_pers(pers=b) - self.canvas.Refresh(False) - - def on_bgcolor(self, event): - c = tuple(np.array(event.GetColour()[:3])/255) - self.canvas.manager.set_background(c) - self.canvas.Refresh(False) - - def on_save(self, evt): - dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} - filt = 'PNG files (*.png)|*.png' - dialog = wx.FileDialog(self, 'Save Picture', '', '', filt, wx.FD_SAVE) - rst = dialog.ShowModal() - if rst == wx.ID_OK: - path = dialog.GetPath() - self.canvas.save_bitmap(path) - dialog.Destroy() - - def get_obj(self, name): - return self.canvas.manager.get_obj(name) - - def on_visible(self, evt): - self.curobj.set_style(visible=evt.IsChecked()) - self.canvas.Refresh(False) - - def on_blend(self, evt): - self.curobj.set_style(blend=evt.GetInt()/10.0) - self.canvas.Refresh(False) - - def on_mode(self, evt): - self.curobj.set_style(mode=evt.GetString()) - self.canvas.Refresh(False) - - def on_color(self, evt): - c = tuple(np.array(evt.GetColour()[:3])/255) - self.curobj.set_style(color = c) - self.canvas.Refresh(False) - - def on_select(self, evt): - n = self.cho_obj.GetSelection() - self.curobj = self.get_obj(self.cho_obj.GetString(n)) - self.chk_visible.SetValue(self.curobj.visible) - color = (np.array(self.curobj.color)*255).astype(np.uint8) - self.col_color.SetColour((tuple(color))) - self.sli_blend.SetValue(int(self.curobj.blend*10)) - self.cho_mode.SetSelection(['mesh', 'grid'].index(self.curobj.mode)) - - def add_surf_asyn(self, name, vts, fs, ns, cs, mode=None, blend=None, color=None, visible=None): - wx.CallAfter(pub.sendMessage, 'add_surf', name=name, vts=vts, fs=fs, ns=ns, cs=cs, obj=self, - mode=mode, blend=blend, color=color, visible=visible) - - def add_surf(self, name, vts, fs, ns, cs, obj=None, mode=None, blend=None, color=None, visible=None): - if obj!=None and not obj is self:return - manager = self.canvas.manager - surf = manager.add_surf(name, vts, fs, ns, cs) - surf.set_style(mode=mode, blend=blend, color=color, visible=visible) - if len(manager.objs)==1: - manager.reset() - self.cho_obj.Append(name) - self.canvas.Refresh(False) - - def add_mark_asyn(self, name, vts, fs, ps, h, cs): - wx.CallAfter(pub.sendMessage, 'add_mark', name=name, vts=vts, fs=fs, ps=ps, h=h, cs=cs) - - def add_mark(self, name, vts, fs, ps, h, cs): - manager = self.canvas.manager - surf = manager.add_mark(name, vts, fs, ps, h, cs) - if len(manager.objs)==1: - manager.reset() - self.cho_obj.Append(name) - self.canvas.Refresh(False) - -if __name__ == '__main__': - app = wx.App(False) - frm = wx.Frame(None, title='GLCanvas Sample') - canvas = Canvas3D(frm) - - frm.Show() - app.MainLoop() \ No newline at end of file diff --git a/imagepy/core/myvi/frame3d.py b/imagepy/core/myvi/frame3d.py deleted file mode 100644 index 1455b7f8..00000000 --- a/imagepy/core/myvi/frame3d.py +++ /dev/null @@ -1,43 +0,0 @@ -import wx, os -from .canvas3d import Canvas3D -from . import util -from . import canvas3d -import numpy as np - -class Frame3D(wx.Frame): - frms = {} - - def __init__(self, parent, title='Frame3D', manager=None): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = title, pos = wx.DefaultPosition, size = wx.Size( 800,600 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) - self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) - sizer = wx.BoxSizer( wx.VERTICAL ) - root = os.path.abspath(os.path.dirname(__file__)) - - self.SetIcon(wx.Icon(os.path.join(root, 'imgs/logo.ico'), wx.BITMAP_TYPE_ICO)) - self.viewer = canvas3d.Viewer3D( self , manager) - sizer.Add( self.viewer, 1, wx.EXPAND |wx.ALL, 0 ) - self.Bind(wx.EVT_CLOSE, self.on_closing) - - self.SetSizer( sizer ) - self.Layout() - - self.Centre( wx.BOTH ) - - @classmethod - def figure(cls, parent, title): - if not title in cls.frms: - cls.frms[title] = Frame3D(parent, title) - cls.frms[title].Show() - # wx.Yield() - return cls.frms[title] - - def on_closing(self, event): - if self.GetTitle() in Frame3D.frms: - Frame3D.frms.pop(self.GetTitle()) - event.Skip() - -if __name__ == '__main__': - app = wx.App(False) - frm = Frame3D(None, title='GLCanvas Sample') - frm.Show() - app.MainLoop() diff --git a/imagepy/core/myvi/manager.py b/imagepy/core/myvi/manager.py deleted file mode 100644 index e7ad218c..00000000 --- a/imagepy/core/myvi/manager.py +++ /dev/null @@ -1,274 +0,0 @@ -import struct -import numpy as np -import moderngl -from time import time - -from scipy.misc import imread -import numpy as np -from math import sin, cos, tan, pi -import scipy.ndimage as nimg - -def look_at(eye, target, up, dtype=None): - forward = (target - eye)/np.linalg.norm(target - eye) - side = (np.cross(forward, up))/np.linalg.norm(np.cross(forward, up)) - up = (np.cross(side, forward)/np.linalg.norm(np.cross(side, forward))) - - return np.array(( - (side[0], up[0], -forward[0], 0.), - (side[1], up[1], -forward[1], 0.), - (side[2], up[2], -forward[2], 0.), - (-np.dot(side, eye), -np.dot(up, eye), np.dot(forward, eye), 1.0) - ), dtype=np.float32) - -def perspective(xmax, ymax, near, far): - left, right = -xmax, xmax - bottom, top = -ymax, ymax - - A = (right + left) / (right - left) - B = (top + bottom) / (top - bottom) - C = -(far + near) / (far - near) - D = -2. * far * near / (far - near) - E = 2. * near / (right - left) - F = 2. * near / (top - bottom) - return np.array(( - ( E, 0., 0., 0.), - ( 0., F, 0., 0.), - ( A, B, C,-1.), - ( 0., 0., D, 0.), - ), dtype=np.float32) - -def orthogonal(xmax, ymax, near, far): - rml = xmax * 2 - tmb = ymax * 2 - fmn = far - near - - A = 2. / rml - B = 2. / tmb - C = -2. / fmn - Tx = 0 - Ty = 0 - Tz = -(far + near) / fmn - - return np.array(( - ( A, 0., 0., 0.), - (0., B, 0., 0.), - (0., 0., C, 0.), - (Tx, Ty, Tz, 1.), - ), dtype=np.float32) - -class Surface: - def __init__(self, vts, ids, ns, cs=(0,0,1)): - self.vts, self.ids, self.ns, self.cs = vts, ids, ns, cs - self.box = np.vstack((vts.min(axis=0), vts.max(axis=0))) - self.mode, self.blend, self.visible = 'mesh', 1.0, True - self.color = cs if isinstance(cs, tuple) else (0,0,0) - - def on_ctx(self, ctx, prog): - self.ctx = ctx - vts, ids, ns, cs = self.vts, self.ids, self.ns, self.cs; - buf = self.buf = np.zeros((len(vts), 9), dtype=np.float32) - buf[:,0:3], buf[:,3:6], buf[:,6:9] = vts, ns, cs - self.vbo = ctx.buffer(buf.tobytes()) - ibo = ctx.buffer(ids.tobytes()) - - content = [(self.vbo, '3f 3f 3f', 'v_vert', 'v_norm', 'v_color')] - self.vao = ctx.vertex_array(prog, content, ibo) - self.prog = prog - - def set_style(self, mode=None, blend=None, color=None, visible=None): - if not mode is None: self.mode = mode - if not blend is None: self.blend=blend - if not visible is None: self.visible=visible - if not color is None: - self.buf[:,6:9] = color - self.vbo.write(self.buf.tobytes()) - self.color = color if isinstance(color, tuple) else (0,0,0) - - def draw(self, mvp): - if not self.visible: return - self.ctx.line_width = 1 - mvp = np.dot(*mvp) - self.prog['Mvp'].write(mvp.astype(np.float32).tobytes()) - self.prog['blend'].value = self.blend - - self.vao.render({'mesh':moderngl.TRIANGLES, 'grid':moderngl.LINES}[self.mode]) - -class MarkText: - def __init__(self, vts, ids, os, h, color): - self.vts, self.ids, self.color, self.os, self.h = vts, ids, color, os, h - self.blend, self.box, self.visible, self.mode = 1, None, True, 'grid' - - def on_ctx(self, ctx, prog): - self.ctx = ctx - vts, ids, os = self.vts, self.ids, self.os - buf = self.buf = np.zeros((len(vts), 6), dtype=np.float32) - buf[:,0:3], buf[:,3:6] = vts, os - self.vbo = ctx.buffer(buf.tobytes()) - ibo = ctx.buffer(ids.tobytes()) - content = [(self.vbo, '3f 3f', 'v_vert', 'v_pos')] - self.vao = ctx.vertex_array(prog, content, ibo) - self.prog = prog - - def set_style(self, mode=None, blend=None, color=None, visible=None): - if not visible is None: self.visible = visible - if not color is None: self.color = color - - def draw(self, mvp): - if not self.visible: return - self.ctx.line_width = 2 - self.prog['mv'].write(mvp[0].astype(np.float32).tobytes()) - self.prog['proj'].write(mvp[1].astype(np.float32).tobytes()) - self.prog['f_color'].write(np.array(self.color).astype(np.float32).tobytes()) - self.prog['h'].value = self.h - self.vao.render(moderngl.LINES) - -class Manager: - def __init__(self): - self.h, self.v, self.r = 1.5, 0, 300 - self.ratio, self.dial = 1.0, 1.0 - self.pers, self.center = True, (0,0,0) - self.background = 0.4, 0.4, 0.4 - self.objs = {} - self.ctx = None - - def on_ctx(self): - self.ctx = moderngl.create_context() - self.prog_suf = self.ctx.program( - vertex_shader=''' - #version 330 - uniform mat4 Mvp; - in vec3 v_vert; - in vec3 v_norm; - in vec3 v_color; - out vec3 f_norm; - out vec3 f_color; - void main() { - gl_Position = Mvp * vec4(v_vert, 1); - f_norm = v_norm; - f_color = v_color; - } - ''', - fragment_shader=''' - #version 330 - uniform vec3 light = vec3(1,1,0.8); - uniform float blend = 0.1; - in vec3 f_norm; - in vec3 f_color; - out vec4 color; - void main() { - float d = clamp((dot(light, f_norm)+1)*0.5, 0, 1); - color = vec4(f_color*d, blend); - } - ''' - ) - - self.prog_txt = self.ctx.program( - vertex_shader=''' - #version 330 - uniform mat4 mv; - uniform mat4 proj; - uniform float h; - in vec3 v_vert; - in vec3 v_pos; - void main() { - vec4 o = mv * vec4(v_pos, 1); - gl_Position = proj *(o + vec4(v_vert.x*h, v_vert.y*h, v_vert.z, 0)); - } - ''', - fragment_shader=''' - #version 330 - uniform vec3 f_color; - out vec4 color; - void main() { - color = vec4(f_color, 1); - } - ''') - - for i in self.objs.values(): - if isinstance(i, Surface): i.on_ctx(self.ctx, self.prog_suf) - if isinstance(i, MarkText): i.on_ctx(self.ctx, self.prog_txt) - - def add_surf(self, name, vts, ids, ns=None, cs=(0,0,1), real=True): - surf = Surface(vts, ids, ns, cs) - if not real: surf.box = None - if not self.ctx is None: - surf.on_ctx(self.ctx, self.prog_suf) - self.objs[name] = surf - self.count_box() - return surf - - def add_mark(self, name, vts, ids, o, h, cs=(0,0,1)): - mark = MarkText(vts, ids, o, h, cs) - if not self.ctx is None: - mark.on_ctx(self.ctx, self.prog_txt) - self.objs[name] = mark - return mark - - - def get_obj(self, key): - if not key in self.objs: return None - return self.objs[key] - - def draw(self): - self.ctx.clear(*self.background) - self.ctx.enable(moderngl.DEPTH_TEST) - #self.ctx.enable(ModernGL.CULL_FACE) - self.ctx.enable(moderngl.BLEND) - for i in self.objs.values(): i.draw(self.mvp) - - def count_box(self): - minb = np.array([i.box[0] for i in self.objs.values() if not i.box is None]).min(axis=0) - maxb = np.array([i.box[1] for i in self.objs.values() if not i.box is None]).max(axis=0) - self.box = np.vstack((minb, maxb)) - #print(self.box) - self.center = self.box.mean(axis=0) - self.dial = np.linalg.norm(self.box[1]-self.box[0]) - - def count_mvp(self): - #print('mvp') - ymax = (1.0 if self.pers else self.l) * np.tan(self.fovy * np.pi / 360.0) - xmax = ymax * self.ratio - proj = (perspective if self.pers else orthogonal)(xmax, ymax, 1.0, 100000) - lookat = look_at(self.eye, self.center, (0.0,0.0,1.0)) - self.mvp = (lookat, proj) - - def set_viewport(self, x, y, width, height): - self.ctx.viewport = (x, y, width, height) - self.ratio = width*1.0/height - - def set_background(self, rgb): - self.background = rgb - - def reset(self, fovy=45, angx=0, angy=0): - self.fovy, self.angx, self.angy = fovy, angx, angy - self.l = self.dial/2/(tan(fovy*pi/360)) - v = np.array([cos(angy)*cos(angx), cos(angy)*sin(angx), sin(angy)]) - self.eye = self.center + v*self.l*1 - self.count_mvp() - #print('reset', self.eye, self.center) - - def set_pers(self, fovy=None, angx=None, angy=None, l=None, pers=None): - if not pers is None: self.pers = pers - if not fovy is None: self.fovy = fovy - if not angx is None: self.angx = angx - if not angy is None: self.angy = angy - self.angx %= 2*pi - self.angy = max(min(pi/2-1e-4, self.angy), -pi/2+1e-4) - if not l is None: self.l = l - v = np.array([cos(self.angy)*cos(self.angx), - cos(self.angy)*sin(self.angx), sin(self.angy)]) - - self.eye = self.center + v*self.l*1 - self.count_mvp() - - def show(self, title='Myvi'): - import wx - from .frame3d import Frame3D - app = wx.App(False) - self.locale = wx.Locale(wx.LANGUAGE_ENGLISH) - Frame3D(None, title, self).Show() - app.MainLoop() - -if __name__ == '__main__': - img = imread('gis.png') - build_surf2d(img) \ No newline at end of file diff --git a/imagepy/core/myvi/txtmark.py b/imagepy/core/myvi/txtmark.py deleted file mode 100644 index f4ace5a7..00000000 --- a/imagepy/core/myvi/txtmark.py +++ /dev/null @@ -1,13 +0,0 @@ -lib = {'0':([(0,0.5,0.5,0,0)],[(1,1,0,0,1)],0.5), - '1':([(0.25,0.25)], [(0,1)], 0.5), - '2':([(0,0.5,0.5,0,0,0.5)], [(1,1,0.5,0.5,0,0)], 0.5), - '3':([(0,0.5,0.5,0),(0,0.5)],[(1,1,0,0),(0.5,0.5)], 0.5), - '4':([(0,0,0.5),(0.5,0.5)],[(1,0.5,0.5),(1,0)],0.5), - '5':([(0.5,0,0,0.5,0.5,0)], [(1,1,0.5,0.5,0,0)], 0.5), - '6':([(0.5,0,0,0.5,0.5,0,0)], [(1,1,0.5,0.5,0,0,0.5)], 0.5), - '7':([(0,0.5,0.5)], [(1,1,0)], 0.5), - '8':([(0.5,0.5,0,0,0.5,0.5,0,0)], [(0.5,1,1,0.5,0.5,0,0,0.5)], 0.5), - '9':([(0.5,0.5,0,0,0.5,0.5,0)], [(0.5,1,1,0.5,0.5,0,0)], 0.5), - 'I':([(0,0.5),(0.25,0.25),(0,0.5)],[(1,1),(1,0),(0,0)],0.5), - 'D':([(0,0.25,0.4,0.5,0.5,0.4,0.25,0),(0.1,0.1)],[(1,1,0.9,0.75,0.25,0.1,0,0),(0,1)],0.5), - ':':([(0.2,0.3),(0.2,0.3)],[(0.75,0.75),(0.25,0.25)],0.5)} \ No newline at end of file diff --git a/imagepy/core/pixel/__init__.py b/imagepy/core/pixel/__init__.py deleted file mode 100644 index 4287ca86..00000000 --- a/imagepy/core/pixel/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# \ No newline at end of file diff --git a/imagepy/core/pixel/bliter.py b/imagepy/core/pixel/bliter.py deleted file mode 100644 index ed972f19..00000000 --- a/imagepy/core/pixel/bliter.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Dec 11 22:58:58 2016 -@author: yxl -""" -import numpy as np - -def count_box(s1, s2, c, r): - box1c = slice(max(0, c), min(s1[1], c+s2[1])) - box1r = slice(max(0, r), min(s1[0], r+s2[0])) - box2c = slice(max(-c, 0), min(s2[1], s1[1]-c)) - box2r = slice(max(-r, 0), min(s2[0], s1[0]-r)) - return (box1r, box1c), (box2r, box2c) - -def blit_copy(img1, img2): - img1[:] = img2 - -def blit_max(img1, img2): - msk = img2>img1 - img1[msk] = img2[msk] - -def blit_min(img1, img2): - msk = img2img1 - umsk = True ^ msk - img1[msk] = img2[msk] - img1[msk] - img1[umsk] = img1[umsk] - img2[umsk] - -def blit_add(img1, img2): - if img1.dtype == np.uint8: - msk = img2 > 255-img1 - img1 += img2 - img1[msk] = 255 - else: img1 += img2 - -def blit_substract(img1, img2): - if img1.dtype == np.uint8: - msk = img1self.box[2]:self.box[2]=x - if yself.box[3]:self.box[3]=y - - def get_box(self): - if self.infoupdate: - self.countbox() - self.infoupdate=False - return self.box - - def pick(self, x, y, lim): - return self.snap(x, y, lim) - - def draged(self, ox, oy, nx, ny, i): - i[0][i[1]] = (nx, ny) - self.update, self.infoupdate = True, True - - def info(self, ips, cur): - k, u = ips.unit - if cur==None:return - x, y = cur[0][cur[1]] - IPy.set_info('Line : points:%.0f x:%.1f y:%.1f'%(len(cur[0]), x*k, y*k)) - - def draw(self, dc, f): - dc.SetPen(wx.Pen(RoiManager.get_color(), width=RoiManager.get_lw(), style=wx.SOLID)) - for line in self.body: - if len(line)>1: - dc.DrawLines([f(*i) for i in line]) - for i in line:dc.DrawCircle(f(*i),2) - - ''' - def affine(self, m, o): - plg = LineRoi() - plg.body = affine(self.body, m, o) - plg.update = True - plg.infoupdate = True - return plg - ''' - - def sketch(self, img, w=1, color=None): - pen = paint.Paint() - for i in self.body: - xs, ys = [x[0] for x in i], [x[1] for x in i] - pen.draw_path(img, xs, ys, w, color) - - def fill(self, img, color=None): - self.sketch(img, 1, color) \ No newline at end of file diff --git a/imagepy/core/roi/operator.py b/imagepy/core/roi/operator.py deleted file mode 100644 index 85f6412a..00000000 --- a/imagepy/core/roi/operator.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Nov 24 01:57:23 2016 -@author: yxl -""" - -def affine(body, m, o): - if isinstance(body, list): - return [affine(i, m, o) for i in body] - if isinstance(body, tuple): - return tuple(m.dot(body)+o) - diff --git a/imagepy/core/roi/ovalroi.py b/imagepy/core/roi/ovalroi.py deleted file mode 100644 index bbbc7e15..00000000 --- a/imagepy/core/roi/ovalroi.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Nov 11 21:29:59 2016 -@author: yxl -""" -import wx -import numpy as np -from ..draw import paint -from .roi import ROI -from .polygonroi import PolygonRoi -from ..manager import RoiManager -from imagepy import IPy - -class OvalRoi(ROI): - dtype = 'rect' - def __init__(self, l=0, t=0, r=0, b=0): - self.body = [] - self.update = False - self.lt, self.tp, self.rt, self.bm = l, t, r, b - self.commit() - - def snap(self, x, y, lim): - if abs(x-self.lt)1: - dc.DrawLines([f(*i) for i in self.body]) - for i in [self.lt, (self.lt+self.rt)/2, self.rt]: - for j in [self.tp, (self.tp+self.bm)/2, self.bm]: - dc.DrawCircle(f(i,j),2) - - def sketch(self, img, w=1, color=None): - pen = paint.Paint() - xs, ys = [x[0] for x in self.body], [x[1] for x in self.body] - pen.draw_path(img, xs, ys, w, color) - - def fill(self, img, color=None): - pen = paint.Paint() - pen.fill_polygon(self.body, img, [], color) \ No newline at end of file diff --git a/imagepy/core/roi/pointroi.py b/imagepy/core/roi/pointroi.py deleted file mode 100644 index 386e92e2..00000000 --- a/imagepy/core/roi/pointroi.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Nov 8 22:35:55 2016 -@author: yxl -""" -import wx -from ..draw import paint -from .roi import ROI -from ..manager import RoiManager -from imagepy import IPy - -class PointRoi(ROI): - dtype = 'point' - def __init__(self, body=None): - self.body = body if body!=None else [] - self.update = body!=[] - self.infoupdate = body!=[] - - def add(self, p): - self.body.append(p) - self.update, self.infoupdate = True, True - - def snap(self, x, y, lim): - cur, minl = None, 1e8 - for i in self.body: - d = (i[0]-x)**2+(i[1]-y)**2 - if d < minl:cur,minl = i,d - if minl**0.5>lim:return None - return self.body.index(cur) - - def pick(self, x, y, lim): - return self.snap(x, y, lim) - - def draged(self, ox, oy, nx, ny, i): - self.body[i] = (nx, ny) - self.update = False - - def countbox(self): - self.box = [1000,1000,-1000,-1000] - for x, y in self.body: - if xself.box[2]:self.box[2]=x - if yself.box[3]:self.box[3]=y - - def get_box(self): - if self.infoupdate: - self.countbox() - self.infoupdate=False - return self.box - - def info(self, ips, cur): - k, u = ips.unit - if cur==None:return - x, y = self.body[cur] - IPy.set_info('points:%.0f x:%.1f y:%.1f'%(len(self.body), x*k, y*k)) - - ''' - def affine(self, m, o): - plg = PointRoi() - plg.body = affine(self.body, m, o) - plg.update = True - plg.infoupdate = True - return plg - ''' - - def draw(self, dc, f): - dc.SetPen(wx.Pen(RoiManager.get_color(), width=RoiManager.get_lw(), style=wx.SOLID)) - for i in self.body: - dc.DrawCircle(f(*i), 2) - - def sketch(self, img, w=1, color=None): - pen = paint.Paint() - for i in self.body: - pen.draw_point(img, i[0], i[1], w, color) - - def fill(self, img, color=None): - self.sketch(img, 1, color) - -if __name__ == '__main__': - seq = [(0,0),(1,0),(0,1),(1,1),(0,0)] - p = Polygon(seq) - mp = toSegment(seq) \ No newline at end of file diff --git a/imagepy/core/roi/polygonroi.py b/imagepy/core/roi/polygonroi.py deleted file mode 100644 index 6e9f2057..00000000 --- a/imagepy/core/roi/polygonroi.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Nov 11 12:10:33 2016 -@author: yxl -""" -import wx -from shapely.geometry import Polygon, Point, LineString -from shapely.ops import cascaded_union -from shapely.ops import polygonize -from ..draw import paint -from .roi import ROI -from ..manager import RoiManager -from imagepy import IPy - -def parse_poly(geom): - out = list(geom.exterior.coords) - inn = [list(i.coords) for i in list(geom.interiors)] - return [out, inn] - -def parse_mpoly(geom): - if geom.geom_type == 'Polygon': - return[i for i in [parse_poly(geom)] if i != None] - return [parse_poly(i) for i in list(geom)] - -def to_segment(pg): - p = Polygon(pg) if len(pg)>2 else Polygon(*pg) - if p.is_valid: return [p] - if len(pg)<3: pg = pg[0] - line = LineString(pg) - return polygonize([line.intersection(line)]) - -class PolygonRoi(ROI): - dtype = 'polygon' - def __init__(self, body=None): - self.body = body if body!=None else [] - self.update = body!=None - self.infoupdate = body!=None - self.box = [1000,1000,-1000,-1000] - - def addpoint(self, p): - self.buf[0].append(p) - - def commit(self, buf, oper): - pgs = [] - if len(buf[0])==0:return False - if buf[0][0]!=buf[0][-1]: - buf[0].append(buf[0][0]) - if len(self.body)==0: - self.body.append(buf) - buf = [[],[]] - self.update, self.infoupdate = True, True - return True - for i in self.body: - pgs.extend(to_segment(i)) - unin = cascaded_union(pgs) - cur = cascaded_union(list(to_segment(buf))) - if oper=='+':rst = unin.union(cur) - if oper=='-':rst = unin.difference(cur) - self.body = parse_mpoly(rst) - self.update, self.infoupdate = True, True - - def snap(self, x, y, lim): - if not self.issimple():return None - cur, minl = None, 1e8 - for i in self.body[0][0]: - d = (i[0]-x)**2+(i[1]-y)**2 - if d < minl:cur,minl = i,d - if minl**0.5>lim:return None - return self.body[0][0], self.body[0][0].index(cur) - - def pick(self, x, y, lim): - rst = self.snap(x, y, lim) - if rst!=None:return rst - - pgs = [] - for i in self.body: - pgs.extend(to_segment(i)) - unin = cascaded_union(pgs) - if unin.contains(Point(x,y)): - return True - return None - - def draged(self, ox, oy, nx, ny, i): - self.update, self.infoupdate = True, True - if i==True: - for pg in self.body: - pg[0] = [(p[0]+(nx-ox), p[1]+(ny-oy)) for p in pg[0]] - for i in range(len(pg[1])): - pg[1][i] = [(p[0]+(nx-ox), p[1]+(ny-oy)) for p in pg[1][i]] - else: - i[0][i[1]] = (nx, ny) - #print 'drag,',i,self.body[0][0][i] - if i[1]==0:i[0][-1] = (nx,ny) - if i[1]==len(i[0])-1: - i[0][0] = (nx, ny) - - def info(self, ips, cur): - if cur==None:return - IPy.set_info('Polygon: %.0f fragments'%len(self.body)) - - def countbox(self): - self.box = [1000,1000,-1000,-1000] - for i in self.body: - for x,y in i[0]: - if xself.box[2]:self.box[2]=x - if yself.box[3]:self.box[3]=y - - def get_box(self): - if self.infoupdate: - self.countbox() - self.infoupdate=False - return self.box - - def issimple(self): - if len(self.body)==1 and len(self.body[0][1])==0: - return True - - def topolygon(self):return self - - ''' - def affine(self, m, o): - plg = PolygonRoi() - plg.body = affine(self.body, m, o) - plg.update = True - plg.infoupdate = True - return plg - ''' - - def draw(self, dc, f): - dc.SetPen(wx.Pen(RoiManager.get_color(), width=RoiManager.get_lw(), style=wx.SOLID)) - for pg in self.body: - dc.DrawLines([f(*i) for i in pg[0]]) - if self.issimple(): - for i in pg[0]: dc.DrawCircle(f(*i),2) - for hole in pg[1]: - dc.DrawLines([f(*i) for i in hole]) - - def sketch(self, img, w=1, color=None): - pen = paint.Paint() - for i in self.body: - xs, ys = [x[0] for x in i[0]], [x[1] for x in i[0]] - pen.draw_path(img, xs, ys, w) - for j in i[1]: - xs, ys = [x[0] for x in j], [x[1] for x in j] - pen.draw_path(img, xs, ys, w, color) - - def fill(self, img, color=None): - pen = paint.Paint() - for i in self.body: - pen.fill_polygon(i[0], img, i[1], color) \ No newline at end of file diff --git a/imagepy/core/roi/rectangleroi.py b/imagepy/core/roi/rectangleroi.py deleted file mode 100644 index 42ca10c9..00000000 --- a/imagepy/core/roi/rectangleroi.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Nov 11 21:29:59 2016 -@author: yxl -""" -import wx -from ..draw import paint -from .polygonroi import PolygonRoi -from .roi import ROI -from ..manager import RoiManager -from imagepy import IPy - -class RectangleRoi(ROI): - dtype = 'rect' - def __init__(self, l=0, t=0, r=0, b=0): - self.body = [] - self.update = False - self.lt, self.tp, self.rt, self.bm = l, t, r, b - self.commit() - - def snap(self, x, y, lim): - if abs(x-self.lt)1): - dc.DrawLines([f(*i) for i in self.body]) - for i in self.body:dc.DrawCircle(f(*i),2) - dc.DrawCircle(f(self.lt, (self.tp+self.bm)/2),2) - dc.DrawCircle(f(self.rt, (self.tp+self.bm)/2),2) - dc.DrawCircle(f((self.lt+self.rt)/2, self.tp),2) - dc.DrawCircle(f((self.lt+self.rt)/2, self.bm),2) - - def sketch(self, img, w=1, color=None): - pen = paint.Paint() - xs, ys = [x[0] for x in self.body], [x[1] for x in self.body] - pen.draw_path(img, xs, ys, w, color) - - def fill(self, img, color=None): - pen = paint.Paint() - for i in self.body: - pen.fill_polygon(self.body, img, [], color) \ No newline at end of file diff --git a/imagepy/core/roi/roi.py b/imagepy/core/roi/roi.py deleted file mode 100644 index bfa03943..00000000 --- a/imagepy/core/roi/roi.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 21 15:05:16 2016 -@author: yxl -""" -from .convert import roi2shape, shape2roi -from shapely.affinity import affine_transform - -class ROI: - def __init__(self):pass - - def buffer(self, r): - return shape2roi(roi2shape(self).buffer(r, 4)) - - def convex(self): - return shape2roi(roi2shape(self).convex_hull) - - def bounds(self): - from .rectangleroi import RectangleRoi - box = roi2shape(self).bounds - return RectangleRoi(*box) - - def clip(self, rect): - return shape2roi(roi2shape(rect).intersection(roi2shape(self))) - - def invert(self, rect): - return shape2roi(roi2shape(rect).difference(roi2shape(self))) - - def union(self, roi): - return shape2roi(roi2shape(roi).union(roi2shape(self))) - - def diff(self, roi): - return shape2roi(roi2shape(self).difference(roi2shape(roi))) - - def affine(self, m, o): - mat = [m[0,0], m[0,1], m[1,0], m[1,1], o[0], o[1]] - return shape2roi(affine_transform(roi2shape(self), mat)) diff --git a/imagepy/core/roi/roiio.py b/imagepy/core/roi/roiio.py deleted file mode 100644 index 0d57d2cb..00000000 --- a/imagepy/core/roi/roiio.py +++ /dev/null @@ -1,28 +0,0 @@ -from .convert import roi2shape, shape2roi -import pickle -from shapely import wkt - -def roi2wkt(roi): return wkt.dumps(roi2shape(roi)) -def wkt2roi(con): return shape2roi(wkt.loads(con)) - -def readroi(path): - f = open(path, 'rb') - roi = pickle.load(f) - f.close() - return roi - -def readwkt(path): - f = open(path) - roi = wkt2roi(f.read()) - f.close() - return roi - -def saveroi(roi, path): - f = open(path, 'wb') - pickle.dump(roi, f) - f.close() - -def savewkt(roi, path): - f = open(path, 'w') - f.write(roi2wkt(roi)) - f.close() \ No newline at end of file diff --git a/imagepy/core/util/__init__.py b/imagepy/core/util/__init__.py deleted file mode 100644 index f3425184..00000000 --- a/imagepy/core/util/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .fileio import * -from .testdata import * \ No newline at end of file diff --git a/imagepy/core/util/fileio.py b/imagepy/core/util/fileio.py deleted file mode 100644 index 1120d265..00000000 --- a/imagepy/core/util/fileio.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -from ..manager import ViewerManager, ConfigManager -from ..manager import ReaderManager, WriterManager -from ... import IPy, root_dir -from ..engine import Free, Simple, Macros -import numpy as np - -def show_img(img, title): - if img.dtype==np.uint8 and img.ndim==3 and img.shape[2]==4: - img = img[:,:,:3].copy() - IPy.show_img([img], title) - -ViewerManager.add('img', show_img) -ViewerManager.add('imgs', IPy.show_img) -recent = ConfigManager.get('recent') -if recent==None : recent = [] - -def f(path): - return Macros(path, ["Open>{'path':%s}"%repr(path)]) - -rlist = [f(i) for i in recent] - -def add_recent(path): - global recent, rlist - if path in recent: - idx = recent.index(path) - recent.insert(0, recent.pop(idx)) - rlist.insert(0, rlist.pop(idx)) - else: - recent.insert(0, path) - rlist.insert(0, f(path)) - if len(recent)>=5: - recent.pop(-1) - rlist.pop(-1) - - ConfigManager.set('recent', recent) - IPy.curapp.reload_plugins() - -class Reader(Free): - para = {'path':''} - - def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) - return IPy.getpath('Open..', filt, 'open', self.para) - - #process - def run(self, para = None): - add_recent(para['path']) - - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - read = ReaderManager.get(fe[1:], None) - view = ViewerManager.get(fe[1:]) - - #group, read = (True, read[0]) if isinstance(read, tuple) else (False, read) - obj = read(para['path']) - # if not group: img = [img] - view(obj, fn) - -class Writer(Simple): - note = ['all'] - para={'path':root_dir} - - def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) - return IPy.getpath('Save..', filt, 'save', self.para) - - #process - def run(self, ips, imgs, para = None): - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - write = WriterManager.get(fe[1:]) - group, write = (True, write[0]) if isinstance(write, tuple) else (False, write) - write(para['path'], imgs if group else ips.img) \ No newline at end of file diff --git a/imagepy/core/util/tableio.py b/imagepy/core/util/tableio.py deleted file mode 100644 index 3f35ced2..00000000 --- a/imagepy/core/util/tableio.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -from ..manager import ViewerManager, ReaderManager, WriterManager -from ... import IPy, root_dir -from ..engine import Free, Table, Macros -import numpy as np - -class Reader(Free): - para = {'path':''} - - def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) - return IPy.getpath('Open..', filt, 'open', self.para) - - #process - def run(self, para = None): - - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - read = ReaderManager.get(fe[1:]) - - table = read(para['path']) - ViewerManager.get(fe[1:])(table, fn) - -class Writer(Table): - note = ['all'] - para={'path':root_dir} - - def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) - return IPy.getpath('Save..', filt, 'save', self.para) - - #process - def run(self, tps, data, snap, para = None): - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - write = WriterManager.get(fe[1:], 'tab') - write(para['path'], data) \ No newline at end of file diff --git a/imagepy/core/util/testdata.py b/imagepy/core/util/testdata.py deleted file mode 100644 index c0150756..00000000 --- a/imagepy/core/util/testdata.py +++ /dev/null @@ -1,41 +0,0 @@ -from imagepy import IPy -from imagepy.core.engine import Free -from imagepy.core.manager import ReaderManager, WriterManager, ViewerManager -from scipy.misc import imread -import os.path as osp, os -import numpy as np - -class TestData(Free): - def __init__(self, path, name): - self.name = name - self.path = path - self.title = name.split('.')[0].replace('\\', '/').split('/')[-1] - - def run(self, para = None): - root_dir = osp.abspath(osp.dirname(self.path)) - print(root_dir) - path = osp.join(root_dir, self.name) - if osp.isfile(path): - fp, fn = osp.split(path) - fn, fe = osp.splitext(fn) - read = ReaderManager.get(fe[1:]) - view = ViewerManager.get(fe[1:]) - - group, read = (True, read[0]) if isinstance(read, tuple) else (False, read) - img = read(path) - if img.dtype==np.uint8 and img.ndim==3 and img.shape[2]==4: - img = img[:,:,:3].copy() - if not group: img = [img] - else: - names = [i for i in os.listdir(path) if '.' in i] - read = ReaderManager.get(names[0].split('.')[1]) - view = ViewerManager.get(names[0].split('.')[1]) - imgs = [] - for i in range(len(names)): - self.progress(i, len(names)) - imgs.append(read(osp.join(path,names[i]))) - img = imgs - view(img, self.title) - - def __call__(self): - return self \ No newline at end of file diff --git a/imagepy/core/wraper/imageplus.py b/imagepy/core/wraper/imageplus.py deleted file mode 100644 index 563282d2..00000000 --- a/imagepy/core/wraper/imageplus.py +++ /dev/null @@ -1,153 +0,0 @@ -import numpy as np -from ..manager import ImageManager, ColorManager - -def get_img_type(imgs): - if imgs[0].ndim==3 and imgs[0].dtype==np.uint8:return 'rgb' - if imgs[0].dtype == np.uint8:return '8-bit' - if imgs[0].dtype == np.uint16:return '16-bit' - if imgs[0].dtype == np.int32:return '32-int' - if imgs[0].dtype == np.float32:return '32-float' - if imgs[0].dtype == np.float64:return '64-float' - -class ImagePlus: - """ImagePlus: a class to make operation more flexible """ - def __init__(self, imgs, title=None, is3d=False): - self.set_title(title) - self.snap = None - self.cur = 0 - self.update = False - self.scrchanged = False - self.roi = None - self.mark = None - self.msk = None - self.mskmode = None - self.lut = ColorManager.get_lut('grays') - self.backimg = None - self.backmode = (0.5, 'Mean') - self.tool = None - self.data = None - self.info = {} - self.unit = (1, 'pix') - self.range = (0, 255) - self.set_imgs(imgs) - - def set_title(self, title): - self.title = ImageManager.name(title) - - def set_imgs(self, imgs): - self.is3d = not isinstance(imgs, list) - self.scrchanged = True - self.snap = None - self.imgs = imgs - - self.height, self.width = self.size = self.imgs[0].shape[:2] - print(self.height, self.width) - self.imgtype = get_img_type(self.imgs) - self.channels = 1 if self.imgs[0].ndim==2 else self.imgs[0].shape[2] - self.dtype = self.imgs[0].dtype - - if self.dtype == np.uint8: - self.range = (0, 255) - else: self.range = self.get_updown() - - def get_updown(self): - arr = np.array(([(i.min(),i.max()) for i in self.imgs])) - print('range', arr[:,0].min(), arr[:,1].max()) - return arr[:,0].min(), arr[:,1].max() - - def get_imgtype(self):return self.imgtype - - def get_nslices(self):return len(self.imgs) - - def get_nchannels(self):return self.channels - - def set_cur(self, n): - if n>=0 and n0 else self.data.index - if mskr==True:rmsk = rowmsk - if mskr==False:rmsk = self.data.index.difference(rowmsk) - - if mskc==None:cmsk = slice(None) - colmsk = self.colmsk if len(self.colmsk)>0 else self.data.columns - if mskc==True:cmsk = colmsk - if mskc==False:cmsk = self.data.columns.difference(colmsk) - - self.snap = self.data.loc[rmsk, cmsk].copy() - if num: self.snap = self.snap.select_dtypes( - include=[np.number]) - - def select(self, rs=[], cs=[], byidx=False): - if byidx: rs, cs = self.data.index[rs], self.data.columns[cs] - self.rowmsk = pd.Index(rs).astype(self.data.index.dtype) - self.colmsk = pd.Index(cs).astype(self.data.columns.dtype) - print('tps select', rs, cs, self.rowmsk, self.colmsk) - - def get_titles(self):return self.data.columns - def get_index(self):return self.data.index - - def get_props(self): - props, data = self.props, self.data - if self.props is None or len(props.columns)!=len(data.columns)\ - or (props.columns != data.columns).sum()>0: - ndata = [[i]*data.shape[1] for i in self.default] - newprop = pd.DataFrame(ndata, ['round', 'tc', 'lc', 'ln'], data.columns) - if not self.props is None: - inter = self.props.columns.intersection(newprop.columns) - newprop[inter] = self.props[inter] - self.props = newprop - return self.props - - def count_range(self): - self.range = pd.DataFrame([self.data.min(), self.data.max()]) \ No newline at end of file diff --git a/imagepy/data/config.json b/imagepy/data/config.json new file mode 100644 index 00000000..2daab69b --- /dev/null +++ b/imagepy/data/config.json @@ -0,0 +1 @@ +[["language", "English", null], ["uistyle", "imagepy", null], ["mea_style", {"color": [0, 0, 255], "fcolor": [255, 255, 255], "fill": false, "lw": 2, "tcolor": [0, 255, 0], "size": 12}, null], ["mark_style", {"color": [0, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 0, 0], "size": 8}, null], ["recent", ["C:/Users/54631/Desktop/\u6d77\u51b0\u62a5\u4ef7/testmacros.mc", "C:\\Users\\Administrator\\Downloads\\\u7d20\u6750\u2014\u8f66\u5934\\\u6d4b\u8bd5-JK1-\u52ff\u5220\\20170201\\In-20170201151126911-\u6842J73220-\u9ec4-qj-1.jpg", "C:/Users/Administrator/Desktop/imagepy/imagepy/plugins/demoplugin/menus/Demos/Macros Demo/Macros Gaussian Invert.mc", "DEM.mc"], null], ["roi_style", {"color": [255, 255, 0], "fcolor": [255, 255, 255], "fill": false, "lw": 1, "tcolor": [255, 255, 0], "size": 8}, null]] \ No newline at end of file diff --git a/imagepy/data/logolong.png b/imagepy/data/logolong.png new file mode 100644 index 00000000..72cadb49 Binary files /dev/null and b/imagepy/data/logolong.png differ diff --git a/imagepy/data/luts/Blue.lut b/imagepy/data/luts/Blue.lut new file mode 100644 index 00000000..7516bd76 Binary files /dev/null and b/imagepy/data/luts/Blue.lut differ diff --git a/imagepy/data/luts/Cyan.lut b/imagepy/data/luts/Cyan.lut new file mode 100644 index 00000000..dc7c1f69 Binary files /dev/null and b/imagepy/data/luts/Cyan.lut differ diff --git a/imagepy/data/luts/Green.lut b/imagepy/data/luts/Green.lut new file mode 100644 index 00000000..0dcc2e05 Binary files /dev/null and b/imagepy/data/luts/Green.lut differ diff --git a/imagepy/data/luts/Jet.lut b/imagepy/data/luts/Jet.lut new file mode 100644 index 00000000..9afc9e01 Binary files /dev/null and b/imagepy/data/luts/Jet.lut differ diff --git a/imagepy/data/luts/Magenta.lut b/imagepy/data/luts/Magenta.lut new file mode 100644 index 00000000..ee164fed Binary files /dev/null and b/imagepy/data/luts/Magenta.lut differ diff --git a/imagepy/data/luts/Others/5_ramps.lut b/imagepy/data/luts/Others/5_ramps.lut new file mode 100644 index 00000000..4ab5bd8e Binary files /dev/null and b/imagepy/data/luts/Others/5_ramps.lut differ diff --git a/imagepy/data/luts/Others/Errors.lut b/imagepy/data/luts/Others/Errors.lut new file mode 100644 index 00000000..b4b826a2 Binary files /dev/null and b/imagepy/data/luts/Others/Errors.lut differ diff --git a/imagepy/data/luts/Others/FRC.lut b/imagepy/data/luts/Others/FRC.lut new file mode 100644 index 00000000..31356c73 --- /dev/null +++ b/imagepy/data/luts/Others/FRC.lut @@ -0,0 +1,2 @@ +:;<=?@ABDEFGIJKMNOPRSTUWXY[\]^`abcefgijklnopqstuvxyz|}~LMNOPQRSUVWXYZ[\^_`abcdeghijklmopqrstuvxyz{|}~}|zxwusrpnmkigfdba_]\ZXWUSQPNLKIGFDB@?=;:865310.,*)'%$"  + ~}{zxwutsqpnmkjigfdca`_]\ZYWVUSRPOMLKIHFECBA?><;9875421/.-+*('& \ No newline at end of file diff --git a/imagepy/data/luts/Others/Glow.lut b/imagepy/data/luts/Others/Glow.lut new file mode 100644 index 00000000..3c8fc06d Binary files /dev/null and b/imagepy/data/luts/Others/Glow.lut differ diff --git a/imagepy/data/luts/Others/Rainbow_RGB.lut b/imagepy/data/luts/Others/Rainbow_RGB.lut new file mode 100644 index 00000000..72777e63 Binary files /dev/null and b/imagepy/data/luts/Others/Rainbow_RGB.lut differ diff --git a/imagepy/data/luts/Others/Thermal.lut b/imagepy/data/luts/Others/Thermal.lut new file mode 100644 index 00000000..5d8cd5a8 Binary files /dev/null and b/imagepy/data/luts/Others/Thermal.lut differ diff --git a/imagepy/data/luts/Others/Viridis.lut b/imagepy/data/luts/Others/Viridis.lut new file mode 100644 index 00000000..eb4a3e6c --- /dev/null +++ b/imagepy/data/luts/Others/Viridis.lut @@ -0,0 +1 @@ +DDDEEEFFFFGGGGGGGHHHHHHHHHGGGGGGGFFFFEEEEDDCCCBBBAA@@??>>===<<;;::998877665544332211100//...--,,,++***))((('''&&&%%$$$###"""!!!  !!"##$%&'()*+,./0235689;=>@BDEGIKMOQSUWY[^`bdgikmprtwy|~  !"#%&'(*+,-/01245679:;<=>@ABCDEGHIJKLMNPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyzz{|}~TUWXZ[\^_abcefgijklnopqrstuvwxyz{||}~~}}|{zzyxwvvutsrqponmlkihgfedba`_]\[YXVUTRQONLKIGFDCA?><:875320.,+)'&$"!!"$ \ No newline at end of file diff --git a/imagepy/data/luts/Others/ametrine.lut b/imagepy/data/luts/Others/ametrine.lut new file mode 100644 index 00000000..598d81df Binary files /dev/null and b/imagepy/data/luts/Others/ametrine.lut differ diff --git a/imagepy/data/luts/Others/blue_orange_icb.lut b/imagepy/data/luts/Others/blue_orange_icb.lut new file mode 100644 index 00000000..d1fe182d --- /dev/null +++ b/imagepy/data/luts/Others/blue_orange_icb.lut @@ -0,0 +1,6 @@ + +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{} +  !"#$%&'()*+,-./0123456789:;<=>?@@?>=<;:9876543210/.-,+*)('&%$#"!  +  !#%')+-/13579;=?@BDFHJLNPRTVXZ\^`bdfhjlnprtvxz|~ +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{}}{ywusqomkigeca_][YWUSQOMKIGECA@><:86420.,*(&$"  +  $(,048<@CGKOSW[_cgkosw{ \ No newline at end of file diff --git a/imagepy/data/luts/Others/cool.lut b/imagepy/data/luts/Others/cool.lut new file mode 100644 index 00000000..6099489a --- /dev/null +++ b/imagepy/data/luts/Others/cool.lut @@ -0,0 +1,4 @@ + +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{} !#%')+-/13579;=?@BDFHJLNPRTVXZ\^`bdfhjlnprtvxz|~~|zxvtrpnljhfdb`^\ZXVTRPNLJHFDB@?=;97531/-+)'%#!  +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{} +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{}¿{wsokgc_[WSOKGC@<840,($    $(,048<@CGKOSW[_cgkosw{ \ No newline at end of file diff --git a/imagepy/data/luts/Others/edges.lut b/imagepy/data/luts/Others/edges.lut new file mode 100644 index 00000000..ead70ca2 --- /dev/null +++ b/imagepy/data/luts/Others/edges.lut @@ -0,0 +1 @@ + $).3=Zix´xiPF=3.)$  $).3=Zix´xiPF=3.)$  $).3=Zx´xiPF=3.)$  \ No newline at end of file diff --git a/imagepy/data/luts/Others/glasbey.lut b/imagepy/data/luts/Others/glasbey.lut new file mode 100644 index 00000000..04bef3a8 Binary files /dev/null and b/imagepy/data/luts/Others/glasbey.lut differ diff --git a/imagepy/data/luts/Others/glasbey_inverted.lut b/imagepy/data/luts/Others/glasbey_inverted.lut new file mode 100644 index 00000000..02766a60 Binary files /dev/null and b/imagepy/data/luts/Others/glasbey_inverted.lut differ diff --git a/imagepy/data/luts/Others/glasbey_on_dark.lut b/imagepy/data/luts/Others/glasbey_on_dark.lut new file mode 100644 index 00000000..eacfcfef Binary files /dev/null and b/imagepy/data/luts/Others/glasbey_on_dark.lut differ diff --git a/imagepy/data/luts/Others/isolum.lut b/imagepy/data/luts/Others/isolum.lut new file mode 100644 index 00000000..dd6711a6 Binary files /dev/null and b/imagepy/data/luts/Others/isolum.lut differ diff --git a/imagepy/data/luts/Others/morgenstemning.lut b/imagepy/data/luts/Others/morgenstemning.lut new file mode 100644 index 00000000..a9af2d5d Binary files /dev/null and b/imagepy/data/luts/Others/morgenstemning.lut differ diff --git a/imagepy/data/luts/Others/mpl-inferno.lut b/imagepy/data/luts/Others/mpl-inferno.lut new file mode 100644 index 00000000..d372179f Binary files /dev/null and b/imagepy/data/luts/Others/mpl-inferno.lut differ diff --git a/imagepy/data/luts/Others/mpl-magma.lut b/imagepy/data/luts/Others/mpl-magma.lut new file mode 100644 index 00000000..6b31ee40 Binary files /dev/null and b/imagepy/data/luts/Others/mpl-magma.lut differ diff --git a/imagepy/data/luts/Others/mpl-plasma.lut b/imagepy/data/luts/Others/mpl-plasma.lut new file mode 100644 index 00000000..a2fd009c Binary files /dev/null and b/imagepy/data/luts/Others/mpl-plasma.lut differ diff --git a/imagepy/data/luts/Others/mpl-viridis.lut b/imagepy/data/luts/Others/mpl-viridis.lut new file mode 100644 index 00000000..eb4a3e6c --- /dev/null +++ b/imagepy/data/luts/Others/mpl-viridis.lut @@ -0,0 +1 @@ +DDDEEEFFFFGGGGGGGHHHHHHHHHGGGGGGGFFFFEEEEDDCCCBBBAA@@??>>===<<;;::998877665544332211100//...--,,,++***))((('''&&&%%$$$###"""!!!  !!"##$%&'()*+,./0235689;=>@BDEGIKMOQSUWY[^`bdgikmprtwy|~  !"#%&'(*+,-/01245679:;<=>@ABCDEGHIJKLMNPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyzz{|}~TUWXZ[\^_abcefgijklnopqrstuvwxyz{||}~~}}|{zzyxwvvutsrqponmlkihgfedba`_]\[YXVUTRQONLKIGFDCA?><:875320.,+)'&$"!!"$ \ No newline at end of file diff --git a/imagepy/data/luts/Others/phase.lut b/imagepy/data/luts/Others/phase.lut new file mode 100644 index 00000000..e37dd360 Binary files /dev/null and b/imagepy/data/luts/Others/phase.lut differ diff --git a/imagepy/data/luts/Others/physics.lut b/imagepy/data/luts/Others/physics.lut new file mode 100644 index 00000000..033c07f9 Binary files /dev/null and b/imagepy/data/luts/Others/physics.lut differ diff --git a/imagepy/data/luts/Others/royal.lut b/imagepy/data/luts/Others/royal.lut new file mode 100644 index 00000000..a2ba33e7 --- /dev/null +++ b/imagepy/data/luts/Others/royal.lut @@ -0,0 +1,3 @@ + +$-5>FOW`irz $*/5;@FKQUY]bhmsy~zvrniea]XTPLGBD@;72.)%  +'4@LYfs $*/5;@FKQUY]bhmsy~οo_O@0 '4@LYfs \ No newline at end of file diff --git a/imagepy/data/luts/Others/sepia.lut b/imagepy/data/luts/Others/sepia.lut new file mode 100644 index 00000000..60289be7 Binary files /dev/null and b/imagepy/data/luts/Others/sepia.lut differ diff --git a/imagepy/data/luts/Others/smart.lut b/imagepy/data/luts/Others/smart.lut new file mode 100644 index 00000000..06f2e37b --- /dev/null +++ b/imagepy/data/luts/Others/smart.lut @@ -0,0 +1,7 @@ + +  !!"#$$%&''())*+,,-.//0122344567789::;<==>?@@@AABCDDEFGGHIJJKLMMNOPPQRSSTUVVWXYYZ[\\]^__`abbcdeefgfijkmopqrtuwz} +  !!"#$$%&''())*+,,-.//0122344567789::;<==>?@@@AABCDDEFGGHIJJKGD@>:73/-+*(&$"  !"$%')+/2579;>@CGJNSX\adgknqy +  !!"#$$%&''())*+,,-.//0122344567789::;<==>?@@@AABCDDEFGGHIJJKGB>:50+(&$"  +  + +  $(+/258;BJRYeq} \ No newline at end of file diff --git a/imagepy/data/luts/Others/thal.lut b/imagepy/data/luts/Others/thal.lut new file mode 100644 index 00000000..2a71f22d --- /dev/null +++ b/imagepy/data/luts/Others/thal.lut @@ -0,0 +1 @@ + !%)-159=@DHLPTX\`dhlptx| !%)-159=@DHLPTX\`dhlptx|Ͽp`P@1! !%)-159=@DHLPTX\`dhlptx| !)19@HPX`hpxϿp`P@1!!1@P`p \ No newline at end of file diff --git a/imagepy/data/luts/Others/thallium.lut b/imagepy/data/luts/Others/thallium.lut new file mode 100644 index 00000000..c90b5424 --- /dev/null +++ b/imagepy/data/luts/Others/thallium.lut @@ -0,0 +1,6 @@ + + + !"#$&'()+,-.0125679:;<>?@ACFHKMPRUW\_adfiknpsuxz} +  !!""##$%%&&'(()**+,--./0013345667889:;;<==>?@@BCDEFFGHIJKLMNOPQQRSVWXZ[]^`acdfgijlmopsuvwyz|} + + !"#$&'()+,-.0125679:;<>?@ACFHKMPRUW\_adfiknpsuxz} \ No newline at end of file diff --git a/imagepy/data/luts/Others/unionjack.lut b/imagepy/data/luts/Others/unionjack.lut new file mode 100644 index 00000000..99ba803f Binary files /dev/null and b/imagepy/data/luts/Others/unionjack.lut differ diff --git a/imagepy/data/luts/Pink.lut b/imagepy/data/luts/Pink.lut new file mode 100644 index 00000000..f30fd896 Binary files /dev/null and b/imagepy/data/luts/Pink.lut differ diff --git a/imagepy/data/luts/Red.lut b/imagepy/data/luts/Red.lut new file mode 100644 index 00000000..881982ec Binary files /dev/null and b/imagepy/data/luts/Red.lut differ diff --git a/imagepy/data/luts/Yellow.lut b/imagepy/data/luts/Yellow.lut new file mode 100644 index 00000000..6b3e9ea3 Binary files /dev/null and b/imagepy/data/luts/Yellow.lut differ diff --git a/imagepy/data/luts/Yellow_Hot.lut b/imagepy/data/luts/Yellow_Hot.lut new file mode 100644 index 00000000..5ccebb7a Binary files /dev/null and b/imagepy/data/luts/Yellow_Hot.lut differ diff --git a/imagepy/data/luts/gem.lut b/imagepy/data/luts/gem.lut new file mode 100644 index 00000000..8ea4af6a --- /dev/null +++ b/imagepy/data/luts/gem.lut @@ -0,0 +1,9 @@ + +  "$&(*,.02468:<>@ACEGIKMOQSUWY[]_acegikmoqsuwy{} + +  !!  + +  + !"$%'(*+-.0134679:<=?@ABDEGHJKMNPQSTVWYZ\]__bdgilnqsvx{} +  !"#$%&'()*+,-./0123456789:;<=>?@@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~}{ywusqomkigeca_][YWUSQOMKIGECA@><:86420.,*(&$"  +  $(,048<@CGKOSW[_cgkosw{ \ No newline at end of file diff --git a/imagepy/data/shortcut.json b/imagepy/data/shortcut.json new file mode 100644 index 00000000..0ba8a061 --- /dev/null +++ b/imagepy/data/shortcut.json @@ -0,0 +1 @@ +[["Invert", "Ctrl-I", null], ["Open", "Ctrl-O", null], ["New", "Ctrl-N", null], ["8-bit", "Ctrl-8", null], ["Undo", "Ctrl-Z", null], ["Save", "Ctrl-S", null]] \ No newline at end of file diff --git a/imagepy/data/shotcut.cfg b/imagepy/data/shotcut.cfg deleted file mode 100644 index 45404d18..00000000 --- a/imagepy/data/shotcut.cfg +++ /dev/null @@ -1 +0,0 @@ -{'Topic': 'F1', 'Open': 'Ctrl-O', 'Select All': 'Ctrl-A', 'Save': 'Ctrl-S', 'New': 'Ctrl-N', 'Copy': 'Ctrl-C', 'Paste': 'Ctrl-V', 'Undo': 'Ctrl-Z', '6_Shades': '', '16_Colors': '', 'Show ConvexHull': '', 'Exit': 'Alt-X', '2DSurface Demo': ''} \ No newline at end of file diff --git a/imagepy/data/watermark.png b/imagepy/data/watermark.png new file mode 100644 index 00000000..ad8fb25c Binary files /dev/null and b/imagepy/data/watermark.png differ diff --git a/imagepy/doc/Chinese/plugins/File/New Image.md b/imagepy/doc/Chinese/plugins/File/New Image.md new file mode 100644 index 00000000..41bf6985 --- /dev/null +++ b/imagepy/doc/Chinese/plugins/File/New Image.md @@ -0,0 +1,19 @@ +# 新建图像 + +**描述:** 创建一个空白图像 + + +## 参数 + +**名称:** 新图像的标题 + +**宽度:** 图像宽度 + +**高度:** 图像高度 + +**类型:** 图像类型 + +1. 8-bit: 创建8位灰度图像 +2. rgb: 创建24位RGB图像 + +**slice:** 序列层数 \ No newline at end of file diff --git a/imagepy/doc/English/plugins/File/New Image.md b/imagepy/doc/English/plugins/File/New Image.md new file mode 100644 index 00000000..2464a4a2 --- /dev/null +++ b/imagepy/doc/English/plugins/File/New Image.md @@ -0,0 +1,19 @@ +# New + +**Description:** create a new Image + + +## Parameter + +**name:** the new image's title + +**width:** image's width + +**height:** image's height + +**type:** image's type + +1. 8-bit: create a 8-bit one channel image +2. rgb: create a 24-bit three channel image + +**slice:** the z thickness \ No newline at end of file diff --git a/imagepy/doc/English/tools/standard/Rectangle ROI.md b/imagepy/doc/English/tools/standard/Rectangle ROI.md new file mode 100644 index 00000000..9499f508 --- /dev/null +++ b/imagepy/doc/English/tools/standard/Rectangle ROI.md @@ -0,0 +1 @@ +# Rectangel ROI Tool \ No newline at end of file diff --git a/imagepy/ipyalg/__init__.py b/imagepy/ipyalg/__init__.py index 8c8b3b6c..c6b8367e 100644 --- a/imagepy/ipyalg/__init__.py +++ b/imagepy/ipyalg/__init__.py @@ -1,4 +1,7 @@ from .hydrology.findmax import find_maximum from .hydrology.ridge import ridge from .hydrology.isoline import stair, isoline -from .hydrology.watershed import watershed \ No newline at end of file +from .hydrology.watershed import watershed +from .hydrology.edt import distance_transform_edt +from .classify import feature +from .transform.transform import linear_polar, polar_linear \ No newline at end of file diff --git a/imagepy/core/myvi/imgs/__init__.py b/imagepy/ipyalg/classify/__init__.py similarity index 100% rename from imagepy/core/myvi/imgs/__init__.py rename to imagepy/ipyalg/classify/__init__.py diff --git a/imagepy/ipyalg/classify/feature.py b/imagepy/ipyalg/classify/feature.py new file mode 100644 index 00000000..c9987956 --- /dev/null +++ b/imagepy/ipyalg/classify/feature.py @@ -0,0 +1,138 @@ +import numpy as np +import scipy.ndimage as ndimg +from skimage.filters import sobel +from skimage.feature import structure_tensor, structure_tensor_eigenvalues + +# chans 是通道,可以是int,list,默认None,代表所有通道 +para = {'chans':None, 'grade':2, 'w':1, 'items':['ori', 'blr', 'sob', 'eig']} + +def get_feature_one(img, msk=None, para=para): + chans, grade, w, items = para['chans'], para['grade'], para['w'], para['items'] + feats, titles = [], [] + img = img.reshape(img.shape[:2]+(-1,)) + if msk is None: msk = np.ones(img.shape[:2], dtype='bool') + if chans is None: chans = range(img.shape[2]) + for c in [chans] if isinstance(chans, int) else chans: + if 'ori' in items: + feats.append(img[:,:,c][msk].astype(np.float32)) + titles.append('c%d_ori'%c) + for o in range(grade): + blurimg = ndimg.gaussian_filter(img[:,:,c], 2**o, output=np.float32) + feat_sobel = sobel(blurimg)[msk] if 'sob' in items else None + if 'eig' in items: + Axx, Axy, Ayy = structure_tensor(blurimg, w) + l1, l2 = structure_tensor_eigvals(Axx, Axy, Ayy) + feat_l1, feat_l2 = l1[msk], l2[msk] + else: feat_l1 = feat_l2 = None + feat_gauss = blurimg[msk] if 'blr' in items else None + featcr = [feat_gauss, feat_sobel, feat_l1, feat_l2] + title = ['c%d_s%d_%s'%(c,o,i) for i in ['gauss', 'sobel', 'l1', 'l2']] + titles.extend([title[i] for i in range(4) if not featcr[i] is None]) + feats.extend([featcr[i] for i in range(4) if not featcr[i] is None]) + return np.array(feats).T, titles + +def make_slice(l, size, mar): + xs = list(range(0, l, size))+[l] + ins = np.array((xs[:-1], xs[1:])).T + outs = np.clip(ins + [-mar, mar], 0, l) + return outs, ins + +def grid_slice(H, W, size, mar): + h_out, h_in = make_slice(H, size, mar) + w_out, w_in = make_slice(W, size, mar) + out_slice, in_slice = [], [] + for rs, re in h_out: + for cs, ce in w_out: + out_slice.append((slice(rs, re), slice(cs, ce))) + for rs, re in h_in: + for cs, ce in w_in: + in_slice.append((slice(rs, re), slice(cs, ce))) + return out_slice, in_slice + +def get_feature(imgs, labs, key=para, size=1024, callback=print): + if not isinstance(labs, list) and labs.ndim==2: + imgs, labs = [imgs], [labs] + out_slice, in_slice = grid_slice(*imgs[0].shape[:2], size, 2**(key['grade']-1)*3) + m, n = len(out_slice), len(labs) + feats, vs = [], [] + msk = np.zeros(imgs[0].shape[:2], dtype='bool') + for i, img, lab in zip(range(n), imgs, labs): + for j, outs, ins in zip(range(m), out_slice, in_slice): + callback(i*m+j, m*n) + msk[outs] = 0; msk[ins] = 1; + msk[outs][lab[outs]==0] = 0 + if msk[outs].sum()==0: continue + feat, title = get_feature_one(img[outs], msk[outs], key) + feats.append(feat) + vs.append(lab[outs][msk[outs]]) + feats = np.vstack(feats) + vs = np.hstack(vs).reshape((-1,1)) + mins, ptps = feat.min(axis=0), feat.ptp(axis=0) + feats -= mins + feats /= ptps + para = key.copy() + para['min'] = mins.tolist() + para['ptp'] = ptps.tolist() + para['titles'] = title + return feats, vs, para + +def get_predict(imgs, model, key=para, out=None, size=1024, callback=print): + lut = {'ori':1, 'blr':key['grade'], 'sob':key['grade'], 'eig':key['grade']*2} + chans = len(key['titles'])/sum([lut[i] for i in key['items']]) + print(chans) + islist = isinstance(imgs, list) + if islist and imgs[0].ndim == 2 and chans == 1: pass + elif islist and imgs[0].shape[2] == chans: pass + elif not islist and imgs.shape[2] == chans and imgs.ndim == 3: imgs = [imgs] + elif not islist and imgs.ndim == 4 and imgs.shape[3] == chans: pass + else: return None + out_slice, in_slice = grid_slice(*imgs[0].shape[:2], size, 2**(key['grade']-1)*3) + m, n = len(out_slice), len(imgs) + if out is None: + temp = imgs[0] if imgs[0].ndim==2 else imgs[0][:,:,0] + out = [temp.astype(np.uint8)]; out[0] *= 0; + for i in range(1, len(imgs)): out.append(out[0].copy()) + msk = np.zeros(imgs[0].shape[:2], dtype='bool') + for i, img, ot in zip(range(len(imgs)), imgs, out): + for j, outs, ins in zip(range(len(out_slice)), out_slice, in_slice): + callback(i*m+j, m*n) + msk[outs] = 0; msk[ins] = 1; + feat, title = get_feature_one(img[outs], msk[outs], key) + feat -= key['min'] + feat /= key['ptp'] + labs = model.predict(feat) + ot[ins] = labs.reshape(ot[ins].shape) + return out + +def dump_model(model, para, path): + joblib.dump(path, (model, para)) + +def load_model(model, para, path): + return joblib.load(path) + +if __name__ == '__main__': + from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier + #a, b = grid_slice(100, 80, 50, 5) + from skimage.data import camera + from skimage.io import imread + import matplotlib.pyplot as plt + import joblib + img = imread('img.png') + lab = imread('lab.png') + + #a, b = grid_slice(*img.shape, 500, 5) + #feats = get_feature(img, lab>0) + size = 512 + feat, lab, key = get_feature(img, lab, para, size=size) + + model = RandomForestClassifier(n_estimators=100, random_state=42, + max_features = 'sqrt', n_jobs=-1, verbose = 1) + # model = AdaBoostClassifier() + model.fit(feat, lab) + rst = get_predict(img, model, key, size=size) + ''' + para, model = joblib.load('a.clsf') + rst = predict(img, model, para) + ''' + plt.imshow(rst) + plt.show() \ No newline at end of file diff --git a/imagepy/ipyalg/graph/connect.py b/imagepy/ipyalg/graph/connect.py new file mode 100644 index 00000000..e025e0ac --- /dev/null +++ b/imagepy/ipyalg/graph/connect.py @@ -0,0 +1,71 @@ +import numpy as np +from numba import jit +import random +from scipy.ndimage import generate_binary_structure + +def neighbors(shape, conn=1): + dim = len(shape) + block = generate_binary_structure(dim, conn) + block[tuple([1]*dim)] = 0 + idx = np.where(block>0) + idx = np.array(idx, dtype=np.uint8).T + idx = np.array(idx-[1]*dim) + acc = np.cumprod((1,)+shape[::-1][:-1]) + return np.dot(idx, acc[::-1]) + +@jit(nopython=True) +def unique(idx): + msk = idx[:,0]0: + k = nodes.pop(0) + counter[k] += 1 + hist = [1e4] + [0] * n + for p in conmap[k]: + hist[colors[p]] += 1 + if min(hist)==0: + colors[k] = hist.index(min(hist)) + counter[k] = 0 + continue + hist[colors[k]] = 1e4 + minc = hist.index(min(hist)) + if counter[k]==rand: + counter[k] = 0 + minc = random.randint(1,4) + colors[k] = minc + for p in conmap[k]: + if colors[p] == minc: + nodes.append(p) + return colors \ No newline at end of file diff --git a/imagepy/ipyalg/graph/skel2d.py b/imagepy/ipyalg/graph/skel2d.py index 644c32cd..2ea980a0 100644 --- a/imagepy/ipyalg/graph/skel2d.py +++ b/imagepy/ipyalg/graph/skel2d.py @@ -3,7 +3,8 @@ from numba import jit from scipy.ndimage import label, generate_binary_structure -strc = np.ones((3,3), dtype=np.bool) +from scipy.ndimage import distance_transform_edt +strc = np.ones((3,3), dtype='bool') # check whether this pixcel can be removed def check(n): @@ -32,12 +33,11 @@ def check(n): fac = np.array([1,2,4,8,16,32,64,128]) -@jit +@jit(nopython=True) def medial_axis(data, idx, branch = True): h, w = data.shape data = data.ravel() for id in idx: - if data[id]==0:continue i2=id-w;i8=id+w;i1=i2-1;i3=i2+1; i4=id-1;i6=id+1;i7=i8-1;i9=i8+1; @@ -50,7 +50,8 @@ def medial_axis(data, idx, branch = True): return 0; def mid_axis(img): - dis = ndimg.distance_transform_edt(img) + dis = distance_transform_edt(img) + dis[[0,-1],:] = 0; dis[:,[0,-1]] = 0 idx = np.argsort(dis.flat).astype(np.int32) medial_axis(dis, idx, lut) return dis @@ -58,7 +59,7 @@ def mid_axis(img): if __name__ == '__main__': from time import time from skimage.data import horse - from skimage.morphology import medial_axis + #from skimage.morphology import medial_axis import matplotlib.pyplot as plt img = ~horse()*255 diff --git a/imagepy/ipyalg/graph/sknw.py b/imagepy/ipyalg/graph/sknw.py index 090bbfb3..a03289ab 100644 --- a/imagepy/ipyalg/graph/sknw.py +++ b/imagepy/ipyalg/graph/sknw.py @@ -2,7 +2,6 @@ from numba import jit import networkx as nx -# get neighbors d index def neighbors(shape): dim = len(shape) block = np.ones([3]*dim) @@ -13,9 +12,8 @@ def neighbors(shape): acc = np.cumprod((1,)+shape[::-1][:-1]) return np.dot(idx, acc[::-1]) -@jit # my mark -def mark(img): # mark the array use (0, 1, 2) - nbs = neighbors(img.shape) +@jit(nopython=True) # my mark +def mark(img, nbs): # mark the array use (0, 1, 2) img = img.ravel() for p in range(len(img)): if img[p]==0:continue @@ -25,7 +23,7 @@ def mark(img): # mark the array use (0, 1, 2) if s==2:img[p]=1 else:img[p]=2 -@jit # trans index to r, c... +@jit(nopython=True) # trans index to r, c... def idx2rc(idx, acc): rst = np.zeros((len(idx), len(acc)), dtype=np.int16) for i in range(len(idx)): @@ -35,31 +33,30 @@ def idx2rc(idx, acc): rst -= 1 return rst -@jit # fill a node (may be two or more points) +@jit(nopython=True) # fill a node (may be two or more points) def fill(img, p, num, nbs, acc, buf): - back = img[p] img[p] = num buf[0] = p - cur = 0; s = 1; + cur = 0; s = 1; iso = True; while True: p = buf[cur] for dp in nbs: cp = p+dp - if img[cp]==back: + if img[cp]==2: img[cp] = num buf[s] = cp s+=1 + if img[cp]==1: iso=False cur += 1 if cur==s:break - return idx2rc(buf[:s], acc) + return iso, idx2rc(buf[:s], acc) -@jit # trace the edge and use a buffer, then buf.copy, if use [] numba not works +@jit(nopython=True) # trace the edge and use a buffer, then buf.copy, if use [] numba not works def trace(img, p, nbs, acc, buf): c1 = 0; c2 = 0; newp = 0 - cur = 0 - + cur = 1 while True: buf[cur] = p img[p] = 0 @@ -67,31 +64,42 @@ def trace(img, p, nbs, acc, buf): for dp in nbs: cp = p + dp if img[cp] >= 10: - if c1==0:c1=img[cp] - else: c2 = img[cp] + if c1==0: + c1 = img[cp] + buf[0] = cp + else: + c2 = img[cp] + buf[cur] = cp if img[cp] == 1: newp = cp p = newp if c2!=0:break - return (c1-10, c2-10, idx2rc(buf[:cur], acc)) + return (c1-10, c2-10, idx2rc(buf[:cur+1], acc)) -@jit # parse the image then get the nodes and edges -def parse_struc(img): - nbs = neighbors(img.shape) - acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1] +@jit(nopython=True) # parse the image then get the nodes and edges +def parse_struc(img, nbs, acc, iso, ring): img = img.ravel() - pts = np.array(np.where(img==2))[0] buf = np.zeros(131072, dtype=np.int64) num = 10 nodes = [] - for p in pts: + for p in range(len(img)): if img[p] == 2: - nds = fill(img, p, num, nbs, acc, buf) + isiso, nds = fill(img, p, num, nbs, acc, buf) + if isiso and not iso: continue num += 1 nodes.append(nds) - edges = [] - for p in pts: + for p in range(len(img)): + if img[p] <10: continue + for dp in nbs: + if img[p+dp]==1: + edge = trace(img, p+dp, nbs, acc, buf) + edges.append(edge) + if not ring: return nodes, edges + for p in range(len(img)): + if img[p]!=1: continue + img[p] = num; num += 1 + nodes.append(idx2rc([p], acc)) for dp in nbs: if img[p+dp]==1: edge = trace(img, p+dp, nbs, acc, buf) @@ -99,47 +107,76 @@ def parse_struc(img): return nodes, edges # use nodes and edges build a networkx graph -def build_graph(nodes, edges, multi=False): +def build_graph(nodes, edges, multi=False, full=True): + os = np.array([i.mean(axis=0) for i in nodes]) + if full: os = os.round().astype(np.uint16) graph = nx.MultiGraph() if multi else nx.Graph() for i in range(len(nodes)): - graph.add_node(i, pts=nodes[i], o=nodes[i].mean(axis=0)) + graph.add_node(i, pts=nodes[i], o=os[i]) for s,e,pts in edges: + if full: pts[[0,-1]] = os[[s,e]] l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum() graph.add_edge(s,e, pts=pts, weight=l) return graph -def buffer(ske): - buf = np.zeros(tuple(np.array(ske.shape)+2), dtype=np.uint16) - buf[tuple([slice(1,-1)]*buf.ndim)] = ske +def mark_node(ske): + buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16) + nbs = neighbors(buf.shape) + acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1] + mark(buf, nbs) return buf - -def build_sknw(ske, multi=False): - buf = buffer(ske) - mark(buf) - nodes, edges = parse_struc(buf) - return build_graph(nodes, edges, multi) + +def build_sknw(ske, multi=False, iso=True, ring=True, full=True): + buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16) + nbs = neighbors(buf.shape) + acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1] + mark(buf, nbs) + nodes, edges = parse_struc(buf, nbs, acc, iso, ring) + return build_graph(nodes, edges, multi, full) # draw the graph def draw_graph(img, graph, cn=255, ce=128): acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1] img = img.ravel() - for idx in graph.nodes(): - pts = graph.node[idx]['pts'] - img[np.dot(pts, acc)] = cn for (s, e) in graph.edges(): eds = graph[s][e] - for i in eds: - pts = eds[i]['pts'] - img[np.dot(pts, acc)] = ce + if isinstance(graph, nx.MultiGraph): + for i in eds: + pts = eds[i]['pts'] + img[np.dot(pts, acc)] = ce + else: img[np.dot(eds['pts'], acc)] = ce + for idx in graph.nodes(): + pts = graph.nodes[idx]['pts'] + img[np.dot(pts, acc)] = cn if __name__ == '__main__': - g = nx.MultiGraph() - g.add_nodes_from([1,2,3,4,5]) - g.add_edges_from([(1,2),(1,3),(2,3),(4,5),(5,4)]) - print(g.nodes()) - print(g.edges()) - a = g.subgraph(1) - print('d') - print(a) - print('d') - \ No newline at end of file + import matplotlib.pyplot as plt + + img = np.array([ + [0,0,0,1,0,0,0,0,0], + [0,0,0,1,0,0,0,1,0], + [0,0,0,1,0,0,0,0,0], + [1,1,1,1,0,0,0,0,0], + [0,0,0,0,1,0,0,0,0], + [0,1,0,0,0,1,0,0,0], + [1,0,1,0,0,1,1,1,1], + [0,1,0,0,1,0,0,0,0], + [0,0,0,1,0,0,0,0,0]]) + + node_img = mark_node(img) + graph = build_sknw(img, False, iso=True, ring=True) + plt.imshow(node_img[1:-1,1:-1], cmap='gray') + + # draw edges by pts + for (s,e) in graph.edges(): + ps = graph[s][e]['pts'] + plt.plot(ps[:,1], ps[:,0], 'green') + + # draw node by o + nodes = graph.nodes() + ps = np.array([nodes[i]['o'] for i in nodes]) + plt.plot(ps[:,1], ps[:,0], 'r.') + + # title and show + plt.title('Build Graph') + plt.show() \ No newline at end of file diff --git a/imagepy/ipyalg/hydrology/edt.py b/imagepy/ipyalg/hydrology/edt.py new file mode 100644 index 00000000..b51ba9cd --- /dev/null +++ b/imagepy/ipyalg/hydrology/edt.py @@ -0,0 +1,117 @@ +import numpy as np +from numba import jit +from scipy.ndimage import label, generate_binary_structure + +def neighbors(shape): + dim = len(shape) + block = generate_binary_structure(dim, 1) + block[tuple([1]*dim)] = 0 + idx = np.where(block>0) + idx = np.array(idx, dtype=np.uint8).T + idx = np.array(idx-[1]*dim) + acc = np.cumprod((1,)+shape[::-1][:-1]) + return np.dot(idx, acc[::-1]) + +@jit(nopython=True) +def dist(idx1, idx2, acc): + dis = 0 + for i in range(len(acc)): + c1 = idx1//acc[i] + c2 = idx2//acc[i] + dis += (c1-c2)**2 + idx1 -= c1*acc[i] + idx2 -= c2*acc[i] + return dis + +@jit(nopython=True) +def step(dis, pts, roots, s, level, nbs, acc, scale): + cur = 0 + while curlevel: + cur += 1 + continue + for dp in nbs: + cp = p+dp + if dis[cp]=0xffff-1: continue # edge or back + for dp in nbs: + if dis[p+dp]==0xffff-1: + pts[cur] = p + root[cur] = p + cur += 1 + break + return cur + +@jit(nopython=True) +def bufjit(line): + for i in range(len(line)): + line[i] = 1 if line[i]==0 else 0xffff + +def buffer(img, dtype): + buf = np.ones(tuple(np.array(img.shape)+2), dtype=dtype) + buf[tuple([slice(1,-1)]*buf.ndim)] = img + bufjit(buf.ravel()) + buf[tuple([slice(1,-1)]*buf.ndim)] -= 1 + return buf + +def distance_transform_edt(img, output=np.float32, scale=1): + dis = buffer(img, output) + nbs = neighbors(dis.shape) + acc = np.cumprod((1,)+dis.shape[::-1][:-1])[::-1] + line = dis.ravel() + pts = np.zeros(max(line.size//4, 1024**2), dtype=np.int64) + roots = np.zeros(max(line.size//4, 1024**2), dtype=np.int64) + s = collect(line, nbs, pts, roots) + for level in range(10000): + s, c = clear(pts, roots, s, 0) + s = step(line, pts, roots, s, level, nbs, acc, scale) + if s==0:break + return dis[(slice(1,-1),)*img.ndim] + +if __name__ == '__main__': + import matplotlib.pyplot as plt + from skimage.io import imread + from glob import glob + arr = np.ones((255,255), dtype=np.uint8) + arr[128,128] = 0 + from skimage.data import horse + + arr = ~horse()*255 + print(arr.min(), arr.max()) + dis = distance_transform_edt(arr, np.float64) + + from scipy.ndimage import distance_transform_edt as sciedt + dis2 = sciedt(arr) + print(np.abs(dis-dis2).max()) + plt.imshow(dis2) + plt.show() diff --git a/imagepy/ipyalg/hydrology/findmax.py b/imagepy/ipyalg/hydrology/findmax.py index fa9a27c3..1869b93f 100644 --- a/imagepy/ipyalg/hydrology/findmax.py +++ b/imagepy/ipyalg/hydrology/findmax.py @@ -12,7 +12,7 @@ def neighbors(shape): acc = np.cumprod((1,)+shape[::-1][:-1]) return np.dot(idx, acc[::-1]) -@jit # trans index to r, c... +@jit(nopython=True) # trans index to r, c... def idx2rc(idx, acc): rst = np.zeros((len(idx), len(acc)), dtype=np.int16) for i in range(len(idx)): @@ -21,10 +21,7 @@ def idx2rc(idx, acc): idx[i] -= rst[i,j]*acc[j] return rst - - -@jit # fill a node (may be two or more points) - +@jit(nopython=True) # fill a node (may be two or more points) def fill(img, msk, p, nbs, buf): buf[0] = p back = img[p] @@ -43,9 +40,8 @@ def fill(img, msk, p, nbs, buf): s-=cur; cur=0; cur += 1 -@jit # my mark -def mark(img, msk, buf, mode): # mark the array use (0, 1, 2) - nbs = neighbors(img.shape) +@jit(nopython=True) # my mark +def mark(img, nbs, msk, buf, mode): # mark the array use (0, 1, 2) idx = np.zeros(msk.size//3, dtype=np.int64) img = img.ravel() msk = msk.ravel() @@ -71,15 +67,13 @@ def mark(img, msk, buf, mode): # mark the array use (0, 1, 2) if s==len(idx):break return idx[:s].copy() -@jit # 3 max 2 zmd b4 ptd -def filter(img, msk, idx, bur, tor, mode): - nbs = neighbors(img.shape) - acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1] +@jit(nopython=True) # 3 max 2 zmd b4 ptd +def filter(img, msk, nbs, acc, idx, bur, tor, mode): img = img.ravel() msk = msk.ravel() arg = np.argsort(img[idx])[::-1 if mode else 1] - + for i in arg: if msk[idx[i]]!=3: idx[i] = 0 @@ -117,16 +111,20 @@ def find_maximum(img, tor, mode = True): msk = np.zeros_like(img, dtype=np.uint8) msk[tuple([slice(1,-1)]*img.ndim)] = 1 buf = np.zeros(img.size//3, dtype=np.int64) - idx = mark(img, msk, buf, mode) - idx = filter(img, msk, idx, buf, tor, mode) + nbs = neighbors(img.shape) + acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1] + idx = mark(img, nbs, msk, buf, mode) + idx = filter(img, msk, nbs, acc, idx, buf, tor, mode) return idx if __name__ == '__main__': - from scipy.misc import imread - from scipy.ndimage import gaussian_filter + from skimage.io import imread + from scipy.ndimage import gaussian_filter, distance_transform_edt from time import time import matplotlib.pyplot as plt - img = gaussian_filter(imread('test.png'), 0) + from skimage.data import horse + + img = distance_transform_edt(~horse()) pts = find_maximum(img, 20, True) start = time() pts = find_maximum(img, 10, True) diff --git a/imagepy/ipyalg/hydrology/isoline.py b/imagepy/ipyalg/hydrology/isoline.py index a83d7fbf..0f8f32a2 100644 --- a/imagepy/ipyalg/hydrology/isoline.py +++ b/imagepy/ipyalg/hydrology/isoline.py @@ -12,7 +12,7 @@ def neighbors(shape): acc = np.cumprod((1,)+shape[::-1][:-1]) return np.dot(idx, acc[::-1]) -@jit +@jit(nopython=True) def stair(img, low=0, high=255, step=20): img = img.ravel() for i in range(len(img)): @@ -20,18 +20,19 @@ def stair(img, low=0, high=255, step=20): elif img[i]>high:img[i]=high else: img[i] = (img[i]-low)//step*step+low -@jit # my mark +@jit(nopython=True) # my mark +def isoline_jit(img, mark, nbs): + for p in range(len(img)): + if mark[p]==0:continue + s = 0 + for dp in nbs: + if img[p] > img[p+dp]:s+=1 + if s==0:mark[p] = 0 + def isoline(img, low=0, high=255, step=20): stair(img, low, high, step) nbs = neighbors(img.shape) mark = np.zeros_like(img, dtype=np.uint8) mark[tuple([slice(1,-1)]*img.ndim)] = 255 - img = img.ravel() - mark1 = mark.ravel() - for p in range(len(img)): - if mark1[p]==0:continue - s = 0 - for dp in nbs: - if img[p] > img[p+dp]:s+=1 - if s==0:mark1[p] = 0 + isoline_jit(img.ravel(), mark.ravel(), nbs) return mark \ No newline at end of file diff --git a/imagepy/ipyalg/hydrology/ridge.py b/imagepy/ipyalg/hydrology/ridge.py index 50f0abb8..b950d7ae 100644 --- a/imagepy/ipyalg/hydrology/ridge.py +++ b/imagepy/ipyalg/hydrology/ridge.py @@ -3,7 +3,7 @@ from scipy.ndimage import label, generate_binary_structure -strc = np.ones((3,3), dtype=np.bool) +strc = np.ones((3,3), dtype='bool') def count(n): a = [(n>>i) & 1 for i in range(8)] @@ -201,7 +201,7 @@ def ridge(img, mark, up=True): else: mark[i] = 0 if __name__ == '__main__': - from scipy.misc import imread + from skimage.io import imread import scipy.ndimage as ndimg import matplotlib.pyplot as plt from time import time diff --git a/imagepy/ipyalg/hydrology/watershed.py b/imagepy/ipyalg/hydrology/watershed.py index cb1b1312..70790c23 100644 --- a/imagepy/ipyalg/hydrology/watershed.py +++ b/imagepy/ipyalg/hydrology/watershed.py @@ -1,7 +1,7 @@ import numpy as np from numba import jit from scipy.ndimage import label, generate_binary_structure -from scipy.misc import imread, imsave +from skimage.io import imread, imsave def neighbors(shape, conn=1): dim = len(shape) @@ -13,7 +13,7 @@ def neighbors(shape, conn=1): acc = np.cumprod((1,)+shape[::-1][:-1]) return np.dot(idx, acc[::-1]) -@jit +@jit(nopython=True) def step(img, msk, line, pts, s, level, up, nbs): cur = 0 while cur0xfffffff0:mark[i]=0 @@ -113,11 +113,11 @@ def watershed(img, mark, conn=1, line=False, up=True): markers[coins > 150] = 2 plt.imshow(markers) plt.show() - watershed(dem, markers) + markers = watershed(dem, markers) plt.imshow(markers) plt.show() ''' - from scipy.misc import imread + from skimage.io import imread import matplotlib.pyplot as plt from time import time diff --git a/imagepy/core/wraper/__init__.py b/imagepy/ipyalg/transform/__init__.py similarity index 100% rename from imagepy/core/wraper/__init__.py rename to imagepy/ipyalg/transform/__init__.py diff --git a/imagepy/ipyalg/transform/transform.py b/imagepy/ipyalg/transform/transform.py new file mode 100644 index 00000000..2ca87abd --- /dev/null +++ b/imagepy/ipyalg/transform/transform.py @@ -0,0 +1,57 @@ +from skimage.io import imread +from skimage.data import astronaut, camera +from scipy.ndimage import map_coordinates +import numpy as np +# import matplotlib.pyplot as plt + +def linear_polar(img, o=None, r=None, order=1, cont=0, output=None): + if o is None: o = np.array(img.shape[:2])/2 - 0.5 + if r is None: r = (np.array(img.shape[:2])**2).sum()**0.5/2 + cns = 1 if img.ndim == 2 else img.shape[2] + if output is None: + shp = int(round(r)), int(round(r*2*np.pi)) + output = np.zeros(shp+(cns,), dtype=img.dtype) + elif isinstance(output, tuple): + output = np.zeros(output+(cns,), dtype=img.dtype) + out_h, out_w, _ = output.shape + out_img = np.zeros((out_h, out_w), dtype=img.dtype) + rs = np.linspace(0, r, out_h) + ts = np.linspace(0, np.pi*2, out_w) + xs = rs[:,None] * np.cos(ts) + o[1] + ys = rs[:,None] * np.sin(ts) + o[0] + img = img.reshape(img.shape[:2]+(-1,)) + for i in range(cns): + map_coordinates(img[:,:,i], (ys, xs), order=order, output=output[:,:,i]) + return output.reshape(output.shape[:2]) if output.shape[2]==1 else output + +def polar_linear(img, o=None, r=None, order=1, cont=0, output=None): + if r is None: r = img.shape[0] + cns = 1 if img.ndim == 2 else img.shape[2] + if output is None: + output = np.zeros((r*2, r*2, cns), dtype=img.dtype) + elif isinstance(output, tuple): + output = np.zeros(output+(cns,), dtype=img.dtype) + if o is None: o = np.array(output.shape[:2])/2 - 0.5 + out_h, out_w, _ = output.shape + ys, xs = np.mgrid[:out_h, :out_w] - o[:,None,None] + rs = (ys**2+xs**2)**0.5 + ts = np.arccos(xs/rs) + ts[ys<0] = np.pi*2 - ts[ys<0] + ts *= (img.shape[1]-1)/(np.pi*2) + img = img.reshape(img.shape[:2]+(-1,)) + for i in range(cns): + map_coordinates(img[:,:,i], (rs, ts), order=order, output=output[:,:,i]) + return output.reshape(output.shape[:2]) if output.shape[2]==1 else output + + +if __name__ == '__main__': + img = camera() + ax = plt.subplot(311) + ax.imshow(img) + out = linear_polar(img) + ax = plt.subplot(312) + ax.imshow(out) + img = polar_linear(out, output=img.shape[:2]) + ax = plt.subplot(313) + ax.imshow(img) + plt.show() \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/Edit.dic b/imagepy/lang/Chinese/plugins/Edit.dic new file mode 100644 index 00000000..0288ab16 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/Edit.dic @@ -0,0 +1,17 @@ +Undo::撤回 + +Copy::拷贝 + +Paste::粘贴 + +Sketch::描边 + width::宽度 + pix::像素 + +Fill::填充 + +Invert::反转 + +Clear::清空(ROI内) + +Clear Out::清空(ROI外) \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/Kit3D.dic b/imagepy/lang/Chinese/plugins/Kit3D.dic new file mode 100644 index 00000000..795ab5d3 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/Kit3D.dic @@ -0,0 +1,143 @@ +Filters 3D::三维滤波 + +Gaussian 3D::三维高斯滤波 + sigma::方差 + +Uniform 3D::三维均值滤波 + size::核大小 + +Sobel 3D::三维Sobel滤波 + sigma::方差 + wight::权重 + +Unsharp Mask 3D::三维非锐化掩膜 + +Up Down Watershed 3D::三维上下限分水岭 + +Features 3D::三维特征增强 + +Frangi 3D::三维Frangi特征增强 + sigma start::标准差上限 + sigma end::标准差下限 + sigma step::步长 + alpha::alpha值 + beta::beta值 + gamma::gamma增益 + +Meijering 3D::三维Meijering特征增强 + sigma start::标准差上限 + sigma end::标准差下限 + sigma step::步长 + +Sato 3D::三维Sato特征增强 + sigma start::标准差上限 + sigma end::标准差下限 + sigma step::步长 + +Hessian 3D::三维Hessian特征增强 + sigma start::标准差上限 + sigma end::标准差下限 + sigma step::步长 + alpha::alpha值 + beta::beta值 + gamma::gamma增益 + +Binary 3D::三维二值化 + +Dilation 3D::三维膨胀 + r::半径 + +Erosion 3D::三维腐蚀 + r::半径 + +Opening 3D::三维开运算 + r::半径 + +Closing 3D::三维闭运算 + r::半径 + +Fill Holes 3D::三维填充孔洞 + +Skeleton 3D::三维骨架提取 + +Distance 3D::三维距离变换 + +Binary Watershed 3D::三维二值分水岭 + tolerance::容错 + +Analysis 3D::三维分析 + +Pixel Statistic 3D::三维像素统计 + +Frequence 3D::三维频数计算 + bins::直方图箱数 + +Region Label 3D::三维区域标记 + +Geometry Analysis 3D::三维几何分析 + +Geometry Filter 3D::三维几何过滤 + front color::前景色 + back color::背景色 + volume::体积 + diagonal::对角线 + +Measure Surface And Volume::表面体积测量 + +Network 3D::三维网络图 + +Skeleton 3D::三维骨架提取 + +Build Graph 3D::三维拓扑建立 + +Graph Cut Branch 3D::三维剪枝 + limit::阈值 + +Remove Isolate 3D::三维移除孤立点 + +Remove 2Path Node 3D::三维移除2路径节点 + +Graph Statistic 3D::三维拓扑关系统计 + +Graph Summarise 3D::三维拓扑关系汇总 + +Show Graph 3D::三维拓扑可视化 + radius::半径 + node::节点 + line::线 + path::路径 + +Show Graph R 3D::三维球棍模型可视化 + +Viewer 3D::三维画布 + +Show Viewer 3D::新建三维画布 + +2D Surface::2D地表重建 + Name::名字 + down scale::降采样 + sigma::平滑标准差 + scale z::z轴缩放 + +3D Surface::三维表面重建 + Name::名字 + down scale::降采样 + march step::重建步长 + color::颜色 + +3D Image Cube::三维立方体 + +RGB Points Cloud::RGB点云 + Name::名字 + number::数量 + radius::半径 + +Table Point Cloud::点云表格 + +2DSurface Demo::2D表面演示 + +Lines Demo::三维线演示 + +Random Balls Demo::随机球演示 + +Decoration Demo::小饰品演示 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/Plugins.dic b/imagepy/lang/Chinese/plugins/Plugins.dic new file mode 100644 index 00000000..bae8493b --- /dev/null +++ b/imagepy/lang/Chinese/plugins/Plugins.dic @@ -0,0 +1,92 @@ +New::新建 + +New Filter::新建Filter + +New Simple::新建Simple + +New Free::新建Free + +New Tool::新建Tool + +Macros::宏 + +Macros Recorder::宏录制器 + +Run Macros::运行宏 + +Manager::管理 + +Plugins Manager::插件管理 + Search::搜索 + Name::名称 + Author::作者 + Version::版本 + Status::状态 + installed::已安装 + only installed::只显示已安装 + Install::安装 + Update::更新 + Remove::移除 + +Plugin Tree View::插件树浏览 + Plugin Information::插件信息 + [SourceCode]::[查看源码] + +Tool Tree View::工具树浏览 + Tool Information::插件信息 + [SourceCode]::[查看源码] + +Plugin List View::插件列表 + Search::搜索 + +Shortcut Editor::快捷键编辑器 + +Command Line::命令行 + +Install::安装 + +Install Packages::安装包 + +List Packages::包列表 + +Install Plugins::安装插件 + input a zipfile url or github url as http://github.com/username/project::输入一个zip文件链接或github链接,例如http://github.com/username/project + package::链接 + Use proxy::使用代理 + Protocol::协议类型 + IP Address::IP地址 + Port::端口号 + +List Plugins::插件列表 + +Contribute::参与改进 + +Contribute Document::提交文档 + +Contribute Plugin::提交插件 + +Plugins Manager::插件管理 + +Contributions::支持信息 + +Update Software::软件升级 + +Reload Plugins::重新载入插件 + +StackReg::StackReg(图像栈对齐) + +Stack Register::生成图像栈 + +Register By Mats::矩阵生成图像栈 + +StackReg License::许可文件 + +Games::游戏 + +Cross Stick::十字绣 + +Stroke Step::笔迹 + +Game Of Life::生命游戏 + +Screen Capture::截屏 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/analysis.dic b/imagepy/lang/Chinese/plugins/analysis.dic new file mode 100644 index 00000000..68590b36 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/analysis.dic @@ -0,0 +1,181 @@ +Label Image::区域标记 + +Mark Boundaries::标记边界 + structure::联通 + connect::邻域 + slice::序列 + mode::边界模式 + +Label Render::标记着色 + structure::联通 + connect::邻域 + colors::颜色数量 + background::标记背景 + slice::序列 + +Frequence::频数统计 + count frequence::统计频率 + slice::序列 + +Pixel Statistic::像素统计 + max::最大值 + min::最小值 + mean::平均值 + var::方差 + std::标准差 + slice::序列 + +Histogram::直方图 + +Points Value::ROI灰度 + buffer by the value::以中心为中心,灰度为半径 + slice::序列 + +Shortest Route::最小代价路径 + step::几何距离 + cost::权重 + output::输出类型 + geometric::几何 + fully connected::允许走对角线 + max cost::最大代价 + +Pixel Cluster::像素聚类 + +Gray Cluster::灰度聚类 + torlerance::容错 + use cov instead of distance::使用协方差椭圆 + only points within::仅提取与种子点联通的区域 + result as new mask::结果作为掩膜 + mask::掩膜方式 + +Color Cluster::色彩聚类 + torlerance::容错 + use cov instead of distance::使用协方差椭圆 + only points within::仅提取与种子点联通的区域 + result as new mask::结果作为掩膜 + mask::掩膜方式 + +Gray Cluster 3D::3D灰度图聚类 + torlerance::容错 + use cov instead of distance::使用协方差椭圆 + only points within::仅提取与种子点联通的区域 + result as new mask::结果作为掩膜 + mask::掩膜方式 + +Color Cluster 3D::3D彩色图聚类 + torlerance::容错 + use cov instead of distance::使用协方差椭圆 + only points within::仅提取与种子点联通的区域 + result as new mask::结果作为掩膜 + mask::掩膜方式 + +Gray Points Statistic::灰度ROI统计 + +Color Points Statistic::彩色图ROI统计 + +K-Means::K均值聚类 + k::质心数 + iterate::迭代次数 + threshold::变化阈值 + resample::采样间隔 + +Region Analysis::区域分析 + +Geometry Analysis::几何分析 + conection::联通 + pix::邻域 + slice::序列 + ========= indecate =========::========= 指标 ========= + center::质心 + area::面积 + perimeter::周长 + extent::外接矩形 + equivalent diameter::等效直径 + convex area::凸包面积 + holes::镂空 + filled area::填充面积 + solidity::丰满度 + cov::质量协方差 + +Geometry Filter::几何特征过滤 + front color::前景色 + back color::背景色 + area::面积 + perimeter::周长 + holes::镂空数 + solidity::丰满度 + eccentricity::偏心率 + ratio::比例 + unit::单位 + unit^2::单位^2 + invert::过滤黑色区域 + conection::联通 + pix::邻域 + num::个 + Filter: "+" means >=, "-" means <::"+" 代表 >=, "-" 代表 < + +Property Marker::属性标记 + conection::联通 + property::染色属性 + color map::色谱 + pix::邻域 + +Connective Analysis::区域邻接分析 + conection::联通 + pix::邻域 + it is a label image::图像已标记 + nonzero::只分析非0区域 + slice::序列 + +Intensity Analysis::区域灰度分析 + intensity::目标图像 + conection::联通 + pix::邻域 + slice::序列 + has labeled::图像已标记 + ========= indecate =========::========= 指标 ========= + center::质心 + extent::外接矩 + max::最大值 + min::最小值 + mean::平均值 + standard::标准差 + sum::求和 + +Intensity Filter::区域灰度过滤 + intensity::目标图像 + conection::联通 + pix::邻域 + slice::序列 + Filter: "+" means >=, "-" means <::"+" 代表 >=, "-" + front color::前景色 + back color::背景色 + max::最大值 + min::最小值 + mean::平均值 + std::标准差 + sum::求和 + +Skeleton Network::骨架网络 + +Graph Statistic::边,节点统计 + +Build Graph::从骨架建立图 + +Graph Summarise::图信息汇总 + +Remove Isolate Node::移除孤立节点 + +Remove 2Path Node::移除双路径节点 + +Cut Branch::分支剪切 + limit::阈值 + unit::单位 + recursion::递归剪枝 + +Cut By ROI::用ROI剪枝 + +Graph Shortest Path::图最短路径 + start::出发 + end::到达 + id::编号 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/common.dic b/imagepy/lang/Chinese/plugins/common.dic new file mode 100644 index 00000000..1de06239 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/common.dic @@ -0,0 +1,7 @@ +common::普通 + OK::确定 + Cancel::取消 + Help::帮助 + preview::预览 + Widgets::控件 + Table::表格 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/file.dic b/imagepy/lang/Chinese/plugins/file.dic new file mode 100644 index 00000000..538133cd --- /dev/null +++ b/imagepy/lang/Chinese/plugins/file.dic @@ -0,0 +1,111 @@ +New Image::新建图像 + name::名称 + width::宽度 + height::高度 + pix::像素 + type::类型 + slice::层数 + +Open::打开图像 + +Open Url::打开链接 + Input the URL, eg. http://data.imagepy.org/testdata/yxdragon.jpg::输入链接, 例如 http://data.imagepy.org/testdata/yxdragon.jpg + Url::链接 + +Open Recent::打开最近 + +Samples Local::本地图像样例 + +Samples Online::在线图像样例 + +Samples ImageJ::ImageJ图像样例 + +Import::导入 + +Open Raw::打开Raw文件 + +Import Rois from IJ::导入ImageJ感兴趣区域 + +Import Sequence::导入图像序列 + +Export::导出 + +Save Sequence::导出图像序列 + +BMP::BMP图像 + +BMP Open::打开BMP图像 + +BMP Save::保存BMP图像 + +JPG::JPG图像 + +JPG Open::打开JPG图像 + +JPG Save::保存JPG图像 + +PNG::PNG图像 + +PNG Open::打开PNG图像 + +PNG Save::保存PNG图像 + +TIF::TIF图像 + +TIF Open::打开TIF图像 + +TIF Save::保存TIF图像 + +TIF 3D Open::打开3D TIF图像 + +TIF 3D Save::保存3D TIF图像 + +GIF::GIF图像 + +GIF Open::打开GIF图像 + +GIF Save::保存GIF图像 + +GIF Animate Open::打开GIF动画 + +GIF Animate Save::保存GIF动画 + +DICOM::DICOM图像 + +DCM Open::打开DICOM图像 + +DAT::DAT图像 + +DAT Open::打开DAT图像 + +DAT Save::保存DAT图像 + +Numpy::Numpy文件 + +Numpy Open::打开Numpy文件 + +Numpy Save::保存Numpy文件 + +Numpy 3D Open::打开3D Numpy文件 + +Numpy 3D Save::保存3D Numpy文件 + +MAT::MAT图像 + +Mat Open::打开Mat图像 + +Mat Save::保存Mat图像 + +Mat 3D Open::打开3D Mat图像 + +Mat 3D Save::保存3D Mat图像 + +MarkDown::MarkDown文件 + +MarkDown Open::打开MarkDown文件 + +Save::保存图像 + +Save With Mark::保存图像及标记 + +Exit::退出 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/help.dic b/imagepy/lang/Chinese/plugins/help.dic new file mode 100644 index 00000000..36bbe4a5 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/help.dic @@ -0,0 +1,11 @@ +Topic::主题 + +About::关于 + +Home Page::主页 + +Language::语言支持 + +Chinese::中文 + +English::英文 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/image.dic b/imagepy/lang/Chinese/plugins/image.dic new file mode 100644 index 00000000..f3c3481d --- /dev/null +++ b/imagepy/lang/Chinese/plugins/image.dic @@ -0,0 +1,122 @@ +Type::类型 + +Adjust::调整 + +Color::颜色 + +Stack::序列 + +Orthogonal view::正交视图 + +Set Slice::设定切片 + Num::范围 + +Next Slice::下一切片 + +Previous Slice::上一切片 + +Add Slice::添加切片 + +Delete Slice::删除切片 + +Sub Stack::子连续序列 + start::开始 + end::结束 + OK::确定 + Cancel::取消 + Help::帮助 + +Rename::重命名 + name::名字 + +Duplicate::复制 + +Crop::裁剪 + +Canvas Size::画布尺寸 + Weight::宽 + Height::高 + Horizontal::水平 + Vertical::垂直 + pix::像素 + left::左 + center::中 + right::右 + +Resize::设定尺寸 + kx::x比例 + ky::y比例 + kz::z比例 + accu::插值次数 + the kz only works on stack!::z比例仅用于连续序列 + +Scale And Unit::比例和单位 + per::比例 + unit::单位 + pix::像素 + kill scale::清除比例 + +Mark::标记 + +Open Mark::打开标记 + +Save Mark::保存标记 + +Clear Mark::清除标记 + +Mark Setting::标记设定 + line::线 + face::填充 + text::文本 + color::颜色 + width::宽 + solid fill::填充 + +Set Background::设定背景 + background::背景 + mode::模式 + kill::清除 + ratial::混合比例 + +Trans to Stack::连续序列 + +Trans to List::离散序列 + +Background Self::原图作为背景 + mode::模式 + ratial::混合比例 + +Lookup tabel::索引色 + +Threshold::阈值 + +Gray Stairs::灰度等级 + +Bright And Constract::亮度与对比度 + +Bleach Correction::白校正 + +Enhance Contrast::对比度增强 + Saturated pixels::饱和像素 + +Normalize::归一化 + 3D stack::三维连续序列 + Subtract background::减去背景 + +Color Balance::色彩平衡 + +Color Stairs::色彩等级 + +Histogram Normalize::直方图归一化 + +Histogram Match::直方图匹配 + temp::目标 + +Lookup table::索引色 + +Rotate::旋转 + degree::度 + angle::角度 + +Scale::缩放 + fact::因子 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/menus.dic b/imagepy/lang/Chinese/plugins/menus.dic new file mode 100644 index 00000000..48f1740b --- /dev/null +++ b/imagepy/lang/Chinese/plugins/menus.dic @@ -0,0 +1,21 @@ +File::文件 + +Edit::编辑 + +Image::图像 + +Process::处理 + +Selection::选择 + +Analysis::分析 + +Table::表格 + +Kit3D::三维 + +Plugins::插件 + +Window::窗口 + +Help::帮助 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/process.dic b/imagepy/lang/Chinese/plugins/process.dic new file mode 100644 index 00000000..67639b75 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/process.dic @@ -0,0 +1,392 @@ +Math::数学运算 + +Add::加法 + +Subtract::减法 + +Multiply::乘法 + +Max::最大值 + +Min::最小值 + +Square Root::平方根 + +Gamma::伽马矫正 + +Binary::二值图像 + +Binary Dilation::膨胀 + width::结构元宽 + height::结构元高 + +Binary Erosion::腐蚀 + width::结构元宽 + height::结构元高 + +Binary Closing::闭运算 + width::结构元宽 + height::结构元高 + +Binary Opening::开运算 + width::结构元宽 + height::结构元高 + +Binary Outline::轮廓线 + +Fill Holes::填充空洞 + +Clear Border::清理边界线 + +Binary ConvexHull::凸包 + +Skeleton::骨架线 + +Medial Axis::中轴线 + +Distance Transform::距离变换 + +Binary Watershed::二值分水岭 + tolerance::容错 + +Binary Voronoi::Voronoi区域划分 + +Filters::滤波器 + +Uniform::均值滤波 + size::核大小 + +Gaussian::高斯滤波 + sigma::标准差 + +Maximum::最大值滤波 + size::核大小 + +Minimum::最小值滤波 + size::核大小 + +Median::中值滤波 + size::核大小 + pix::像素 + +Percent::百分比滤波 + size::核大小 + percent::百分比 + +Prewitt::Prewitt梯度滤波 + +Sobel::Sobel梯度滤波 + direction::方向 + aixs::轴 + +Laplace::拉普拉斯滤波 + +Gaussian Laplace::高斯拉普拉斯滤波 + sigma::标准差 + +DOG::高斯差分 + sigma1::标准差1 + sigma2::标准差2 + +Variance::方差滤波 + size::核大小 + +Laplace Sharp::拉普拉斯锐化 + weight::权重 + +Unsharp Mask::USM锐化 + sigma::标准差 + weight::权重 + +FFT::傅里叶变换 + +FFT::傅里叶变换 + +Inverse FFT::傅里叶逆变换 + +Zero Center::零点到中心 + +Zero Edge::零点到边缘 + +Split Real And Image::实部序部分离 + +Threshold::阈值 + +Simple Threshold::简单阈值 + +Auto Threshold::自动阈值 + +Adaptive Threshold::自适应阈值 + method::方法 + blocksize::局部块大小 + offset::偏离值 + +Niblack Threshold::Niblack阈值 + blocksize::局部块大小 + offset::偏离值 + +Sauvola Threshold::Sauvola阈值 + blocksize::局部块大小 + offset::偏离值 + +Hysteresis Threshold::双阈值 + +Hydrology::水文算法 + +Find Maximum::查找极大值 + tolerance::容错 + +Find Minimum::查找极小值 + tolerance::容错 + +Find IsoLine::查找等高线 + low::下阈值 + high::上阈值 + step::步长 + output::输出 + +Find Ridge::查找山脊线 + sigma::标准差 + Low::阈值 + ascend::涨水 + output::输出 + +Active Ridge::交互查找山脊线 + Low::阈值 + ascend::涨水 + output::输出 + +Find Watershed::分水岭 + sigma::标准差 + Low::阈值 + full connectivity::八邻域连通 + ascend::涨水 + output::输出 + +Up And Down Watershed::上下阈值分水岭 + Low::下阈值 + High::上阈值 + output::输出 + +Watershed With ROI::带ROI的分水岭 + full connectivity::八邻域连通 + ascend::涨水 + output::输出 + +Features::特征点选取 + +Canny::Canny边缘检测 + sigma::标准差 + +Harris::Harris角点检测 + sigma::标准差 + K::灵敏度 + +Kitchen Rosenfeld::Kitchen角点检测 + cval::边界外值 + +Moravec::Moravec角点检测 + size::窗口大小 + +Tomasi::Tomasi角点检测 + sigma::标准差大小 + +Frangi::Frangi脊线检测 + sigma start::起始标准差 + sigma end::终止标准差 + sigma step::步长 + alpha::blob灵敏度 + beta::纹理灵敏度 + gamma::伽马值 + +Meijering::Meijering脊线检测 + sigma start::起始标准差 + sigma end::终止标准差 + sigma step::步长 + +Sato::Sato脊线检测 + sigma start::起始标准差 + sigma end::终止标准差 + sigma step::步长 + +Hessian::Hessian特征提取 + sigma start::起始标准差 + sigma end::终止标准差 + sigma step::步长 + alpha::blob灵敏度 + beta::纹理灵敏度 + gamma::伽马值 + +Blob Dog::Dog尺度特征 + min::最小标准偏差 + max::最大标准偏差 + ratio::步长比例 + threshold::强度阈值 + overlab::临近聚合 + +Blob Doh::Doh尺度特征 + min::最小标准偏差 + max::最大标准偏差 + ratio::步长比例 + threshold::强度阈值 + overlab::临近聚合 + +Blob Log::Log尺度特征 + min::最小标准偏差 + max::最大标准偏差 + ratio::步长比例 + threshold::强度阈值 + overlab::临近聚合 + +Segment::分割 + +SLIC Superpixel::SLIC超像素 + segments::分割数量 + campactness::紧凑性 + max_iter::最大迭代次数 + sigma::高斯平滑 + smooth::标准差 + n:: + color-space::色彩 + +Quick Shift::快速Shift聚类 + ratio::色彩/空间平衡 + kernel_size::卷积核宽度 + distance::到切点距离 + cut off:: + sigma::高斯平滑标准差 + color-space::比例 + sigma::高斯平滑 + smooth::标准差 + +Felzenszwalb::Felzenszwalb聚类 + scale::尺度参数 + sigma::高斯平滑内核的宽度 + min_size::最小区域面积 + +SLIC Superpixel Label::SLIC超像素标记 + segments::分割数量 + campactness::紧凑性 + max_iter::最大迭代次数 + sigma::高斯平滑 + smooth::标准差 + n:: + color-space::色彩 + stack::序列 + +Quickshift Label::快速Shift聚类标记 + ratio::色彩/空间平衡 + kernel_size::卷积核宽度 + distance::到切点距离 + cut off:: + sigma::高斯平滑标准差 + color-space::比例 + sigma::高斯平滑 + smooth::标准差 + stack::序列 + +Felzenszwalb Label::Felzenszwalb聚类标记 + scale::尺度参数 + sigma::高斯平滑内核的宽度 + min_size::最小区域面积 + stack::序列 + +RAG Threshold::相邻区域阈值融合 + threshold::阈值 + sigma::标准差 + +RAG Cut Normalized::相邻区域关系简化 + threshold::阈值 + num::数量 + sigma::标准差 + +RAG Merge Hierarchical::相邻区域层级融合 + threshold::阈值 + sigma::标准差 + +Evolving Level Set::水平集收敛 + mu::距离正则系数R(phi) + lambda1::加权长度系数L(phi) + lambda2::加权面积系数A(phi) + tol::容错 + max_iter::最大迭代次数 + dt::步长 + +Morphological Snake Fit::形态学蛇行收敛 + iterations::迭代次数 + smoothing::平滑因子 + lambda1::外轮廓权重 + lambda2::内轮廓权重 + +Bound Snake Fit::外接矩蛇行收敛 + iterations::迭代次数 + smoothing::平滑因子 + threshold::阈值 + balloon::张力 + +ROI Snake Fit::选区蛇行拟合 + iterations::迭代次数 + smoothing::平滑因子 + threshold::阈值 + balloon::张力 + +Random Walker::随机游走分割 + beta::扩散系数 + tolerance::容错 + +Classify::分类器 + +Label Tool::特征标记工具 + +Feature Classify Panel::特征标记面板 + +Random Forest Classify::随机森林分类器 + ===== Classifier Parameter =====::===== 分类器参数 ===== + ===== Feature Parameter =====::===== 特征提取参数 ===== + img::目标图像 + back::背景 + estimators::决策树数量 + n::个 + features::特征 + depth::树最大深度 + max::层 + grade::多尺度等级 + sigma::结构标准差 + tensor::结构张量 + add image feature::原始图像作为特征 + add blur feature::图像多尺度特征 + add sobel feature::图像梯度特征 + add eig feature::结构张量特征 + +ExtraTrees Classify::拓展决策树分类器 + estimators::估计参数 + depth::树深度 + grade::多尺度等级 + sigma::结构标准差 + +Bagging Classify::词袋分类器 + estimators::分类器个数 + depth::决策树深度 + grade::多尺度等级 + sigma::结构标准差 + +AdaBoost Classify::AdaBoost分类器 + estimators::分类器个数 + depth::决策深度 + grade::多尺度等级 + sigma::结构标准差 + +Gradient Boosting Classsify::梯度增强分类器 + estimators::估计参数 + depth::决策树深度 + learn::学习率 + grade::多尺度等级 + sigma::结构标准差 + +Feature Predictor::特征预测 + +Build Mark Image::生成空白标记 + +Fragment Repair::碎片修复 + +Image Calculator::通道运算 diff --git a/imagepy/lang/Chinese/plugins/selection.dic b/imagepy/lang/Chinese/plugins/selection.dic new file mode 100644 index 00000000..455daab9 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/selection.dic @@ -0,0 +1,75 @@ +Select All::全选 + +Select None::取消选区 + +ROI Inflate::选区扩张 + +ROI Shrink::选区收缩 + +ROI Convex Hull::选区凸包 + +ROI Bound Box::选区外接矩 + +ROI Clip::选区剪切 + +ROI Invert::选区补集 + +ROI Open::打开ROI文件 + +ROI Save::保存ROI文件 + +ROI Add::添加到管理器 + Name::名称 + +ROI Load::从管理器加载 + Name::名称 + +ROI Remove::从管理器移除 + Name::名称 + +ROI Intersect::选区交集 + Name::名称 + +ROI Union::选区并集 + Name::名称 + +ROI Difference::选区差集 + Name::名称 + +ROI Symmetric Diff::并集-交集 + Name::名称 + +ROI Setting::选区设置 + line::轮廓线 + face::内部 + text::文字 + width::线宽 + color::颜色 + pix::像素 + size::高度 + solid fill::内部填充 + +ROI Ctrl Panel::选区控制面板 + Manage::管理 + Add::添加 + Load::加载 + Update::更新 + Remove::移除 + Open::打开 + Save::保存 + Operate::几何运算 + Inflate::扩张 + Shrink::收缩 + Convex::凸包 + Bound::外接矩形 + Clip::裁剪 + Invert::反向 + Relationship::关系运算 + Intersect::相交 + Union::合并 + Difference::减去 + Sym Diff::异或 + Draw::绘图 + Sketch::描边 + Clear::清除内部 + Clear Out::清除外部 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/table.dic b/imagepy/lang/Chinese/plugins/table.dic new file mode 100644 index 00000000..c2ff9e90 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/table.dic @@ -0,0 +1,169 @@ +Table IO::表格输入输出 + +CSV Open::打开CSV文件 + +CSV Save::保存CSV文件 + +Excel Open::打开Excel文件 + +Excel Save::保存Excel文件 + +Universal Generator::通用生成器 + +Unit Matrix::单元矩阵 + size::尺寸 + +Uniform Random::均匀随机分布矩阵 + low::最小值 + high::最大值 + row::行数 + col::列数 + +Gaussian Random::高斯随机分布矩阵 + mean::均值 + std::标准差 + row::行数 + col::列数 + +Calendar::日历 + year::年份 + month::月份 + +Basic Operator::基础操作 + +Table Transpose::表格转置 + +Table Duplicate::表格复制 + name::名称 + +Table Crop::表格裁剪 + +Delete Rows::删除行 + +Delete Columns::删除列 + +Append Rows::添加行 + count::行数 + fill by last row::用最后一行填充 + +Add Column::添加列 + name::名称 + value::数值 + +Statistic::统计 + +Table Statistic::表格统计 + axis::轴 + sum::和 + mean::均值 + max::最大值 + min::最小值 + var::方差 + std::标准差 + skew::偏斜度 + kurt::峭度 + +Group Statistic::分组统计 + field to statistic::选择统计区域 + group by::组类别 + major::主组 + minor::副组 + sum::和 + mean::均值 + max::最大值 + min::最小值 + var::方差 + std::标准差 + skew::偏斜度 + +Values Frequency::数值频率 + fields to count::选择统计区域 + +Table Bins Frequency::表格直方图频率 + bins::直方图 + n::个数 + auto scale::自动变换范围 + min range::最小值 + max range::最大值 + fields to count::选择统计区域 + count times::统计出现次数 + count frequency::统计出现频率 + count weight::统计权重 + +Table Sort By Key::通过键值排序 + major::主类 + key::键值 + minor::副类 + descend::降序 + +Selection::选择 + +Sel By C Name R Count::通过列名和行号选择 + fields::所选列 + all columns::选择所有列 + start rows::起始行 + end rows::结束行 + all rows::选择所有行 + +Signal::信号处理 + +Signal Uniform Filter::信号均匀滤波器 + size::尺寸 + +Chart::作图 + +Plot Chart::折线图 + title::名称 + select fields::选择作图区域 + line width::线宽 + grid::显示网格 + +Area Chart::面积图 + title::名称 + select fields::选择作图区域 + alpha::透明度 + stacked::层叠显示 + grid::显示网格 + +Bar Chart::条形图 + title::名称 + select fields::选择作图区域 + horizon::水平显示 + stacked::层叠 + grid::显示网格 + +Box Chart::箱形图 + title::名称 + select fields::选择作图区域 + by::通过 + group::组别 + horizontal::水平显示 + grid::显示网格 + +Hist Chart::直方图 + title::名称 + select fields::选择作图区域 + bins::直方图区间个数 + alpha::透明度 + orientation::显示方向 + stacked::层叠显示 + draw every columns in one::所有列在一张图中绘制 + grid::显示网格 + +Pie Chart::饼图 + title::名称 + select fields::选择作图区域 + +Scatter Chart::散点图 + title::名称 + x data::x轴数据 + y data::y轴数据 + size::尺寸 + pix::像素数 + radius column::半径按照此列 + optional::可选 + alpha::透明度 + color::颜色 + color column::颜色按照此列 + color map::颜色映射 + grid::显示网格 \ No newline at end of file diff --git a/imagepy/lang/Chinese/plugins/window.dic b/imagepy/lang/Chinese/plugins/window.dic new file mode 100644 index 00000000..efde03c5 --- /dev/null +++ b/imagepy/lang/Chinese/plugins/window.dic @@ -0,0 +1,57 @@ +Windows Style::窗口样式 + Shown in ImageJ style when next setup!::下次启动将会以ImageJ风格展示 + Shown in ImagePy style when next setup!::下次启动将会以ImagePy风格展示 + +Pay Tribute To ImageJ::ImageJ布局风格 + +Elegant ImagePy Style::ImagePy布局风格 + +Kill Image::关闭图像 + name::图像名称 + close all images::关闭所有图像 + OK::确认 + cancel::取消 + Help::帮助 + +Kill Table::关闭表格 + name::表格名称 + close all tables::关闭所有表格 + +Widgets::控件列表 + +Toolbar::工具栏 + +Tables Window::表格窗口 + +Macros Recorder::宏记录器 + +Command Line::命令行 + +Plugin List View::插件列表 + +Plugin Tree View::插件树浏览 + +Tool Tree View::工具树形图 + +Develop Tool Sute::开发工具集 + Plugin Tree View::插件树浏览 + Tool Tree View::工具树浏览 + Plugin List View::插件列表 + Shortcut Editor::快捷键编辑器 + Macros Recorder::宏录制器 + Command Line::命令行 + Search::搜索 + Name::名称 + Author::作者 + Version::版本 + Status::状态 + installed::已安装 + only installed::只显示已安装 + Install::安装 + Update::更新 + Remove::移除 + Plugin Information::插件信息 + [SourceCode]::[查看源码] + Tool Information::插件信息 + [SourceCode]::[查看源码] + Search::搜索 \ No newline at end of file diff --git a/imagepy/lang/Chinese/tools/standard.dic b/imagepy/lang/Chinese/tools/standard.dic new file mode 100644 index 00000000..e569d867 --- /dev/null +++ b/imagepy/lang/Chinese/tools/standard.dic @@ -0,0 +1,4 @@ +Color Picker::取色器 + front::前景色 + back::背景色 + color::颜色 \ No newline at end of file diff --git a/imagepy/lang/Chinese/tools/tools.dic b/imagepy/lang/Chinese/tools/tools.dic new file mode 100644 index 00000000..5133a177 --- /dev/null +++ b/imagepy/lang/Chinese/tools/tools.dic @@ -0,0 +1,11 @@ +Draw::绘图 + +Transform::变换 + +Stack::序列 + +Measure::测量 + +Network::拓扑 + +Toolkit3D::三维 \ No newline at end of file diff --git a/imagepy/lang/Chinese/widgets/widgets.dic b/imagepy/lang/Chinese/widgets/widgets.dic new file mode 100644 index 00000000..a62632c0 --- /dev/null +++ b/imagepy/lang/Chinese/widgets/widgets.dic @@ -0,0 +1,18 @@ +Curve Adjust::曲线调整 + apply::刷新 + clear::清除 + reset::重置 + invert::反向 + +Histogram::直方图 + min-max::极限 + slice::单张 + stack::序列 + +Channels RGB::通道 + stack::序列 + +Navigator::导航栏 + Apply::刷新 + Fit::适应屏幕 + Normal::原始大小 \ No newline at end of file diff --git a/imagepy/lang/English/english.dic b/imagepy/lang/English/english.dic new file mode 100644 index 00000000..3bdfed2f --- /dev/null +++ b/imagepy/lang/English/english.dic @@ -0,0 +1 @@ +Nothing::English need nothing to be translated! \ No newline at end of file diff --git a/imagepy/menus/Analysis/Pixel Cluster/__init__.py b/imagepy/menus/Analysis/Pixel Cluster/__init__.py new file mode 100644 index 00000000..c0a689da --- /dev/null +++ b/imagepy/menus/Analysis/Pixel Cluster/__init__.py @@ -0,0 +1 @@ +catlog = ['cluster_plgs', '-', 'statistic_plgs','-','kmeans_plgs'] diff --git a/imagepy/menus/Analysis/Pixel Cluster/cluster_plgs.py b/imagepy/menus/Analysis/Pixel Cluster/cluster_plgs.py new file mode 100644 index 00000000..c8dfd95e --- /dev/null +++ b/imagepy/menus/Analysis/Pixel Cluster/cluster_plgs.py @@ -0,0 +1,167 @@ +import scipy.ndimage as ndimg +import numpy as np +from sciapp.action import Filter, Simple +import pandas as pd + +def colorselect(img, pts, k, usecov=True): + pts = img[pts].T + mean = pts.mean(axis=-1) + cov = np.cov(pts) + dif = img-mean + dis2 = (dif**2).sum(axis=2) + if not usecov: + return dis20: mark['body'][i] = layer + if para['center']: dt.append([round(i.centroid[1]*k,1) for i in ls]) dt.append([round(i.centroid[0]*k,1) for i in ls]) @@ -112,13 +93,13 @@ def run(self, ips, imgs, para = None): dt.append([round(i.orientation*k, 1) for i in ls]) data.extend(list(zip(*dt))) - ips.mark = Mark(mark) - IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-region') + ips.mark = mark2shp(mark if para['slice'] else mark['body'][0]) + self.app.show_table(pd.DataFrame(data, columns=titles), ips.title+'-region') # center, area, l, extent, cov class RegionFilter(Filter): title = 'Geometry Filter' - note = ['8-bit', '16-bit', 'auto_msk', 'auto_snap','preview'] + note = ['8-bit', '16-bit', 'int', 'auto_msk', 'auto_snap','preview'] para = {'con':'4-connect', 'inv':False, 'area':0, 'l':0, 'holes':0, 'solid':0, 'e':0, 'front':255, 'back':100} view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'), (bool, 'inv', 'invert'), @@ -131,12 +112,11 @@ class RegionFilter(Filter): (float, 'solid', (-1, 1,), 1, 'solidity', 'ratio'), (float, 'e', (-100,100), 1, 'eccentricity', 'ratio')] - #process def run(self, ips, snap, img, para = None): k, unit = ips.unit strc = generate_binary_structure(2, 1 if para['con']=='4-connect' else 2) - lab, n = label(snap==0 if para['inv'] else snap, strc, output=np.uint16) + lab, n = label(snap==0 if para['inv'] else snap, strc, output=np.uint32) idx = (np.ones(n+1)*(0 if para['inv'] else para['front'])).astype(np.uint8) ls = regionprops(lab) @@ -179,4 +159,43 @@ def run(self, ips, snap, img, para = None): idx[0] = para['front'] if para['inv'] else 0 img[:] = idx[lab] -plgs = [RegionCounter, RegionFilter] \ No newline at end of file + +# center, area, l, extent, cov +class PropertyMarker(Filter): + title = 'Property Marker' + note = ['8-bit', '16-bit', 'auto_msk', 'auto_snap','preview'] + para = {'con':'4-connect', 'pro':'area', 'cm':'gray'} + view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'), + (list, 'pro', ['area', 'perimeter', 'solid', 'eccentricity'], str, 'property', ''), + ('cmap', 'cm', 'color map')] + + def load(self, ips): + self.lut = ips.lut + return True + + def cancel(self, ips): + ips.lut = self.lut + Filter.cancel(self, ips) + + #process + def run(self, ips, snap, img, para = None): + strc = generate_binary_structure(2, 1 if para['con']=='4-connect' else 2) + + lab, n = label(snap, strc, output=np.uint32) + idx = (np.zeros(n+1)).astype(np.uint8) + ls = regionprops(lab) + + if para['pro'] == 'area': ps = [i.area for i in ls] + if para['pro'] == 'perimeter': ps = [i.perimeter for i in ls] + if para['pro'] == 'solid': ps = [i.solidity for i in ls] + if para['pro'] == 'eccentricity': ps = [i.major_axis_length/i.minor_axis_length for i in ls] + + ps = np.array(ps) + if ps.max() != ps.min(): + ps = (ps - ps.min()) / (ps.max() - ps.min()) + else: ps = ps / ps.max() + idx[1:] = ps * 245 + 10 + img[:] = idx[lab] + ips.lut = ColorManager.get(para['cm']) + +plgs = [RegionCounter, RegionFilter, PropertyMarker] \ No newline at end of file diff --git a/imagepy/menus/Analysis/Region Analysis/statistic_plgs.py b/imagepy/menus/Analysis/Region Analysis/statistic_plgs.py index f2084f41..d7144a20 100644 --- a/imagepy/menus/Analysis/Region Analysis/statistic_plgs.py +++ b/imagepy/menus/Analysis/Region Analysis/statistic_plgs.py @@ -5,50 +5,21 @@ """ import numpy as np from scipy import ndimage -import wx - -from imagepy import IPy -from imagepy.core.engine import Simple, Filter -from imagepy.core.manager import ImageManager -from imagepy.core.roi.pointroi import PointRoi +from sciapp.action import Simple, Filter +from sciapp.object import mark2shp import pandas as pd -class Mark: - def __init__(self, data): - self.data = data - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((255,255,0), width=1, style=wx.SOLID)) - dc.SetTextForeground((255,255,0)) - font = wx.Font(8, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - - dc.SetFont(font) - data = self.data[0 if len(self.data)==0 else key['cur']] - - pos = [f(*(i[0][1], i[0][0])) for i in data] - for i in pos:dc.DrawCircle(i[0], i[1], 2) - - txts = ['id={}'.format(i) for i in range(len(data))] - dc.DrawTextList(txts, pos) - - if data[0][1]==None:return - lt = [f(*(i[1][1], i[1][0])) for i in data] - rb = [f(*(i[1][3], i[1][2])) for i in data] - rects = [(x1,y1,x2-x1,y2-y1) for (x1,y1),(x2,y2) in zip(*(lt,rb))] - dc.DrawRectangleList(rects, brushes = wx.Brush((0,0,0), wx.BRUSHSTYLE_TRANSPARENT)) - - class RegionStatistic(Simple): title = 'Intensity Analysis' - note = ['8-bit', '16-bit'] + note = ['8-bit', '16-bit', 'int'] para = {'con':'8-connect','inten':None, 'slice':False, 'max':True, 'min':True,'mean':False, - 'center':True, 'var':False,'std':False,'sum':False, 'extent':False} + 'center':True, 'var':False,'std':False,'sum':False, 'extent':False, 'labeled':False} - view = [('img', 'intensity', 'inten', ''), + view = [('img', 'inten', 'intensity', ''), (list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'), (bool, 'slice', 'slice'), + (bool, 'labeled', 'has labeled'), ('lab', None, '========= indecate ========='), (bool, 'center', 'center'), (bool, 'extent', 'extent'), @@ -60,7 +31,7 @@ class RegionStatistic(Simple): #process def run(self, ips, imgs, para = None): - inten = ImageManager.get(para['inten']) + inten = self.app.get_img(para['inten']) if not para['slice']: imgs = [inten.img] msks = [ips.img] @@ -69,7 +40,7 @@ def run(self, ips, imgs, para = None): imgs = inten.imgs if len(msks)==1: msks *= len(imgs) - buf = imgs[0].astype(np.uint16) + buf = imgs[0].astype(np.uint32) strc = ndimage.generate_binary_structure(2, 1 if para['con']=='4-connect' else 2) idct = ['Max','Min','Mean','Variance','Standard','Sum'] key = {'Max':'max','Min':'min','Mean':'mean', @@ -80,22 +51,25 @@ def run(self, ips, imgs, para = None): if para['extent']: titles.extend(['Min-Y','Min-X','Max-Y','Max-X']) titles.extend(idct) k = ips.unit[0] - data, mark = [], [] + data, mark = [],{'type':'layers', 'body':{}} + # data,mark=[],[] for i in range(len(imgs)): - n = ndimage.label(msks[i], strc, output=buf) + if para['labeled']: + n, buf[:] = msks[i].max(), msks[i] + else: n = ndimage.label(msks[i], strc, output=buf) index = range(1, n+1) dt = [] if para['slice']:dt.append([i]*n) dt.append(range(n)) - xy = ndimage.center_of_mass(imgs[i], buf, index) + xy = ndimage.center_of_mass(buf, buf, index) xy = np.array(xy).round(2).T if para['center']:dt.extend([xy[1]*k, xy[0]*k]) boxs = [None] * n if para['extent']: boxs = ndimage.find_objects(buf) - boxs = [(i[0].start, i[1].start, i[0].stop, i[1].stop) for i in boxs] + boxs = [( i[1].start, i[0].start, i[1].stop-i[1].start,i[0].stop-i[0].start) for i in boxs] for j in (0,1,2,3): dt.append([i[j]*k for i in boxs]) if para['max']:dt.append(ndimage.maximum(imgs[i], buf, index).round(2)) @@ -104,42 +78,25 @@ def run(self, ips, imgs, para = None): if para['var']:dt.append(ndimage.variance(imgs[i], buf, index).round(2)) if para['std']:dt.append(ndimage.standard_deviation(imgs[i], buf, index).round(2)) if para['sum']:dt.append(ndimage.sum(imgs[i], buf, index).round(2)) + + layer = {'type':'layer', 'body':[]} + xy=np.int0(xy).T - mark.append([(center, cov) for center,cov in zip(xy.T, boxs)]) - data.extend(list(zip(*dt))) - - IPy.show_table(pd.DataFrame(data, columns=titles), inten.title+'-region statistic') - inten.mark = Mark(mark) - inten.update = True - -class RGMark: - def __init__(self, data): - self.xy, self.msk = data - - def draw(self, dc, f, **key): - dc.SetTextForeground((255,255,0)) - font = wx.Font(8, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - dc.SetFont(font) - - dc.SetPen(wx.Pen((0,255,0), width=1, style=wx.SOLID)) - dc.SetBrush(wx.Brush((0,255,0))) - pos = [f(*(i[1], i[0])) for i in self.xy[self.msk]] - for i in pos:dc.DrawCircle(int(i[0]), int(i[1]), 2) - - - dc.SetPen(wx.Pen((255,0,0), width=1, style=wx.SOLID)) - dc.SetBrush(wx.Brush((255,0,0))) - pos = [f(*(i[1], i[0])) for i in self.xy[~self.msk]] - for i in pos:dc.DrawCircle(int(i[0]), int(i[1]), 2) - + texts = [(i[1],i[0])+('id=%d'%n,) for i,n in zip(xy,range(len(xy)))] + layer['body'].append({'type':'texts', 'body':texts}) + if para['extent']: layer['body'].append({'type':'rectangles', 'body':boxs}) + mark['body'][i] = layer + data.extend(list(zip(*dt))) + self.app.show_table(pd.DataFrame(data, columns=titles), inten.title+'-pixels') + inten.mark = mark2shp(mark) + inten.update() class IntensityFilter(Filter): title = 'Intensity Filter' - note = ['8-bit', '16-bit', 'auto_msk', 'auto_snap', 'not_slice', 'preview'] + note = ['8-bit', '16-bit', 'int', 'auto_msk', 'auto_snap', 'not_slice', 'preview'] para = {'con':'4-connect', 'inten':None, 'max':0, 'min':0, 'mean':0, 'std':0, 'sum':0, 'front':255, 'back':0} - view = [('img', 'intensity', 'inten', ''), + view = [('img', 'inten', 'intensity', ''), (list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'), ('lab', None, 'Filter: "+" means >=, "-" means <'), (int, 'front', (0, 255), 0, 'front color', ''), @@ -152,12 +109,12 @@ class IntensityFilter(Filter): #process def run(self, ips, snap, img, para = None): - intenimg = ImageManager.get(para['inten']).img + intenimg = self.app.get_img(para['inten']).img strc = ndimage.generate_binary_structure(2, 1 if para['con']=='4-connect' else 2) buf, n = ndimage.label(snap, strc, output=np.uint32) index = range(1, n+1) idx = (np.ones(n+1)*para['front']).astype(np.uint8) - msk = np.ones(n, dtype=np.bool) + msk = np.ones(n, dtype='bool') if para['mean']>0: msk *= ndimage.mean(intenimg, buf, index)>=para['mean'] if para['mean']<0: msk *= ndimage.mean(intenimg, buf, index)<-para['mean'] @@ -170,7 +127,6 @@ def run(self, ips, snap, img, para = None): if para['std']>0: msk *= ndimage.standard_deviation(intenimg, buf, index)>=para['std'] if para['std']<0: msk *= ndimage.standard_deviation(intenimg, buf, index)<-para['std'] - xy = ndimage.center_of_mass(intenimg, buf, index) xy = np.array(xy).round(2).T @@ -178,7 +134,11 @@ def run(self, ips, snap, img, para = None): idx[0] = 0 img[:] = idx[buf] - ImageManager.get(para['inten']).mark = RGMark((xy.T, msk)) - ImageManager.get(para['inten']).update = True + red_pts = {'type':'points', 'body':xy[::-1].T[~msk], 'color':(255,0,0)} + green_pts = {'type':'points', 'body':xy[::-1].T[msk], 'color':(0,255,0)} + + self.app.get_img(para['inten']).mark = mark2shp( + {'type':'layer', 'body':[red_pts, green_pts]}) + self.app.get_img(para['inten']).update() plgs = [RegionStatistic, IntensityFilter] \ No newline at end of file diff --git a/imagepy/menus/Analysis/Skeleton Network/graph_plgs.py b/imagepy/menus/Analysis/Skeleton Network/graph_plgs.py index ac8ba2cb..9043adfc 100644 --- a/imagepy/menus/Analysis/Skeleton Network/graph_plgs.py +++ b/imagepy/menus/Analysis/Skeleton Network/graph_plgs.py @@ -1,28 +1,19 @@ -from imagepy.core.engine import Filter, Simple +from sciapp.action import Filter, Simple from imagepy.ipyalg.graph import sknw import numpy as np +from numpy.linalg import norm import networkx as nx, wx -from imagepy import IPy from numba import jit import pandas as pd +from sciapp.object import mark2shp -# build statistic sumerise cut edit -class Mark: - def __init__(self, graph): - self.graph = graph - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((255,255,0), width=3, style=wx.SOLID)) - dc.SetTextForeground((255,255,0)) - font = wx.Font(8, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - dc.SetFont(font) - - ids = self.graph.nodes() - pts = [self.graph.node[i]['o'] for i in ids] - pts = [f(i[1], i[0]) for i in pts] - dc.DrawPointList(pts) - dc.DrawTextList([str(i) for i in ids], pts) +def graph_mark(graph): + ids = graph.nodes() + pts = [graph.nodes[i]['o'] for i in ids] + pts = {'type':'points', 'body':[(i[1], i[0]) for i in pts]} + txt = [(a,b,str(c)) for (a,b),c in zip(pts['body'], ids)] + txt = {'type':'texts', 'body':txt} + return mark2shp({'type':'layer', 'body':[pts, txt]}) class BuildGraph(Filter): title = 'Build Graph' @@ -32,7 +23,7 @@ class BuildGraph(Filter): def run(self, ips, snap, img, para = None): ips.data = sknw.build_sknw(img, True) sknw.draw_graph(img, ips.data) - ips.mark = Mark(ips.data) + ips.mark = graph_mark(ips.data) class Statistic(Simple): title = 'Graph Statistic' @@ -40,7 +31,7 @@ class Statistic(Simple): def load(self, ips): if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); + self.app.alert("Please build graph!"); return False; return True; @@ -50,9 +41,11 @@ def run(self, ips, imgs, para = None): etitles = ['PartID', 'StartID', 'EndID', 'Length'] k, unit = ips.unit comid = 0 - for g in nx.connected_component_subgraphs(ips.data, False): + # for g in nx.connected_components(ips.data): + # for idx in g.nodes(): + for g in [ips.data.subgraph(c).copy() for c in nx.connected_components(ips.data)]: for idx in g.nodes(): - o = g.node[idx]['o'] + o = g.nodes[idx]['o'] print(idx, g.degree(idx)) nodes.append([comid, idx, g.degree(idx), round(o[1]*k,2), round(o[0]*k,2)]) for (s, e) in g.edges(): @@ -61,8 +54,8 @@ def run(self, ips, imgs, para = None): edges.append([comid, s, e, round(eds[i]['weight']*k, 2)]) comid += 1 - IPy.show_table(pd.DataFrame(nodes, columns=ntitles), ips.title+'-nodes') - IPy.show_table(pd.DataFrame(edges, columns=etitles), ips.title+'-edges') + self.app.show_table(pd.DataFrame(nodes, columns=ntitles), ips.title+'-nodes') + self.app.show_table(pd.DataFrame(edges, columns=etitles), ips.title+'-edges') class Sumerise(Simple): title = 'Graph Summarise' @@ -73,7 +66,7 @@ class Sumerise(Simple): def load(self, ips): if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); + self.app.alert("Please build graph!"); return False; return True; @@ -81,7 +74,7 @@ def run(self, ips, imgs, para = None): titles = ['PartID', 'Noeds', 'Edges', 'TotalLength', 'Density', 'AveConnect'] k, unit = ips.unit - gs = nx.connected_component_subgraphs(ips.data, False) if para['parts'] else [ips.data] + gs = [ips.data.subgraph(c).copy() for c in nx.connected_components(ips.data)] if para['parts'] else [ips.data] comid, datas = 0, [] for g in gs: sl = 0 @@ -90,7 +83,9 @@ def run(self, ips, imgs, para = None): datas.append([comid, g.number_of_nodes(), g.number_of_edges(), round(sl*k, 2), round(nx.density(g), 2), round(nx.average_node_connectivity(g),2)][1-para['parts']:]) comid += 1 - IPy.show_table(pd.DataFrame(datas, columns=titles[1-para['parts']:]), ips.title+'-graph') + # print('======datas=========', datas) + # print('======columns=========', titles[1-para['parts']:]) + self.app.show_table(pd.DataFrame(datas, columns=titles[1-para['parts']:]), ips.title+'-graph') class CutBranch(Filter): title = 'Cut Branch' @@ -102,18 +97,19 @@ class CutBranch(Filter): def load(self, ips): if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); + self.app.alert("Please build graph!"); return False; + self.buf = ips.data return True; def run(self, ips, snap, img, para = None): - g = ips.data.copy() + g = ips.data = self.buf.copy() k, unit = ips.unit while True: rm = [] for i in g.nodes(): if g.degree(i)!=1:continue - s,e = g.edges(i)[0] + s,e = list(g.edges(i))[0] if g[s][e][0]['weight']*k<=para['lim']: rm.append(i) g.remove_nodes_from(rm) @@ -121,24 +117,60 @@ def run(self, ips, snap, img, para = None): img *= 0 sknw.draw_graph(img, g) + def cancel(self, ips): + if 'auto_snap' in self.note: + ips.swap() + ips.update() + ips.data = self.buf + class RemoveIsolate(Filter): title = 'Remove Isolate Node' - note = ['all'] + note = ['all', 'not_slice', 'not_channel', 'auto_snap'] def load(self, ips): if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); + self.app.alert("Please build graph!"); return False; return True; def run(self, ips, snap, img, para = None): g = ips.data - for n in g.nodes(): + for n in list(g.nodes()): if len(g[n])==0: g.remove_node(n) img *= 0 sknw.draw_graph(img, g) + ips.mark = graph_mark(ips.data) + +class Remove2Node(Simple): + title = 'Remove 2Path Node' + note = ['all'] + + def load(self, ips): + if not isinstance(ips.data, nx.MultiGraph): + self.app.alert("Please build graph!"); + return False; + return True; -@jit + def run(self, ips, imgs, para = None): + g = ips.data + for n in list(g.nodes()): + if len(g[n])!=2 or n in g[n]: continue + (k1, e1), (k2, e2) = g[n].items() + if isinstance(g, nx.MultiGraph): + if len(e1)!=1 or len(e2)!=1: continue + e1, e2 = e1[0], e2[0] + l1, l2 = e1['pts'], e2['pts'] + d1 = norm(l1[0]-g.nodes[n]['o']) > norm(l1[-1]-g.nodes[n]['o']) + d2 = norm(l2[0]-g.nodes[n]['o']) < norm(l2[-1]-g.nodes[n]['o']) + pts = np.vstack((l1[::[-1,1][d1]], l2[::[-1,1][d2]])) + l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum() + g.remove_node(n) + g.add_edge(k1, k2, pts=pts, weight=l) + ips.img[:] = 0 + sknw.draw_graph(ips.img, g) + ips.mark = graph_mark(ips.data) + +@jit(nopython=True) def floodfill(img, x, y): buf = np.zeros((131072,2), dtype=np.uint16) color = img[int(y), int(x)] @@ -165,10 +197,10 @@ def floodfill(img, x, y): class CutROI(Filter): title = 'Cut By ROI' - note = ['8-bit', 'not_slice', 'not_channel', 'auto_snap', 'preview'] + note = ['8-bit', 'req_roi', 'not_slice', 'not_channel', 'auto_snap', 'preview'] def run(self, ips, snap, img, para = None): - msk = ips.get_msk(3) * (img>0) + msk = ips.mask(3) * (img>0) r,c = np.where(msk) for x,y in zip(c,r): if img[y,x]>0: @@ -184,7 +216,7 @@ class ShortestPath(Simple): def load(self, ips): if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); + self.app.alert("Please build graph!"); return False; return True; @@ -197,11 +229,13 @@ def run(self, ips, imgs, para = None): pts = sorted([(i['weight'], i['pts']) for i in ps]) paths.append(((s,e), pts[0])) sknw.draw_graph(ips.img, ips.data) + for i in paths: ips.img[i[1][1][:,0], i[1][1][:,1]] = 255 - IPy.write('%s-%s:%.4f'%(i[0][0], i[0][1], i[1][0]), 'ShortestPath') - IPy.write('Nodes:%s, Length:%.4f'%(len(nodes), sum([i[1][0] for i in paths])), 'ShortestPath') + + data = [(a[0], a[1], b[0]) for a,b in paths] + self.app.show_table(pd.DataFrame(data, columns=['from','to','l']), 'shortest-path') -plgs = [BuildGraph, Statistic, Sumerise, '-', RemoveIsolate, CutBranch, CutROI, '-', ShortestPath] \ No newline at end of file +plgs = [BuildGraph, Statistic, Sumerise, '-', RemoveIsolate, Remove2Node, CutBranch, CutROI, '-', ShortestPath] \ No newline at end of file diff --git a/imagepy/menus/Analysis/Tables/__init__.py b/imagepy/menus/Analysis/Tables/__init__.py deleted file mode 100644 index 9461a9a1..00000000 --- a/imagepy/menus/Analysis/Tables/__init__.py +++ /dev/null @@ -1 +0,0 @@ -catlog = ['savetable_plg', '-', 'operate_plg'] \ No newline at end of file diff --git a/imagepy/menus/Analysis/Tables/operate_plg.py b/imagepy/menus/Analysis/Tables/operate_plg.py deleted file mode 100644 index 674df575..00000000 --- a/imagepy/menus/Analysis/Tables/operate_plg.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Dec 27 12:04:18 2016 -@author: yxl -""" -from imagepy import IPy -from imagepy.core.engine import Free - -class Join(Free): - title = 'Join Tables *' - - def run(self, para = None): - IPy.alert('join two tabl with special field, not implemented!') - -class AddField(Free): - title = 'Add Field *' - - def run(self, para = None): - IPy.alert('add ont field to the table, not implemented!') - -class Frequence(Free): - title = 'Frequence Field *' - - def run(self, para = None): - IPy.alert('merge the same value and count frequence, not implemented!') - -plgs = [Join, AddField, Frequence] \ No newline at end of file diff --git a/imagepy/menus/Analysis/Tables/savetable_plg.py b/imagepy/menus/Analysis/Tables/savetable_plg.py deleted file mode 100644 index 4373ed9b..00000000 --- a/imagepy/menus/Analysis/Tables/savetable_plg.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Dec 27 11:30:24 2016 -@author: yxl -""" -import wx -from imagepy import IPy, root_dir -from imagepy.core.engine import Free -from imagepy.core.manager import TableManager - -class Csv(Free): - title = 'Save Table As CSV' - #para = {'tab': None, 'path':root_dir} - para = {'tab': None, 'path':'./'} - - def load(self): - n = len(TableManager.get_titles()) - if n>0:return True - IPy.alert('No table opened!') - return False - - def show(self): - self.view = [('tab', 'Table', 'tab', '')] - rst = IPy.getpara('Import sequence', self.view, self.para) - if rst!=wx.ID_OK:return rst - filt = 'CSV files (*.csv)|*.csv' - return IPy.getpath('Import sequence', filt, self.para) - - def run(self, para = None): - table = TableManager.get(para['tab']) - table.save_tab(para['path'], ',') - -class Tab(Free): - title = 'Save Table As Tab' - #para = {'tab': None, 'path':root_dir} - para = {'tab': None, 'path':'./'} - - def load(self): - n = len(TableManager.get_titles()) - if n>0:return True - IPy.alert('No table opened!') - return False - - def show(self): - self.view = [('tab', 'Table', 'tab', '')] - rst = IPy.getpara('Import sequence', self.view, self.para) - if rst!=wx.ID_OK:return rst - filt = 'TXT files (*.txt)|*.txt' - return IPy.getpath('Import sequence', filt, self.para) - - def run(self, para = None): - table = TableManager.get(para['tab']) - table.save_tab(para['path'], '\t') - -plgs = [Csv, Tab] \ No newline at end of file diff --git a/imagepy/menus/Analysis/__init__.py b/imagepy/menus/Analysis/__init__.py index 2450fc20..dd942c52 100644 --- a/imagepy/menus/Analysis/__init__.py +++ b/imagepy/menus/Analysis/__init__.py @@ -1 +1 @@ -catlog = ['label_plg', '-', 'statistic_plg', '-', 'Region Analysis', '3D Analysis', 'Skeleton Network', '-', 'Tables'] \ No newline at end of file +catlog = ['label_plg', '-', 'statistic_plg', '-', 'Pixel Cluster', 'Region Analysis', '3D Analysis', 'Skeleton Network'] diff --git a/imagepy/menus/Analysis/label_plg.py b/imagepy/menus/Analysis/label_plg.py index 20c37759..6de9d348 100644 --- a/imagepy/menus/Analysis/label_plg.py +++ b/imagepy/menus/Analysis/label_plg.py @@ -5,38 +5,75 @@ """ import numpy as np from scipy.ndimage import label, generate_binary_structure +from skimage.segmentation import find_boundaries +from sciapp.action import Filter, Simple +from imagepy.ipyalg.graph import connect, render +from sciapp.object import Image -from imagepy import IPy -from imagepy.core.engine import Filter -from imagepy.ui.canvasframe import CanvasFrame - -class Plugin(Filter): +class Label(Simple): title = 'Label Image' - note = ['8-bit', 'not_slice', 'preview'] - - para = {'thr':128, 'con':'4-Connect'} - view = [('slide', 'thr', (0,255), 0, 'Threshold'), - (list, 'con', ['4-Connect','8-Connect'], str, 'Structure', 'connect')] - - def load(self, ips): - self.lut = ips.lut - ips.lut = self.lut.copy() - return True - - def preview(self, ips, para): - ips.lut[:] = self.lut - ips.lut[para['thr']:] = [255,0,0] - ips.update = 'pix' - - def run(self, ips, snap, img, para = None): - if para == None: para = self.para - ips.lut = self.lut - msk = img>para['thr'] - con = 1 if para['con']=='4-Connect' else 2 - strc = generate_binary_structure(2, con) - msk = label(msk, strc, output = np.int16) - - IPy.show_img([msk[0]], ips.title+'-label') + note = ['8-bit', '16-bit'] + para = {'slice':False, 'con':'4-Connect'} + view = [(list, 'con', ['4-Connect','8-Connect'], str, 'Structure', 'connect'), + (bool, 'slice', 'slice')] + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + labels = [] + for i in range(len(imgs)): + self.progress(i, len(imgs)) + con = 1 if para['con']=='4-Connect' else 2 + strc = generate_binary_structure(2, con) + lab, n = label(imgs[i], strc, output = np.int32) + labels.append(lab) + self.app.show_img(labels, ips.title+'-label') + +class Boundaries(Simple): + title = 'Mark Boundaries' + note = ['8-bit', '16-bit','int'] + para = {'slice':False, 'mode':'outer', 'con':'4-Connect'} + view = [(list, 'con', ['4-Connect','8-Connect'], str, 'structure', 'connect'), + (list, 'mode', ['thick', 'inner', 'outer', 'subpixel'], str, 'mode', ''), + (bool, 'slice', 'slice')] - \ No newline at end of file + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + labels = [] + for i in range(len(imgs)): + self.progress(i, len(imgs)) + con = 1 if para['con']=='4-Connect' else 2 + bound = find_boundaries(imgs[i], con, para['mode']) + bound.dtype = np.uint8 + bound *= 255 + labels.append(bound) + self.app.show_img(labels, ips.title+'-boundary') + +class Render(Simple): + title = 'Label Render' + note = ['8-bit', '16-bit', 'int'] + para = {'slice':False, 'con':'8-Connect', 'colors':6, 'back':False} + view = [(list, 'con', ['4-Connect','8-Connect'], str, 'structure', 'connect'), + (int, 'colors', (4, 255), 0, 'colors', 'n'), + (bool, 'back', 'background'), + (bool, 'slice', 'slice')] + + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + labels = [] + for i in range(len(imgs)): + self.progress(i, len(imgs)) + con = 1 if para['con']=='4-Connect' else 2 + idx = connect.connect_graph(imgs[i], con, para['back']) + idx = connect.mapidx(idx) + cmap = render.node_render(idx, para['colors'], 10) + + lut = np.ones(imgs[i].max()+1, dtype=np.uint8) + lut[0] = 0 + for j in cmap: lut[j] = cmap[j] + labels.append(lut[imgs[i]]) + + ips = Image(labels, ips.title+'-render') + ips.range = (0, para['colors']) + self.app.show_img(ips) + +plgs = [Label, Boundaries, Render] \ No newline at end of file diff --git a/imagepy/menus/Analysis/statistic_plg.py b/imagepy/menus/Analysis/statistic_plg.py index ae7ddd3d..5f1d63fb 100644 --- a/imagepy/menus/Analysis/statistic_plg.py +++ b/imagepy/menus/Analysis/statistic_plg.py @@ -3,15 +3,16 @@ Created on Mon Dec 26 20:34:59 2016 @author: yxl """ -from imagepy import IPy, root_dir +from imagepy import root_dir import wx, numpy as np, os -from imagepy.core.engine import Simple -from imagepy.core.roi import PointRoi -from imagepy.core.manager import ImageManager, WindowsManager -from imagepy.ui.widgets import HistCanvas -from wx.lib.pubsub import pub +from sciapp.action import Filter,Simple +from pubsub import pub import pandas as pd +from skimage.graph import route_through_array +from sciapp.object import mark2shp, Circles +from sciwx.widgets.histpanel import HistPanel + class HistogramFrame(wx.Frame): def __init__(self, parent, title, hist): wx.Frame.__init__(self, parent, title=title, style = wx.DEFAULT_DIALOG_STYLE) @@ -27,8 +28,8 @@ def rgb(self, hist): sizer = wx.BoxSizer( wx.VERTICAL ) rgb = ['Red', 'Green', 'Blue'] for i in (0,1,2): - histc = HistCanvas(panel) - histc.set_hist(hist[i]) + histc = HistPanel(panel) + histc.SetValue(hist[i]) txt = wx.StaticText( panel, wx.ID_ANY, 'Channel:'+ rgb[i], wx.DefaultPosition, wx.DefaultSize, 0 ) sizer.Add( txt, 0, wx.LEFT|wx.RIGHT, 8 ) sizer.Add( histc, 0, wx.LEFT|wx.RIGHT, 8 ) @@ -45,8 +46,8 @@ def gray(self, hist): back = wx.BoxSizer( wx.VERTICAL ) back.Add(panel, 1, wx.EXPAND) sizer = wx.BoxSizer( wx.VERTICAL ) - histc = HistCanvas(panel) - histc.set_hist(hist) + histc = HistPanel(panel) + histc.SetValue(hist) txt = wx.StaticText( panel, wx.ID_ANY, 'Channel:'+'Gray', wx.DefaultPosition, wx.DefaultSize, 0 ) sizer.Add( txt, 0, wx.LEFT|wx.RIGHT, 8 ) sizer.Add( histc, 0, wx.LEFT|wx.RIGHT, 8 ) @@ -58,7 +59,6 @@ def gray(self, hist): self.SetSizer(back) self.Fit() - def showhist(parent, title, hist): HistogramFrame(parent, title, hist).Show() @@ -68,18 +68,16 @@ def show_hist(parent, title, hist): class Histogram(Simple): title = 'Histogram' - note = ['8-bit', '16-bit', 'rgb'] + note = ['all'] def run(self, ips, imgs, para = None): - msk = ips.get_msk('in') - if ips.imgtype == 'rgb': - img = ips.img if msk is None else ips.img[msk] - hist = [np.histogram(img[:,i], np.arange(257))[0] for i in (0,1,2)] - else: - img = ips.lookup() if msk is None else ips.lookup()[msk] - hist = np.histogram(img, np.arange(257))[0] - show_hist(WindowsManager.get(), ips.title+'-Histogram', hist) - + msk = ips.mask('in') + rg = np.linspace(*ips.range, 257) + img = ips.img if msk is None else ips.img[msk] + if ips.channels == 3: + hist = [np.histogram(img.ravel()[i::3], rg)[0] for i in (0,1,2)] + else: hist = np.histogram(img,rg)[0] + show_hist(self.app, ips.title+'-Histogram', hist) class Frequence(Simple): title = 'Frequence' @@ -87,19 +85,17 @@ class Frequence(Simple): para = {'fre':True, 'slice':False} view = [(bool, 'fre', 'count frequence'), - (bool, 'slice', 'each slices')] + (bool, 'slice', 'slice')] def run(self, ips, imgs, para = None): if not para['slice']: imgs = [ips.img] data = [] - msk = ips.get_msk('in') + msk = ips.mask('in') for i in range(len(imgs)): img = imgs[i] if msk is None else imgs[i][msk] - maxv = img.max() - if maxv==0:continue - ct = np.histogram(img, maxv, [1,maxv+1])[0] + ct = np.bincount(img.ravel()) #np.histogram(img, maxv+1, [0,maxv])[0] titles = ['slice','value','count'] - dt = [[i]*len(ct), list(range(maxv+1)), ct] + dt = [np.ones(len(ct), dtype=np.uint32)+i, np.arange(len(ct)), ct] if not para['slice']: titles, dt = titles[1:], dt[1:] if self.para['fre']: @@ -109,27 +105,27 @@ def run(self, ips, imgs, para = None): dt = list(zip(*dt)) data.extend(dt) - IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-histogram') + self.app.show_table(pd.DataFrame(data, columns=titles), ips.title+'-histogram') class Statistic(Simple): title = 'Pixel Statistic' - note = ['8-bit', '16-bit', 'int', 'float'] + note = ['all'] para = {'max':True, 'min':True,'mean':False,'var':False,'std':False,'slice':False} view = [(bool, 'max', 'max'), (bool, 'min', 'min'), (bool, 'mean', 'mean'), - (bool, 'variance', 'var'), - (bool, 'standard', 'std'), + (bool, 'var', 'variance'), + (bool, 'std', 'standard'), (bool, 'slice', 'slice')] def count(self, img, para): rst = [] if para['max']: rst.append(img.max()) if para['min']: rst.append(img.min()) - if para['mean']: rst.append(img.mean().round(2)) - if para['var']: rst.append(img.var().round(2)) - if para['std']: rst.append(img.std().round(2)) + if para['mean']: rst.append(img.mean()) + if para['var']: rst.append(img.var()) + if para['std']: rst.append(img.std()) return rst def run(self, ips, imgs, para = None): @@ -139,33 +135,12 @@ def run(self, ips, imgs, para = None): if not self.para['slice']:imgs = [ips.img] data = [] - msk = ips.get_msk('in') + msk = ips.mask('in') for n in range(len(imgs)): img = imgs[n] if msk is None else imgs[n][msk] data.append(self.count(img, para)) self.progress(n, len(imgs)) - IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-statistic') - -class Mark: - def __init__(self, data): - self.data = data - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((255,255,0), width=1, style=wx.SOLID)) - dc.SetTextForeground((255,255,0)) - font = wx.Font(8, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - - dc.SetFont(font) - data = self.data[0 if len(self.data)==1 else key['cur']] - - for i in range(len(data)): - pos = f(*(data[i][0], data[i][1])) - dc.SetBrush(wx.Brush((255,255,255))) - dc.DrawCircle(pos[0], pos[1], 2) - dc.SetBrush(wx.Brush((0,0,0), wx.BRUSHSTYLE_TRANSPARENT)) - dc.DrawCircle(pos[0], pos[1], data[i][2]*key['k']) - dc.DrawText('id={}, r={}'.format(i, data[i][2]), pos[0], pos[1]) + self.app.show_table(pd.DataFrame(data, columns=titles), ips.title+'-statistic') class PointsValue(Simple): title = 'Points Value' @@ -176,9 +151,8 @@ class PointsValue(Simple): (bool, 'slice', 'slice')] def load(self, ips): - if not isinstance(ips.roi, PointRoi): - IPy.alert('a PointRoi needed!') - return False + if ips.roi.roitype != 'point' and ips.roi.roitype != 'points': + return self.app.alert('a PointRoi needed!') return True @@ -188,17 +162,58 @@ def run(self, ips, imgs, para = None): if not para['slice']: imgs = [ips.img] titles = titles[1:] - data, mark = [], [] - pts = np.array(ips.roi.body).round().astype(np.int) + data = [] + pts = np.vstack([i.body.reshape(-1,2) for i in ips.roi.body]) + layers = {'type':'layers', 'body':{}} for n in range(len(imgs)): - xs, ys = (pts.T*k).round(2) + xs, ys = (pts.T[:2]*k).round(2).astype(np.int16) vs = imgs[n][ys, xs] cont = ([n]*len(vs), xs, ys, vs.round(2)) if not para['slice']: cont = cont[1:] data.extend(zip(*cont)) - if para['buf']:mark.append(list(zip(xs, ys, vs.round(2)))) + if para['buf']: + layers['body'][n] = {'type':'circles', 'body':list(zip(xs, ys, vs.round(2)))} self.progress(n, len(imgs)) - IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-points') - if para['buf']:ips.mark = Mark(mark) + self.app.show_table(pd.DataFrame(data, columns=titles), ips.title+'-points') + if para['buf']:ips.mark = mark2shp(layers) -plgs = [Frequence, Statistic, Histogram, PointsValue] \ No newline at end of file +class ShortRoute(Filter): + title = 'Shortest Route' + note = ['auto_snap','8-bit', '16-bit','int', 'float', 'req_roi', '2int', 'preview'] + + para = {'fully connected':True, 'lcost':0, 'max':False, 'geometric':True, 'type':'white line'} + view = [(float, 'lcost', (0, 1e5), 3, 'step', 'cost'), + (bool, 'max', 'max cost'), + (bool, 'fully connected', 'fully connected'), + (bool, 'geometric', 'geometric'), + (list, 'type', ['white line', 'gray line', 'white line on ori'], str, 'output', '')] + + def load(self, ips): + if ips.roi.roitype != 'line': + return self.app.alert('LineRoi are needed!') + return True + + def run(self, ips, snap, img, para = None): + img[:] = snap + if para['max']: img *= -1 + np.add(img, para['lcost']-img.min(), casting='unsafe', out=img) + + minv, maxv = ips.range + routes = [] + for line in ips.roi.body: + pts = np.array(list(zip(line.body[:-1], line.body[1:]))) + for p0, p1 in pts[:,:,::-1].astype(int): + indices, weight = route_through_array(img, p0, p1) + routes.append(indices) + rs, cs = np.vstack(routes).T + if para['type']=='white line on ori': + img[:] = snap + img[rs,cs] = maxv + elif para['type']=='gray line': + img[:] = minv + img[rs,cs] = snap[rs,cs] + elif para['type']=='white line': + img[:] = minv + img[rs,cs] = maxv + +plgs = [Frequence, Statistic, Histogram, PointsValue,ShortRoute] \ No newline at end of file diff --git a/imagepy/menus/Edit/edit_plg.py b/imagepy/menus/Edit/edit_plg.py index 12729635..aa48b3c3 100644 --- a/imagepy/menus/Edit/edit_plg.py +++ b/imagepy/menus/Edit/edit_plg.py @@ -4,12 +4,10 @@ @author: yxl """ import numpy as np -from imagepy.core.pixel import bliter -from imagepy.core.engine import Simple, Tool, Filter -from imagepy.core.roi.rectangleroi import RectangleRoi -from imagepy.core.manager import ClipBoardManager, ColorManager +from sciapp.action import Simple, Filter +from sciapp.action import ImageTool -class PasteMove(Tool): +class PasteMove(ImageTool): def __init__(self): self.moving = True self.cx, self.cy = 0, 0 @@ -26,7 +24,7 @@ def mouse_down(self, ips, x, y, btn, **key): def mouse_up(self, ips, x, y, btn, **key): if self.moving == True: self.moving = False - ci = ClipBoardManager.img + ci = self.app.manager('xxxxxx') img = ips.img #ips.roi.draged(ci.shape[1]/2,ci.shape[0]/2, ips.size[1]/2, ips.size[0]/2, True) #ips.roi = IPy.clipboard[1].affine(np.eye(2), ((np.array(ips.size)-ci.shape[:2])[::-1]/2)) @@ -34,7 +32,7 @@ def mouse_up(self, ips, x, y, btn, **key): bliter.blit(img, ci, y, x) ips.reset(True) - ips.update = 'pix' + ips.update() def mouse_move(self, ips, x, y, btn, **key): if self.moving==True and btn!=None: @@ -42,7 +40,7 @@ def mouse_move(self, ips, x, y, btn, **key): self.cx += x-self.ox self.cy += y-self.oy self.ox, self.oy = x, y - ips.update = True + ips.update() class Paste(Simple): title = 'Paste' @@ -69,14 +67,20 @@ class Clear(Filter): note = ['req_roi', 'all', 'auto_snap', 'not_channel'] def run(self, ips, snap, img, para=None): - img[ips.get_msk()] = ColorManager.get_back(snap.ndim==2) + color = self.app.manager('color').get('back') + color = np.array([color]).ravel() + if ips.channels != len(color): color = color.mean() + img[ips.mask()] = color class ClearOut(Filter): title = 'Clear Out' note = ['req_roi', 'all', 'auto_snap', 'not_channel'] def run(self, ips, snap, img, para=None): - img[ips.get_msk('out')] = ColorManager.get_back(snap.ndim==2) + color = self.app.manager('color').get('back') + color = np.array([color]).ravel() + if ips.channels != len(color): color = color.mean() + img[ips.mask('out')] = color class Copy(Simple): title = 'Copy' @@ -99,14 +103,20 @@ class Sketch(Filter): view = [(int, 'width', (0,30), 0, 'width', 'pix')] def run(self, ips, snap, img, para = None): - img[ips.get_msk(para['width'])] = ColorManager.get_front(snap.ndim==2) + color = self.app.manager('color').get('front') + color = np.array([color]).ravel() + if ips.channels != len(color): color = color.mean() + img[ips.mask(para['width'])] = color class Fill(Filter): title = 'Fill' note = ['req_roi', 'all', 'auto_snap', 'not_channel'] def run(self, ips, snap, img, para=None): - img[ips.get_msk()] = ColorManager.get_front(snap.ndim==2) + color = self.app.manager('color').get('front') + color = np.array([color]).ravel() + if ips.channels != len(color): color = color.mean() + img[ips.mask()] = color class Undo(Simple): title = 'Undo' diff --git a/imagepy/menus/File/BMP/bmp_plgs.py b/imagepy/menus/File/BMP/bmp_plgs.py index 21795a0e..45b3a0f8 100644 --- a/imagepy/menus/File/BMP/bmp_plgs.py +++ b/imagepy/menus/File/BMP/bmp_plgs.py @@ -1,16 +1,17 @@ -from imagepy.core.util import fileio -from scipy.misc import imread, imsave -from imagepy.core.manager import ReaderManager, WriterManager +from sciapp.action import dataio +from skimage.io import imread, imsave -ReaderManager.add('bmp', imread) -WriterManager.add('bmp', imsave) +dataio.ReaderManager.add('bmp', imread, 'img') +dataio.WriterManager.add('bmp', imsave, 'img') -class OpenFile(fileio.Reader): +class OpenFile(dataio.Reader): title = 'BMP Open' + tag = 'img' filt = ['BMP'] -class SaveFile(fileio.Writer): +class SaveFile(dataio.ImageWriter): title = 'BMP Save' + tag = 'img' filt = ['BMP'] plgs = [OpenFile, SaveFile] \ No newline at end of file diff --git a/imagepy/data/__init__.py b/imagepy/menus/File/DAT/__init__.py similarity index 100% rename from imagepy/data/__init__.py rename to imagepy/menus/File/DAT/__init__.py diff --git a/imagepy/menus/File/DAT/dat_plgs.py b/imagepy/menus/File/DAT/dat_plgs.py new file mode 100644 index 00000000..ed302d52 --- /dev/null +++ b/imagepy/menus/File/DAT/dat_plgs.py @@ -0,0 +1,23 @@ +from sciapp.action import dataio +import numpy as np + +def imread(path): + return np.loadtxt(path,dtype=float) + +def imsave(path,img): + np.savetxt(path,img) + +dataio.ReaderManager.add('dat', imread, 'img') +dataio.WriterManager.add('dat', imsave, 'img') + +class OpenFile(dataio.Reader): + title = 'DAT Open' + tag = 'img' + filt = ['DAT'] + +class SaveFile(dataio.ImageWriter): + title = 'DAT Save' + tag = 'img' + filt = ['DAT'] + +plgs = [OpenFile,SaveFile] \ No newline at end of file diff --git a/imagepy/menus/File/DICOM/dicom_plgs.py b/imagepy/menus/File/DICOM/dicom_plgs.py index 6eefbf14..50e75233 100644 --- a/imagepy/menus/File/DICOM/dicom_plgs.py +++ b/imagepy/menus/File/DICOM/dicom_plgs.py @@ -1,16 +1,14 @@ -from imagepy.core.util import fileio +from sciapp.action import dataio import pydicom -from imagepy.core.manager import ReaderManager, WriterManager - - def imread(path): return pydicom.read_file(path, force=True).pixel_array -ReaderManager.add('dcm', imread) +dataio.ReaderManager.add('dcm', imread, 'img') -class OpenFile(fileio.Reader): +class OpenFile(dataio.Reader): title = 'DCM Open' filt = ['DCM'] + tag = 'img' plgs = [OpenFile] \ No newline at end of file diff --git a/imagepy/menus/File/Export/sequence_plg.py b/imagepy/menus/File/Export/sequence_plg.py index 333da6ec..e2c7e949 100644 --- a/imagepy/menus/File/Export/sequence_plg.py +++ b/imagepy/menus/File/Export/sequence_plg.py @@ -4,34 +4,29 @@ @author: yxl """ -import wx -from scipy.misc import imsave -from imagepy.core.engine import Simple -from imagepy.core.manager import WriterManager, ViewerManager -from imagepy import IPy, root_dir +from skimage.io import imsave +from sciapp.action import Simple +from sciapp.action import dataio class Plugin(Simple): title = 'Save Sequence' note = ['all'] para = {'path':'','name':'','format':'png'} #para = {'path':'./','name':'','format':'png'} + view = [('path', 'path', '', 'folder', 'path'), + (str, 'name', 'name', 'number'), + (None)] def load(self, ips): - self.view = [(str, 'name', 'Name', ''), - (list, 'format',list(sorted(WriterManager.all())), str, 'Format', '')] + names = [i[0] for i in dataio.WriterManager.gets(tag='img')] + self.view[2] = (list, 'format',list(sorted(names)), str, 'format', '') return True - def show(self): - self.para['name'] = self.ips.title - rst = IPy.get_para('Save sequence', self.view, self.para) - if rst!=wx.ID_OK:return rst - return IPy.getdir('Save sequence', '', self.para) - #process def run(self, ips, imgs, para = None): path = para['path']+'/'+para['name'] - write = WriterManager.get(para['format']) - print(path) + write = dataio.WriterManager.get(para['format']) + for i in range(len(imgs)): self.progress(i, len(imgs)) name = '%s-%.4d.%s'%(path,i,para['format']) diff --git a/imagepy/menus/File/GIF/__init__.py b/imagepy/menus/File/GIF/__init__.py index c236cbd6..e69de29b 100644 --- a/imagepy/menus/File/GIF/__init__.py +++ b/imagepy/menus/File/GIF/__init__.py @@ -1 +0,0 @@ -catlog = ['gif_plgs', 'animate_plgs'] \ No newline at end of file diff --git a/imagepy/menus/File/GIF/animate_plgs.py b/imagepy/menus/File/GIF/animate_plgs.py deleted file mode 100644 index d6d60e08..00000000 --- a/imagepy/menus/File/GIF/animate_plgs.py +++ /dev/null @@ -1,38 +0,0 @@ -from imagepy.core.util import fileio -from imagepy import IPy -import os -import numpy as np -from PIL import Image, ImageSequence - - - -class SaveAnimate(fileio.Writer): - title = 'GIF Animate Save' - filt = ['GIF'] - note = ['8-bit', 'rgb', 'stack'] - - #process - def run(self, ips, imgs, para = None): - imgs = [Image.fromarray(i) for i in imgs] - imgs[0].save(para['path'], save_all=True, loop=0, append_images=imgs[1:]) - -class OpenAnimate(fileio.Reader): - title = 'GIF Animate Open' - filt = ['GIF'] - note = ['8-bit', 'rgb', 'stack'] - - #process - def run(self, para = None): - #imgs = readGif(para['path']) - - imgs = Image.open(para['path']) - imgs = ImageSequence.Iterator(imgs) - imgs = [np.array(i.convert('RGB')) for i in imgs] - for i in range(len(imgs)): - if imgs[i].ndim==3 and imgs[i].shape[2]>3: - imgs[i] = imgs[i][:,:,:3].copy() - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - IPy.show_img(imgs, fn) - -plgs = [OpenAnimate, SaveAnimate] \ No newline at end of file diff --git a/imagepy/menus/File/GIF/gif_plgs.py b/imagepy/menus/File/GIF/gif_plgs.py index 7e48cd12..03b5ba8c 100644 --- a/imagepy/menus/File/GIF/gif_plgs.py +++ b/imagepy/menus/File/GIF/gif_plgs.py @@ -1,16 +1,40 @@ -from imagepy.core.util import fileio -from scipy.misc import imread, imsave -from imagepy.core.manager import ReaderManager, WriterManager +from sciapp.action import dataio +from sciapp.action import Simple +from skimage.io import imread, imsave +import imageio -ReaderManager.add('gif', imread) -WriterManager.add('fig', imsave) +dataio.ReaderManager.add('gif', imread, 'img') +dataio.WriterManager.add('gif', imsave, 'img') +dataio.ReaderManager.add('gif', imageio.mimread, 'imgs') -class OpenFile(fileio.Reader): +class OpenFile(dataio.Reader): title = 'GIF Open' + tag = 'img' filt = ['GIF'] -class SaveFile(fileio.Writer): +class SaveFile(dataio.ImageWriter): title = 'GIF Save' + tag = 'img' filt = ['GIF'] -plgs = [OpenFile, SaveFile] \ No newline at end of file +class SaveAnimate(Simple): + title = 'GIF Animate Save' + note = ['all'] + filt = ['GIF'] + para={'path':'', 'dur':0.2} + view = [(int, 'dur', (0.01, 10), 2, 'duration', 's')] + + def load(self, ips): + self.para['path'] = self.app.get_path('Save..', self.filt, 'save', '') + return not self.para['path'] is None + + def run(self, ips, imgs, para = None): + imageio.mimsave(para['path'], imgs, 'gif', duration = para['dur']) + +class OpenAnimate(dataio.Reader): + title = 'GIF Animate Open' + filt = ['GIF'] + tag = 'imgs' + note = ['8-bit', 'rgb', 'stack'] + +plgs = [OpenFile, SaveFile, '-', OpenAnimate, SaveAnimate] \ No newline at end of file diff --git a/imagepy/menus/File/Import/raw_plg.py b/imagepy/menus/File/Import/raw_plg.py index 99c5504a..c1930ea0 100644 --- a/imagepy/menus/File/Import/raw_plg.py +++ b/imagepy/menus/File/Import/raw_plg.py @@ -1,9 +1,8 @@ -import wx,os,sys +import os,sys import numpy as np import io# urllib2 urllib.request, urllib.error, urllib.parse -from scipy.misc import imread -from imagepy import IPy -from imagepy.core.engine import Free +from skimage.io import imread +from sciapp.action import Free class Plugin(Free): title = 'Open Raw' @@ -16,9 +15,9 @@ class Plugin(Free): (list, 'c', [1,3], int, 'channel', '')] def load(self): - filt = 'RAW files (*.raw)|*.raw' - rst = IPy.getpath('Open..', filt, 'open', self.para) - if rst!=None:return True + filt = 'raw' + self.para['path'] = self.app.get_path('Open..', filt, 'open', '') + return not self.para['path'] is None #process def run(self, para = None): @@ -28,12 +27,7 @@ def run(self, para = None): img = np.fromfile(para['path'], dtype=para['type']) sp = (para['h'], para['w'], para['c'])[:2 if para['c']==1 else 3] if img.size != para['h']*para['w']*para['c']: - IPy.alert('raw data error!') + self.app.alert('raw data error!') return img.shape = sp - IPy.show_img([img], fn) - -if __name__ == '__main__': - print(Plugin.title) - app = wx.App(False) - Plugin().run() \ No newline at end of file + self.app.show_img([img], fn) \ No newline at end of file diff --git a/imagepy/menus/File/Import/roi_plg.py b/imagepy/menus/File/Import/roi_plg.py new file mode 100644 index 00000000..1f43be26 --- /dev/null +++ b/imagepy/menus/File/Import/roi_plg.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Created on 12/21/2018 +@author: BioinfoTongLI +""" +import numpy as np +import read_roi +from sciapp.action import Free +from skimage.draw import polygon, ellipse + +class Plugin(Free): + """load_ij_roi: use read_roi and th pass to shapely objects""" + title = 'Import Rois from IJ' + + para = {'path': '', 'name': 'Undefined', 'width': 512, 'height': 512} + + view = [(str, 'name', 'name', ''), + (int, 'width', (1, 3000), 0, 'width', 'pix'), + (int, 'height', (1, 3000), 0, 'height', 'pix')] + + def load(self): + filt = 'zip' + self.para['path'] = self.app.get_path(self.title, filt, 'open', self.para['name']) + return not self.para['path'] is None + + def run(self, para=None): + ls = read_roi.read_roi_zip(para['path']) + img = np.zeros((para['height'], para['width']), dtype=np.int32) + for i in ls: + current_roi = ls[i] + roi_type = current_roi["type"] + if roi_type is "freehand": + rs, cs = polygon(ls[i]['y'], ls[i]['x'], img.shape) + elif roi_type is "oval": + rs, cs = ellipse(current_roi["top"]+current_roi["height"]/2, + current_roi["left"]+current_roi["width"]/2, + current_roi["height"]/2, + current_roi["width"]/2) + try: + ind = int(i) + except Exception: + ind = int(i.split("-")[-1]) + img[rs, cs] = ind + self.app.show_img([img], para['name']) diff --git a/imagepy/menus/File/Import/sequence_plg.py b/imagepy/menus/File/Import/sequence_plg.py index c2d667cc..4ee5cdb9 100644 --- a/imagepy/menus/File/Import/sequence_plg.py +++ b/imagepy/menus/File/Import/sequence_plg.py @@ -4,27 +4,25 @@ @author: yxl """ -from imagepy.core.util import fileio -from scipy.misc import imread -from imagepy.core.manager import ReaderManager, ViewerManager -from imagepy.core.engine import Free -from imagepy import IPy +from sciapp.action import dataio +from skimage.io import imread +from sciapp.action import Free from glob import glob -import wx, os +import os.path as osp class Plugin(Free): title = 'Import Sequence' para = {'path':'', 'start':0, 'end':0, 'step':1, 'title':'sequence'} def load(self): - self.filt = sorted(ReaderManager.all()) + self.filt = dataio.ReaderManager.names() return True def show(self): filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) - rst = IPy.getpath('Import sequence', filt, 'open', self.para) - if rst!=wx.ID_OK:return rst - + rst = self.app.get_path('Import sequence', self.filt, 'open') + if rst is None: return rst + self.para['path'] = rst files = self.getfiles(self.para['path']) nfs = len(files) self.para['end'] = nfs-1 @@ -32,10 +30,10 @@ def show(self): (int, 'start', (0, nfs-1), 0, 'Start', '0~{}'.format(nfs-1)), (int, 'end', (0, nfs-1), 0, 'End', '0~{}'.format(nfs-1)), (int, 'step', (0, nfs-1), 0, 'Step', '')] - return IPy.get_para('Import sequence', self.view, self.para) + return self.app.show_para('Import sequence', self.para, self.view) def getfiles(self, name): - p,f = os.path.split(name) + p,f = osp.split(name) s = p+'/*.'+name.split('.')[-1] return glob(s) @@ -52,22 +50,16 @@ def readimgs(self, names, read, shape, dtype): #process def run(self, para = None): - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - read = ReaderManager.get(fe[1:]) - view = ViewerManager.get(fe[1:]) - - try: - img = read(para['path']) - except: - IPy.alert('unknown img format!') - return - + fp, fn = osp.split(para['path']) + fn, fe = osp.splitext(fn) + read = dataio.ReaderManager.get(name=fe[1:]) + try: img = read(para['path']) + except: return self.app.alert('unknown img format!') files = self.getfiles(para['path']) files.sort() imgs = self.readimgs(files[para['start']:para['end']+1:para['step']], read, img.shape, img.dtype) - view(imgs, para['title']) + self.app.show('imgs', imgs, para['title']) if __name__ == '__main__': print(Plugin.title) diff --git a/imagepy/menus/File/JPG/jpg_plgs.py b/imagepy/menus/File/JPG/jpg_plgs.py index e3fdb924..ff977ff4 100644 --- a/imagepy/menus/File/JPG/jpg_plgs.py +++ b/imagepy/menus/File/JPG/jpg_plgs.py @@ -1,16 +1,19 @@ -from imagepy.core.util import fileio -from scipy.misc import imread, imsave -from imagepy.core.manager import ReaderManager, WriterManager +from sciapp.action import dataio +from imageio import imread, imsave -ReaderManager.add('jpg', imread) -WriterManager.add('jpg', imsave) +dataio.ReaderManager.add('jpg', imread, 'img') +dataio.WriterManager.add('jpg', imsave, 'img') +dataio.ReaderManager.add('jpeg', imread, 'img') +dataio.WriterManager.add('jpeg', imsave, 'img') -class OpenFile(fileio.Reader): +class OpenFile(dataio.Reader): title = 'JPG Open' - filt = ['JPG'] + tag = 'img' + filt = ['JPG','JPEG'] -class SaveFile(fileio.Writer): +class SaveFile(dataio.ImageWriter): title = 'JPG Save' - filt = ['JPG'] + tag = 'img' + filt = ['JPG','JPEG'] plgs = [OpenFile, SaveFile] \ No newline at end of file diff --git a/imagepy/menus/File/MAT/__init__.py b/imagepy/menus/File/MAT/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/File/MAT/mat_plgs.py b/imagepy/menus/File/MAT/mat_plgs.py new file mode 100644 index 00000000..1dee31f1 --- /dev/null +++ b/imagepy/menus/File/MAT/mat_plgs.py @@ -0,0 +1,31 @@ +from sciapp.action import dataio +from scipy.io import savemat, loadmat +import os + +dataio.ReaderManager.add('mat', lambda path: loadmat(path)['img'], 'img') +dataio.WriterManager.add('mat', lambda path, img: savemat(path, {'img':img}), 'img') +dataio.ReaderManager.add('mat', lambda path: loadmat(path)['img'], 'imgs') +dataio.WriterManager.add('mat', lambda path, img: savemat(path, {'img':img}), 'imgs') + +class OpenFile(dataio.Reader): + title = 'Mat Open' + tag = 'img' + filt = ['Mat'] + +class SaveFile(dataio.ImageWriter): + title = 'Mat Save' + tag = 'img' + filt = ['Mat'] + +class Open3D(dataio.Reader): + title = 'Mat 3D Open' + tag = 'imgs' + filt = ['Mat'] + +class Save3D(dataio.ImageWriter): + title = 'Mat 3D Save' + tag = 'imgs' + filt = ['Mat'] + note = ['8-bit', 'rgb', 'stack'] + +plgs = [OpenFile, SaveFile, '-', Open3D, Save3D] \ No newline at end of file diff --git a/imagepy/menus/File/MarkDown/md_plg.py b/imagepy/menus/File/MarkDown/md_plg.py new file mode 100644 index 00000000..2874249c --- /dev/null +++ b/imagepy/menus/File/MarkDown/md_plg.py @@ -0,0 +1,11 @@ +from sciapp.action import dataio + +def read(path): + with open(path) as f: return f.read() + +dataio.ReaderManager.add('md', read, 'md') + +class Plugin(dataio.Reader): + title = 'MarkDown Open' + tag = 'md' + filt = 'MD' \ No newline at end of file diff --git a/imagepy/menus/File/Numpy/__init__.py b/imagepy/menus/File/Numpy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/File/Numpy/ndarray_plgs.py b/imagepy/menus/File/Numpy/ndarray_plgs.py new file mode 100644 index 00000000..79ee8a38 --- /dev/null +++ b/imagepy/menus/File/Numpy/ndarray_plgs.py @@ -0,0 +1,31 @@ +from sciapp.action import dataio +import numpy as np +import os + +dataio.ReaderManager.add('npy', np.load, 'img') +dataio.WriterManager.add('npy', np.save, 'img') +dataio.ReaderManager.add('npy', np.load, 'imgs') +dataio.WriterManager.add('npy', np.save, 'imgs') + +class OpenFile(dataio.Reader): + title = 'Numpy Open' + tag = 'img' + filt = ['npy'] + +class SaveFile(dataio.ImageWriter): + title = 'Numpy Save' + tag = 'img' + filt = ['npy'] + +class Open3D(dataio.Reader): + title = 'Numpy 3D Open' + tag = 'imgs' + filt = ['npy'] + +class Save3D(dataio.ImageWriter): + title = 'Numpy 3D Save' + tag = 'imgs' + filt = ['npy'] + note = ['all', 'stack'] + +plgs = [OpenFile, SaveFile, '-', Open3D, Save3D] \ No newline at end of file diff --git a/imagepy/menus/File/Open Recent/recent_plgs.py b/imagepy/menus/File/Open Recent/recent_plgs.py deleted file mode 100644 index 03805c39..00000000 --- a/imagepy/menus/File/Open Recent/recent_plgs.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Dec 5 02:38:04 2016 -@author: yxl -""" -from imagepy.core import manager -from imagepy.core.engine import Macros -from imagepy import IPy -from imagepy.core.util import fileio - -plgs = fileio.rlist \ No newline at end of file diff --git a/imagepy/menus/File/Open Recent/recent_plgs_.py b/imagepy/menus/File/Open Recent/recent_plgs_.py new file mode 100644 index 00000000..87addb41 --- /dev/null +++ b/imagepy/menus/File/Open Recent/recent_plgs_.py @@ -0,0 +1,3 @@ +from sciapp.action import dataio + +# plgs = dataio.rlist \ No newline at end of file diff --git a/imagepy/menus/File/PNG/png_plgs.py b/imagepy/menus/File/PNG/png_plgs.py index 4e576b70..117eed06 100644 --- a/imagepy/menus/File/PNG/png_plgs.py +++ b/imagepy/menus/File/PNG/png_plgs.py @@ -1,16 +1,25 @@ -from imagepy.core.util import fileio -from scipy.misc import imread, imsave -from imagepy.core.manager import ReaderManager, WriterManager +from sciapp.action import dataio +from skimage.io import imread, imsave -ReaderManager.add('png', imread) -WriterManager.add('png', imsave) +def read_png(path): + img = imread(path) + if img.ndim==3 and img.shape[-1]==4: + msk = img[:,:,3] + img = img[:,:,:3].copy() + img[msk==0] = 255 + return img -class OpenFile(fileio.Reader): +dataio.ReaderManager.add('png', read_png, 'img') +dataio.WriterManager.add('png', imsave, 'img') + +class OpenFile(dataio.Reader): title = 'PNG Open' + tag = 'img' filt = ['PNG'] -class SaveFile(fileio.Writer): +class SaveFile(dataio.ImageWriter): title = 'PNG Save' + tag = 'img' filt = ['PNG'] plgs = [OpenFile, SaveFile] \ No newline at end of file diff --git a/imagepy/menus/File/Samples ImageJ/__init__.py b/imagepy/menus/File/Samples ImageJ/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/File/Samples ImageJ/ijsample_plgs.py b/imagepy/menus/File/Samples ImageJ/ijsample_plgs.py new file mode 100644 index 00000000..72a61e1d --- /dev/null +++ b/imagepy/menus/File/Samples ImageJ/ijsample_plgs.py @@ -0,0 +1,28 @@ +from skimage.io import imread +from urllib.request import urlopen +from io import BytesIO as StringIO + +from sciapp.action import Free + +class IJImg(Free): + def __init__(self, title, name): + self.title, self.name = title, name + + def __call__(self): return self + + def run(self, para = None): + try: + response = urlopen('http://imagej.net/images/'+self.name) + stream = StringIO(response.read()) + img = imread(stream) + self.app.show_img([img], self.title) + except Exception as e: + self.app.write('Open url failed!\tErrof:%s'%sys.exc_info()[1]) + +plgs = [IJImg(*i) for i in [('Leaf 36K', 'leaf.jpg'), ('Lena 68K', 'lena.jpg'), ('MRI Head 47K', 'mri.gif'), + ('AuPbSn 40 56K', 'AuPbSn40.jpg'), ('Blob 356K', 'blobs.gif'), ('Baboon 56K', 'baboon.jpg'), + ('Boats 25K', 'boats.gif'), ('Bridge 174K', 'bridge.gif'), ('Clown 14K', 'clown.jpg'), + ('Lymp 17K', 'lymp.tif'), ('M51 177K', 'm51.jpg'), ('FluorescentCells 400K', 'FluorescentCells.jpg'), + ('Microm 32K', 'microm.jpg'), ('SmartSEMSample 780K', 'SmartSEMSample.tif'), + ('NileBend 1.9M', 'NileBend.jpg'), ('Diatoms 60K', 'Diatoms.jpg'),('Tree Rings 48K', 'Tree_Rings.jpg'), + ('Cartwheel Galaxy 231K', 'Cartwheel_Galaxy.jpg'), ('Cell Colony 34K', 'Cell_Colony.jpg')]] \ No newline at end of file diff --git a/imagepy/menus/File/Samples Local/samples_plgs.py b/imagepy/menus/File/Samples Local/samples_plgs.py index fda7d84a..e5dd1dcb 100644 --- a/imagepy/menus/File/Samples Local/samples_plgs.py +++ b/imagepy/menus/File/Samples Local/samples_plgs.py @@ -1,5 +1,4 @@ -from imagepy import IPy -from imagepy.core.engine import Free +from sciapp.action import Free from skimage import data from scipy import misc import numpy as np @@ -13,14 +12,19 @@ def __init__(self, title): def run(self, para = None): img = self.data() - if img.dtype != np.uint8: - img = img.astype(np.uint8) - IPy.show_img([img], self.title) + if isinstance(img, tuple): + return self.app.show_img(list(img), self.title) + if img.dtype == 'bool': + img.dtype = np.uint8 + img *= 255 + self.app.show_img([img], self.title) - def __call__(self): - return self + def __call__(self): return self -datas = ['face', 'ascent', '-', 'page', 'astronaut', 'horse', 'camera', - 'hubble_deep_field', 'coins', 'immunohistochemistry', 'moon'] +datas = ['face', 'ascent', '-', 'binary_blobs', 'brick', 'astronaut', + 'camera', 'cell', 'checkerboard', 'chelsea', 'clock', 'coffee', 'coins', + 'colorwheel', 'grass', 'gravel', 'horse', 'hubble_deep_field', + 'immunohistochemistry', 'microaneurysms', 'moon', 'page', + 'text', 'retina', 'rocket', 'shepp_logan_phantom', 'stereo_motorcycle'] plgs = [i if i=='-' else Data(i) for i in datas] \ No newline at end of file diff --git a/imagepy/menus/File/TIF/__init__.py b/imagepy/menus/File/TIF/__init__.py index e77a53b9..e69de29b 100644 --- a/imagepy/menus/File/TIF/__init__.py +++ b/imagepy/menus/File/TIF/__init__.py @@ -1 +0,0 @@ -catlog = ['tif_plgs', '-', 'tif3d_plgs'] \ No newline at end of file diff --git a/imagepy/menus/File/TIF/tif3d_plgs.py b/imagepy/menus/File/TIF/tif3d_plgs.py deleted file mode 100644 index 739e3dee..00000000 --- a/imagepy/menus/File/TIF/tif3d_plgs.py +++ /dev/null @@ -1,26 +0,0 @@ -from skimage.io import imread, imsave -from imagepy.core.util import fileio -from imagepy import IPy -import os - -class Save(fileio.Writer): - title = 'TIF 3D Save' - filt = ['TIF'] - note = ['8-bit', 'rgb', 'stack'] - - #process - def run(self, ips, imgs, para = None): - imsave(para['path'], imgs) - -class Open(fileio.Reader): - title = 'TIF 3D Open' - filt = ['TIF'] - - #process - def run(self, para = None): - imgs = imread(para['path']).transpose(2,0,1) - fp, fn = os.path.split(para['path']) - fn, fe = os.path.splitext(fn) - IPy.show_img(imgs, fn) - -plgs = [Open, Save] \ No newline at end of file diff --git a/imagepy/menus/File/TIF/tif_plgs.py b/imagepy/menus/File/TIF/tif_plgs.py index c4b5fdf1..e9df8540 100644 --- a/imagepy/menus/File/TIF/tif_plgs.py +++ b/imagepy/menus/File/TIF/tif_plgs.py @@ -1,16 +1,33 @@ -from imagepy.core.util import fileio -from scipy.misc import imread, imsave -from imagepy.core.manager import ReaderManager, WriterManager +from sciapp.action import dataio +from skimage.io import imread, imsave -ReaderManager.add('tif', imread) -WriterManager.add('tif', imsave) +dataio.ReaderManager.add('tif', imread, 'imgs') +dataio.ReaderManager.add('tiff', imread, 'imgs') +dataio.WriterManager.add('tif', imsave, 'imgs') -class OpenFile(fileio.Reader): +dataio.ReaderManager.add('tif', imread, 'img') +dataio.ReaderManager.add('tiff', imread, 'img') +dataio.WriterManager.add('tif', imsave, 'img') + +class OpenTIF(dataio.Reader): title = 'TIF Open' - filt = ['TIF'] + tag = 'img' + filt = ['TIF', 'TIFF'] -class SaveFile(fileio.Writer): +class SaveTIF(dataio.ImageWriter): title = 'TIF Save' + tag = 'img' + filt = ['TIF'] + +class OpenTIFS(dataio.Reader): + title = 'TIF 3D Open' + tag = 'imgs' + filt = ['TIF', 'TIFF'] + +class SaveTIFS(dataio.ImageWriter): + title = 'TIF 3D Save' + tag = 'imgs' filt = ['TIF'] + note = ['all', 'stack3d'] -plgs = [OpenFile, SaveFile] \ No newline at end of file +plgs = [OpenTIF, SaveTIF, '-', OpenTIFS, SaveTIFS] \ No newline at end of file diff --git a/imagepy/menus/File/__init__.py b/imagepy/menus/File/__init__.py index 2921f7ec..1c4d8c47 100644 --- a/imagepy/menus/File/__init__.py +++ b/imagepy/menus/File/__init__.py @@ -1,3 +1,3 @@ ### TODO: Fixme! In this directory, many path should be corrected?! catlog = ['new_plg', '-', 'open_plg', 'save_plg', '-', 'Open Recent', 'Samples Local', 'Samples Online', - '-', 'Import', 'Export', '-', 'BMP', 'JPG', 'PNG', 'TIF', 'GIF','DICOM', '-', 'exit_plg'] \ No newline at end of file + 'Samples ImageJ', '-', 'Import', 'Export', '-', 'BMP', 'JPG', 'PNG', 'TIF', 'GIF', 'DICOM', 'DAT', 'Numpy', 'MAT', '-', 'MarkDown', '-', 'exit_plg'] \ No newline at end of file diff --git a/imagepy/menus/File/exit_plg.py b/imagepy/menus/File/exit_plg.py index 6ee9535e..c9f4a15f 100644 --- a/imagepy/menus/File/exit_plg.py +++ b/imagepy/menus/File/exit_plg.py @@ -3,11 +3,11 @@ Created on Mon Dec 5 05:43:50 2016 @author: yxl """ -from imagepy import IPy -from imagepy.core.engine import Free +from sciapp.action import Free class Plugin(Free): title = 'Exit' + asyn = False def run(self, para = None): - IPy.curapp.on_close(None) \ No newline at end of file + self.app.Close() \ No newline at end of file diff --git a/imagepy/menus/File/new_plg.py b/imagepy/menus/File/new_plg.py index daaffc5d..50c8b99d 100644 --- a/imagepy/menus/File/new_plg.py +++ b/imagepy/menus/File/new_plg.py @@ -4,21 +4,16 @@ @author: yxl """ - -import wx,os -from imagepy.ui.canvasframe import CanvasFrame +from sciapp.action import Free import numpy as np -from imagepy import IPy - -from imagepy.core.engine import Free class Plugin(Free): - title = 'New' + title = 'New Image' para = {'name':'Undefined','width':300, 'height':300, 'type':'8-bit','slice':1} view = [(str, 'name', 'name', ''), - (int, 'width', (1,2048), 0, 'width', 'pix'), - (int, 'height', (1,2048), 0, 'height', 'pix'), - (list, 'type', ['8-bit','RGB'], str, 'Type', ''), + (int, 'width', (1,10240), 0, 'width', 'pix'), + (int, 'height', (1,10240), 0, 'height', 'pix'), + (list, 'type', ['8-bit','RGB'], str, 'type', ''), (int, 'slice', (1,2048), 0, 'slice', '')] #process @@ -28,7 +23,7 @@ def run(self, para = None): slices = para['slice'] shape = (h,w,channels) if channels!=1 else (h,w) imgs = [np.zeros(shape, dtype=np.uint8) for i in range(slices)] - IPy.show_img(imgs, para['name']) + self.app.show_img(imgs, para['name']) if __name__ == '__main__': print(Plugin.title) diff --git a/imagepy/menus/File/open_plg.py b/imagepy/menus/File/open_plg.py index 37138c95..a3b34046 100644 --- a/imagepy/menus/File/open_plg.py +++ b/imagepy/menus/File/open_plg.py @@ -1,31 +1,25 @@ import wx,os,sys -from scipy.misc import imread +from skimage.io import imread -if sys.version_info[0]==2: - from urllib2 import urlopen - from cStringIO import StringIO -else: - from urllib.request import urlopen - from io import BytesIO as StringIO +from urllib.request import urlopen +from io import BytesIO as StringIO -from imagepy.core import manager -from imagepy import IPy -from imagepy.core.engine import Free -from imagepy.core.util import fileio -from imagepy.core.manager import ReaderManager +from sciapp.action import Free +from sciapp.action import dataio -class OpenFile(fileio.Reader): + +class OpenFile(dataio.Reader): title = 'Open' def load(self): - self.filt = sorted(ReaderManager.get(tag=None)) + self.filt = [i for i in sorted(dataio.ReaderManager.names())] return True class OpenUrl(Free): title = 'Open Url' para = {'url':'http://data.imagepy.org/testdata/yxdragon.jpg'} view = [('lab', None, 'Input the URL, eg. http://data.imagepy.org/testdata/yxdragon.jpg'), - (str, 'url', 'Url:', '')] + (str, 'url', 'Url', '')] def run(self, para = None): try: @@ -35,9 +29,10 @@ def run(self, para = None): ## TODO: Fixme! stream = StringIO(response.read()) img = imread(stream) - IPy.show_img([img], fn) + self.app.show_img([img], fn) except Exception as e: - IPy.write('Open url failed!\tErrof:%s'%sys.exc_info()[1]) + print(self.app) + self.app.show_txt('Open url failed!\tErrof:%s'%sys.exc_info()[1]) plgs = [OpenFile, OpenUrl] diff --git a/imagepy/menus/File/save_plg.py b/imagepy/menus/File/save_plg.py index a872cc0c..b72bc0ab 100644 --- a/imagepy/menus/File/save_plg.py +++ b/imagepy/menus/File/save_plg.py @@ -3,23 +3,21 @@ Created on Mon Dec 5 03:19:13 2016 @author: yxl """ -from imagepy.core.util import fileio -from imagepy import IPy, root_dir -from imagepy.core.manager import WriterManager, ImageManager, WindowsManager -from imagepy.core.engine import Simple +from sciapp.action import dataio +from sciapp.action import Simple -class SaveImage(fileio.Writer): +class SaveImage(dataio.ImageWriter): title = 'Save' def load(self, ips): - self.filt = sorted(WriterManager.all()) + self.filt = [i for i in sorted(dataio.WriterManager.names())] return True -class WindowCapture(fileio.Writer): +class WindowCapture(dataio.ImageWriter): title = 'Save With Mark' filt = ['PNG'] def run(self, ips, imgs, para = None): - WindowsManager.get().canvas.save_buffer(para['path']) + self.app.get_img_win().canvas.save_buffer(para['path']) plgs = [SaveImage, WindowCapture] \ No newline at end of file diff --git a/imagepy/menus/Help/Help_plgs.py b/imagepy/menus/Help/Help_plgs.py index 52eba731..b65eef41 100644 --- a/imagepy/menus/Help/Help_plgs.py +++ b/imagepy/menus/Help/Help_plgs.py @@ -3,26 +3,26 @@ Created on Sat Nov 19 14:05:12 2016 @author: yxl """ -from imagepy import IPy import webbrowser -from imagepy.core.engine import Free +from sciapp.action import Free ## TODO:Fixme! class About(Free): title = 'About' asyn = False + def run(self, para=None): - IPy.alert('ImagePy v0.2') + self.app.alert('ImagePy v0.2') class Topic(Free): title = 'Topic' - asyn = False + def run(self, para=None): webbrowser.open('http://www.imagepy.org/document') class Home(Free): title = 'Home Page' - asyn = False + def run(self, para=None): webbrowser.open('http://imagepy.org') diff --git a/imagepy/menus/Help/Language/language_plgs.py b/imagepy/menus/Help/Language/language_plgs.py index cee4cb79..241c7f16 100644 --- a/imagepy/menus/Help/Language/language_plgs.py +++ b/imagepy/menus/Help/Language/language_plgs.py @@ -1,50 +1,16 @@ -from imagepy import IPy -from imagepy.core.manager import ColorManager, LanguageManager -from imagepy.core.engine import Free +from sciapp.action import Free +from imagepy.app import ConfigManager, DictManager class Language(Free): def __init__(self, key): self.title = key asyn = False - #process def run(self, para = None): - LanguageManager.set(self.title) - IPy.curapp.reload_plugins() + ConfigManager.add('language', self.title) + self.app.load_all() def __call__(self): return self -plgs = [Language(i) for i in list(LanguageManager.langs.keys())] -plgs.insert(0, Language('English')) -plgs.append('-') - - -class NewLanguage(Free): - title = 'New Language' - para = {'name':'your language'} - view = [(str, 'name', 'name', '')] - - def run(self, para = None): - LanguageManager.newdic(para['name']) - LanguageManager.write() - -class UpdateDictionary(Free): - title = 'Update Dictionary' - - def run(self, para = None): - LanguageManager.add() - LanguageManager.write() - -class CleanDictionary(Free): - title = 'Clean Dictionary' - - def run(self, para = None): - LanguageManager.rm() - LanguageManager.write() - -plgs.extend([NewLanguage, UpdateDictionary, CleanDictionary]) - -if __name__ == '__main__': - print(list(ColorManager.luts.keys())) - \ No newline at end of file +plgs = [Language(i) for i in DictManager.get('language')] \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/__init__.py b/imagepy/menus/Image/Adjust/__init__.py index 843b06f2..af5b5b05 100644 --- a/imagepy/menus/Image/Adjust/__init__.py +++ b/imagepy/menus/Image/Adjust/__init__.py @@ -1 +1 @@ -catlog = ['threshold_plg','graystairs_plg','brightcons_plg','curve_plg','-','colorbalance_plg','colorstairs_plg','-','histogram_plgs'] \ No newline at end of file +catlog = ['threshold_plg','graystairs_plg','brightcons_plg','curve_plg','-','bleachCorrection_plg','enhanceContrast_plg','normalize_plg','-','colorbalance_plg','colorstairs_plg','-','histogram_plgs'] \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/bleachCorrection_plg.py b/imagepy/menus/Image/Adjust/bleachCorrection_plg.py new file mode 100644 index 00000000..3259e013 --- /dev/null +++ b/imagepy/menus/Image/Adjust/bleachCorrection_plg.py @@ -0,0 +1,90 @@ +""" +Created on Sun Jan 23 11:53:00 2020 +@author: weisong +""" +from sciapp.action import Simple +import numpy as np +from scipy.optimize import curve_fit +from skimage.exposure import histogram_matching +import matplotlib.pyplot as plt +import pandas as pd + +def copy(imgs): + if isinstance(imgs, list): + return [np.zeros_like(imgs[0])] + else: return np.zeros_like(imgs) + +def exponential_func(t, ref, k, offset): + return ref * np.exp(- k * t) + offset + +def simple_ratio(imgs, back=0, inplace=True, out=print): + if isinstance(back, int): back=imgs[back] + buf = imgs if inplace else copy(imgs) + z, (x, y) = len(imgs), imgs[0].shape + values, k0 = np.zeros(z), back.sum()/x/y + lim = 255 if imgs[0].dtype.type==np.uint8 else 65535 + for i in range(z): + values[i] = imgs[i].sum()/x/y + np.clip(imgs[i], 0, lim/(k0/values[i]), out=buf[i]) + np.multiply(buf[i], k0/values[i], out=buf[i], casting='unsafe') + out(i, z) + return buf, values, k0/values + +def exponential_fit(imgs, inplace=True, out=print): + buf = imgs if inplace else copy(imgs) + z, (x, y) = len(imgs), imgs[0].shape + intensity = [i.sum()/x/y for i in imgs] + popt, pcov = curve_fit(exponential_func, np.arange(z), intensity) + k0 = exponential_func(0, popt[0], popt[1], popt[2]) + rst = exponential_func(np.arange(z), popt[0], popt[1], popt[2]) + lim = 255 if imgs[0].dtype.type==np.uint8 else 65535 + for i in range(z): + np.clip(imgs[i], 0, lim/(rst[i]/k0), out=buf[i]) + np.multiply(buf[i], rst[i]/k0, out=buf[i], casting='unsafe') + out(i, z) + return buf, popt, intensity, rst + +def histogram_match(imgs, back=0, inplace=True, out=print): + if isinstance(back, int): back=imgs[back] + buf = imgs if inplace else copy(imgs) + z, (x, y) = len(imgs), imgs[0].shape + for i in range(z): + buf[i] = histogram_matching.match_histograms(imgs[i], back) + out(i, z) + return buf + +def plot(popt, intensity, fitresult): + t = np.arange(len(intensity)) + plt.plot(t, intensity,'r.',label='Experiment') + plt.plot(t, fitresult,'k',label= + 'Exponential fitted curve\n y=a*exp(-bx)+c\n a=%f\n b=%f\n c=%f'%tuple(popt)) + plt.title('Exponential fitted result') + plt.legend() + plt.show() + +def plot_after(popt, intensity, fitresult): + import wx + wx.CallAfter(plot, popt, intensity, fitresult) + +class Plugin(Simple): + title = 'Bleach Correction' + note = ['8-bit','16-bit','stack'] + para = {'method':'Simple Ratio', 'new':True} + view = [(list, 'method', ['Simple Ratio','Exponential Fit','Histogram Match'], + str, 'Correction Method',''), + (bool, 'new', 'show new window'), + ('lab', 'lab', 'Correct intensity based on your current slice!')] + + def run(self, ips, imgs, para = None): + if para['method'] == 'Simple Ratio': + rst, value, ratio = simple_ratio(imgs, ips.img, not para['new'], self.progress) + body = pd.DataFrame({'Mean value': value, 'Ratio':ratio}) + IPy.show_table(body, '%s-simple ratio'%ips.title) + if para['method'] == 'Exponential Fit': + rst, popt, intensity, fitrst = exponential_fit(imgs, not para['new'], self.progress) + plot_after(popt, intensity, fitrst) + body = {'Intensity':intensity, 'Fit':fitrst} + IPy.show_table(pd.DataFrame(body), '%s-exp fit'%ips.title) + if para['method'] == 'Histogram Match': + rst = histogram_match(imgs, ips.img, not para['new'], self.progress) + if para['new']: IPy.show_img(rst, '%s-corrected'%ips.title) \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/brightcons_plg.py b/imagepy/menus/Image/Adjust/brightcons_plg.py index 870c9340..0e293098 100644 --- a/imagepy/menus/Image/Adjust/brightcons_plg.py +++ b/imagepy/menus/Image/Adjust/brightcons_plg.py @@ -2,66 +2,38 @@ Created on Sun Nov 27 00:56:00 2016 @author: yxl """ - -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog -from imagepy.ui.widgets import HistCanvas - -class ThresholdDialog(ParaDialog): - def __init__(self, parent, title, lim): - ParaDialog.__init__(self, parent, title) - self.lim = lim - - def para_check(self, para, key): - mid = 128-para['bright']/(self.lim[1]-self.lim[0])*255 - length = 255/np.tan(para['contrast']/180.0*np.pi) - self.ctrl_dic['hist'].set_lim(mid-length/2, mid+length/2) - return True +from sciapp.action import Filter +#from imagepy.ui.widgets import HistCanvas class Plugin(Filter): title = 'Bright And Constract' note = ['all', 'auto_msk', 'auto_snap', 'not_channel', 'preview'] def load(self, ips): - hist = np.histogram(self.ips.lookup(),list(range(257)))[0] - if ips.imgtype in ('8-bit', 'rgb'): - self.arange = (0, 255) - self.para = {'bright':0, 'contrast':45} - self.view = [('hist', 'hist', hist), - ('slide', 'bright', (-100,100), 0, 'Brightness'), - ('slide', 'contrast', (1,89), 0, 'Contrast')] - if 'not_slice' in self.note: - self.note.remove('not_slice') + hist = ips.histogram(chans='all', step=512) + if ips.dtype == np.uint8: + self.para = {'b_c':(0, 45)} + self.view = [('hist', 'b_c', 'bc', hist, (-255, 255), 0)] else : - self.arange = minv, maxv = ips.img.min(), ips.img.max() - self.para = {'bright':np.mean(ips.range) - np.mean(self.arange), + self.rrange = minv, maxv = ips.img.min(), ips.img.max() + self.para = {'bright':np.mean(ips.range) - np.mean(self.range), 'contrast':round(np.arctan((maxv-minv)/(ips.range[1]-ips.range[0]))/np.pi*180)} - self.view = [('hist', 'hist', hist), - ('slide', 'bright', (-(maxv-minv)/2, (maxv-minv)/2), 10, 'Brightness'), - ('slide', 'contrast', (1,89), 0, 'Contrast')] - if not 'not_slice' in self.note: - self.note.append('not_slice') + self.view = [('hist', 'b_c', 'bc', hist, (-(maxv-minv)/2, (maxv-minv)/2), 10)] + self.lut = ips.lut return True - def show(self, temp=ThresholdDialog): - dialog = lambda win, title, lim = self.ips.range:temp(win, title, lim) - return Filter.show(self, dialog) - - #process def run(self, ips, snap, img, para = None): - if not ips.imgtype in ('8-bit', 'rgb'): - mid = (self.arange[0] + self.arange[1])/2 - para['bright'] - length = (self.arange[1] - self.arange[0])/np.tan(para['contrast']/180.0*np.pi) + bright, contrast = para['b_c'] + if not ips.dtype == np.uint8: + mid = (self.arange[0] + self.arange[1])/2 - bright + length = (self.arange[1] - self.arange[0])/np.tan(contrast/180.0*np.pi) ips.range = (mid-length/2, mid+length/2) return if para == None: para = self.para - mid = 128-para['bright'] - length = 255/np.tan(para['contrast']/180.0*np.pi) - print(255/np.tan(para['contrast']/180.0*np.pi)/2) - print(mid-length/2, mid+length/2) + mid = 128-bright + length = 255/np.tan(contrast/180.0*np.pi) img[:] = snap if mid-length/2>0: np.subtract(img, mid-length/2, out=img, casting='unsafe') diff --git a/imagepy/menus/Image/Adjust/colorbalance_plg.py b/imagepy/menus/Image/Adjust/colorbalance_plg.py index ef98aa48..3669b0c1 100644 --- a/imagepy/menus/Image/Adjust/colorbalance_plg.py +++ b/imagepy/menus/Image/Adjust/colorbalance_plg.py @@ -1,58 +1,25 @@ # -*- coding: utf-8 -*- -""" -Created on Fri Nov 18 22:56:50 2016 - -@author: yxl -""" -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog -from imagepy.ui.widgets import HistCanvas +from sciapp.action import Filter -class BalanceDialog(ParaDialog): - def para_check(self, para, key): - mid = 128-para['b_red'] - length = 255/np.tan(para['c_red']/180.0*np.pi) - self.ctrl_dic['red'].set_lim(mid-length/2, mid+length/2) - mid = 128-para['b_green'] - length = 255/np.tan(para['c_green']/180.0*np.pi) - self.ctrl_dic['green'].set_lim(mid-length/2, mid+length/2) - mid = 128-para['b_blue'] - length = 255/np.tan(para['c_blue']/180.0*np.pi) - self.ctrl_dic['blue'].set_lim(mid-length/2, mid+length/2) - #self.reset() - return True - class Plugin(Filter): - title = 'Color Balance' - note = ['rgb', 'auto_msk', 'auto_snap', 'not_channel', 'preview'] - - #parameter - para = {'b_red':0, 'c_red':45,'b_green':0, 'c_green':45,'b_blue':0, 'c_blue':45} + title = 'Color Balance' + note = ['rgb', 'auto_msk', 'auto_snap', 'not_channel', 'preview'] + + para = {'bc_r':(0, 45), 'bc_g':(0, 45), 'bc_b':(0, 45)} - def load(self, ips): - hists = [np.histogram(ips.img[:,:,i],list(range(257)))[0] for i in (0,1,2)] - self. view = [('hist', 'red', hists[0]), - ('slide', 'b_red', (-100,100), 0, 'Brightness'), - ('slide', 'c_red', (1,89), 0, 'Contrast'), - ('hist', 'green', hists[0]), - ('slide', 'b_green', (-100,100), 0, 'Brightness'), - ('slide', 'c_green', (1,89), 0, 'Contrast'), - ('hist', 'blue', hists[2]), - ('slide', 'b_blue', (-100,100), 0, 'Brightness'), - ('slide', 'c_blue', (1,89), 0, 'Contrast')] - return True - - def show(self, temp=BalanceDialog): - return Filter.show(self, temp) + def load(self, ips): + hists = [ips.histogram(chans=i, step=512) for i in (0,1,2)] + self.view = [('hist', 'bc_r', 'bc', hists[0], (-255,255), 0), + ('hist', 'bc_g', 'bc', hists[1], (-255,255), 0), + ('hist', 'bc_b', 'bc', hists[2], (-255,255), 0)] + return True - #process - def run(self, ips, snap, img, para = None): - for i, c in zip([0,1,2],['red','green','blue']): - mid = 128-para['b_'+c] - length = 255/np.tan(para['c_'+c]/180.0*np.pi) - xs = np.linspace(0,255,256) - ys = 128 + (xs-mid)*(255/length) - index = np.clip(ys, 0, 255).astype(np.uint8) - img[:,:,i] = index[snap[:,:,i]] \ No newline at end of file + def run(self, ips, snap, img, para = None): + for i, c in zip([0,1,2], 'rgb'): + mid = 128-para['bc_'+c][0] + length = 255/np.tan(para['bc_'+c][1]/180.0*np.pi) + xs = np.linspace(0,255,256) + ys = 128 + (xs-mid)*(255/length) + index = np.clip(ys, 0, 255).astype(np.uint8) + img[:,:,i] = index[snap[:,:,i]] \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/colorstairs_plg.py b/imagepy/menus/Image/Adjust/colorstairs_plg.py index 3fef60f4..4a0b5589 100644 --- a/imagepy/menus/Image/Adjust/colorstairs_plg.py +++ b/imagepy/menus/Image/Adjust/colorstairs_plg.py @@ -4,54 +4,27 @@ @author: yxl """ -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog -from imagepy.ui.widgets import HistCanvas - -class StairsDialog(ParaDialog): - - def para_check(self, para, key): - self.ctrl_dic['red'].set_lim(para['t1_red'], para['t2_red']) - self.ctrl_dic['green'].set_lim(para['t1_green'], para['t2_green']) - self.ctrl_dic['blue'].set_lim(para['t1_blue'], para['t2_blue']) - if key == 't1_red':para['t2_red']=max(para['t2_red'], para['t1_red']) - if key == 't2_red':para['t1_red']=min(para['t2_red'], para['t1_red']) - if key == 't1_green':para['t2_green']=max(para['t2_green'], para['t1_green']) - if key == 't2_green':para['t1_green']=min(para['t2_green'], para['t1_green']) - if key == 't1_blue':para['t2_blue']=max(para['t2_blue'], para['t1_blue']) - if key == 't2_blue':para['t1_blue']=min(para['t2_blue'], para['t1_blue']) - self.reset() - return True +from sciapp.action import Filter +#from imagepy.ui.widgets import HistCanvas class Plugin(Filter): title = 'Color Stairs' note = ['rgb', 'auto_msk', 'auto_snap', 'not_channel', 'preview'] - - #parameter - para = {'t1_red':0, 't2_red':255,'t1_green':0, 't2_green':255,'t1_blue':0, 't2_blue':255} + para = {'thre_r':(0, 255), 'thre_g':(0, 255), 'thre_b':(0, 255)} + def load(self, ips): - hists = [np.histogram(ips.img[:,:,i],list(range(257)))[0] for i in (0,1,2)] - self. view = [('hist', 'red', hists[0]), - ('slide', 't1_red', (0,255), 0, 'Low'), - ('slide', 't2_red', (0,255), 0, 'High'), - ('hist', 'green', hists[0]), - ('slide', 't1_green', (0,255), 0, 'High'), - ('slide', 't2_green', (0,255), 0, 'Low'), - ('hist', 'blue', hists[2]), - ('slide', 't1_blue', (0,255), 0, 'Low'), - ('slide', 't2_blue', (0,255), 0, 'High')] + hists = [ips.histogram(chans=i, step=512) for i in (0,1,2)] + self. view = [('hist', 'thre_r', 'lh', hists[0], (0,255), 0), + ('hist', 'thre_g', 'lh', hists[1], (0,255), 0), + ('hist', 'thre_b', 'lh', hists[2], (0,255), 0)] return True - def show(self, temp=StairsDialog): - return Filter.show(self, temp) - #process def run(self, ips, snap, img, para = None): if para == None: para = self.para - for i, c in zip([0,1,2],['red','green','blue']): - t1, t2 = para['t1_'+c], para['t2_'+c] + for i, c in zip([0,1,2],'rgb'): + t1, t2 = para['thre_'+c] xs = np.linspace(0,255,256) ys = (xs-t1)*(255/max(0.5, t2-t1)) index = np.clip(ys, 0, 255).astype(np.uint8) diff --git a/imagepy/menus/Image/Adjust/curve_plg.py b/imagepy/menus/Image/Adjust/curve_plg.py index 97720a3a..8a791c64 100644 --- a/imagepy/menus/Image/Adjust/curve_plg.py +++ b/imagepy/menus/Image/Adjust/curve_plg.py @@ -1,11 +1,9 @@ -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog, widgets -from imagepy.ui.widgets import CurvePanel +from sciapp.action import Filter +#from imagepy.ui.widgets import CurvePanel from scipy import interpolate -widgets['curve'] = CurvePanel +#widgets['curve'] = CurvePanel class Plugin(Filter): title = 'Curve Adjust' @@ -13,7 +11,7 @@ class Plugin(Filter): para = {'curve': [(0,0), (255, 255)]} def load(self, ips): - hist = np.histogram(self.ips.lookup(),list(range(257)))[0] + hist = ips.histogram(chans='all', step=512) self.view = [('curve', 'curve', hist)] return True diff --git a/imagepy/menus/Image/Adjust/enhanceContrast_plg.py b/imagepy/menus/Image/Adjust/enhanceContrast_plg.py new file mode 100644 index 00000000..e396403b --- /dev/null +++ b/imagepy/menus/Image/Adjust/enhanceContrast_plg.py @@ -0,0 +1,17 @@ +""" +Created on Sun Jan 25 17:00:00 2020 +@author: weisong +""" +from sciapp.action import Filter +from skimage import exposure +import numpy as np + +class Plugin(Filter): + title = 'Enhance Contrast' + note = ['all', 'auto_msk', 'auto_snap','preview'] + para = {'percentage': 5} + view = [(float, 'percentage', (0,100), 2, 'Saturated pixels', '%')] + + def run(self, ips, snap, img, para = None): + up, down = np.percentile(snap, (para['percentage']/2, 100-para['percentage']/2)) + return exposure.rescale_intensity(snap, in_range=(up, down)) diff --git a/imagepy/menus/Image/Adjust/graystairs_plg.py b/imagepy/menus/Image/Adjust/graystairs_plg.py index 2bfc0bfc..fa5ef013 100644 --- a/imagepy/menus/Image/Adjust/graystairs_plg.py +++ b/imagepy/menus/Image/Adjust/graystairs_plg.py @@ -3,26 +3,9 @@ Created on Sun Nov 27 00:56:00 2016 @author: yxl """ - -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog -from imagepy.ui.widgets import HistCanvas - -class ThresholdDialog(ParaDialog): - def __init__(self, parent, title, lim): - ParaDialog.__init__(self, parent, title) - self.lim = lim +from sciapp.action import Filter - def para_check(self, para, key): - if key=='thr1':para['thr2'] = max(para['thr1'], para['thr2']) - if key=='thr2':para['thr1'] = min(para['thr1'], para['thr2']) - lim1 = 1.0 * (para['thr1'] - self.lim[0])/(self.lim[1]-self.lim[0]) - lim2 = 1.0 * (para['thr2'] - self.lim[0])/(self.lim[1]-self.lim[0]) - self.ctrl_dic['hist'].set_lim(lim1*255, lim2*255) - self.reset() - return True class Plugin(Filter): title = 'Gray Stairs' @@ -30,36 +13,26 @@ class Plugin(Filter): arange = (0,255) def load(self, ips): - hist = np.histogram(self.ips.lookup(),list(range(257)))[0] - if ips.imgtype in ('8-bit', 'rgb'): - self.para = {'thr1':0, 'thr2':255} - self.view = [('hist', 'hist', hist), - ('slide', 'thr1', (0,255), 0, 'Low'), - ('slide', 'thr2', (0,255), 0, 'High')] - if 'not_slice' in self.note: - self.note.remove('not_slice') + hist = ips.histogram(chans='all', step=512) + if ips.dtype == np.uint8: + self.para = {'thre_lh':(0, 255)} + self.view = [('hist', 'thre_lh', 'lh', hist, (0,255), 0)] else : - self.arange = minv, maxv = ips.img.min(), ips.img.max() - self.para = {'thr1':ips.range[0], 'thr2':ips.range[1]} - self.view = [('hist', 'hist', hist), - ('slide', 'thr1', (minv, maxv), 10, 'Low'), - ('slide', 'thr2', (minv, maxv), 10, 'High')] - if not 'not_slice' in self.note: - self.note.append('not_slice') + self.para = {'thre_lh':(ips.range[0], ips.range[1])} + self.view = [('hist', 'thre_lh', 'lh', hist, ips.range, 10)] + self.arange = ips.range + #if not 'not_slice' in self.note: + # self.note.append('not_slice') return True - - def show(self, temp=ThresholdDialog): - dialog = lambda win, title, lim = self.ips.range:temp(win, title, lim) - return Filter.show(self, dialog) #process def run(self, ips, snap, img, para = None): - if not ips.imgtype in ('8-bit', 'rgb'): - ips.range = (para['thr1'], para['thr2']) + if ips.dtype != np.uint8: + ips.range = para['thre_lh'] return img[:] = snap - np.subtract(img, para['thr1'], out=img, casting='unsafe') - k = 255.0/max(para['thr2']-para['thr1'], 1e-10) + np.subtract(img, para['thre_lh'][0], out=img, casting='unsafe') + k = 255.0/max(para['thre_lh'][1]-para['thre_lh'][0], 1e-10) np.multiply(img, k, out=img, casting='unsafe') - img[snappara['thr2']] = 255 \ No newline at end of file + img[snappara['thre_lh'][1]] = 255 \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/histogram_plgs.py b/imagepy/menus/Image/Adjust/histogram_plgs.py index 5d174289..528841a3 100644 --- a/imagepy/menus/Image/Adjust/histogram_plgs.py +++ b/imagepy/menus/Image/Adjust/histogram_plgs.py @@ -3,11 +3,8 @@ Created on Sun Nov 27 00:56:00 2016 @author: yxl """ - -from imagepy import IPy import numpy as np -from imagepy.core.engine import Filter, Simple -from imagepy.core.manager import ImageManager +from sciapp.action import Filter, Simple def like(hist1, hist2): hist1 = np.cumsum(hist1)/hist1.sum() @@ -20,7 +17,7 @@ def like(hist1, hist2): i2+=1 return hist -def match(img1, img2): +def match(img2, img1): if img1.ndim == 2: temp = np.histogram(img1, np.arange(257))[0] if img2.ndim == 2: @@ -55,37 +52,17 @@ def run(self, ips, snap, img, para = None): ahist = like(temp, hist) img[:] = ahist[img] -class Match(Simple): - """Calculator Plugin derived from imagepy.core.engine.Simple """ +class Match(Filter): + """Calculator Plugin derived from sciapp.action.Simple """ title = 'Histogram Match' - note = ['all'] - para = {'img1':'', 'img2':''} - - def load(self, ips): - titles = ImageManager.get_titles() - self.para['img1'] = titles[0] - self.para['img2'] = titles[0] - Match.view = [(list, 'img1', titles, str, 'template', ''), - (list, 'img2', titles, str, 'object', '')] - return True + note = ['all', 'not_channel', 'auto_snap', 'auto_msk'] + para = {'img':None} + view = [('img', 'img', 'temp', '')] - def run(self, ips, imgs, para = None): - ips1 = ImageManager.get(para['img1']) - ips2 = ImageManager.get(para['img2']) - ips2.snapshot() - - img = ips1.img - imgs = ips2.imgs - - sl1, sl2 = ips1.get_nslices(), ips2.get_nslices() - cn1, cn2 = ips1.get_nchannels(), ips2.get_nchannels() - if not(ips1.img.dtype == np.uint8 and ips2.img.dtype == np.uint8): - IPy.alert('Two image must be type of 8-bit or rgb!') - return - - for i in range(sl2): - self.progress(i, sl2) - match(img, imgs[i]) - ips2.update = 'pix' + def run(self, ips, snap, img, para = None): + temp = self.app.get_img(para['img']).img + if not(ips.dtype == np.uint8 and temp.dtype == np.uint8): + return self.app.alert('Two image must be type of 8-bit or rgb!') + match(img, temp) plgs = [Normalize, Match] \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/normalize_plg.py b/imagepy/menus/Image/Adjust/normalize_plg.py new file mode 100644 index 00000000..9fc7a363 --- /dev/null +++ b/imagepy/menus/Image/Adjust/normalize_plg.py @@ -0,0 +1,33 @@ +""" +Created on Sun Jan 25 9:00:00 2020 +@author: weisong +""" +from sciapp.action import Simple +import numpy as np + +class Plugin(Simple): + title = 'Normalize' + note = ['8-bit','16-bit','float'] + para = {'is3d': False, 'sb':True} + view = [(bool, 'is3d', '3D stack'), + (bool, 'sb', 'Subtract background')] + + def run(self, ips, imgs, para = None): + lim = np.zeros([len(imgs), 2], dtype=imgs[0].dtype) + dic = {np.uint8:255, np.uint16:65535, np.float32:1, np.float64:1} + + self.app.info('count range ...') + for i in range(len(imgs)): + lim[i] = imgs[i].min(), imgs[i].max() + self.progress(i, len(imgs)) + + maxvalue = dic[imgs[0].dtype.type] + if not para['sb']: lim[:,0] = 0 + rg = lim[:,0].min(), lim[:,1].max() + if para['is3d']: lim[:] = rg + self.app.info('adjust range ...') + for i in range(len(imgs)): + if para['sb']: imgs[i] -= lim[i,0] + np.multiply(imgs[i], maxvalue/(lim[i].ptp()), out=imgs[i], casting='unsafe') + self.progress(i, len(imgs)) + ips.range = 0, maxvalue \ No newline at end of file diff --git a/imagepy/menus/Image/Adjust/threshold_plg.py b/imagepy/menus/Image/Adjust/threshold_plg.py index ee0d828a..24c7efad 100644 --- a/imagepy/menus/Image/Adjust/threshold_plg.py +++ b/imagepy/menus/Image/Adjust/threshold_plg.py @@ -3,27 +3,10 @@ Created on Fri Nov 18 22:56:50 2016 @author: yxl """ -from imagepy import IPy -import numpy as np -from imagepy.core.engine import Filter -from imagepy.ui.panelconfig import ParaDialog -from imagepy.ui.widgets import HistCanvas -from ....core.manager import WindowsManager -class ThresholdDialog(ParaDialog): - def __init__(self, parent, title, lim): - ParaDialog.__init__(self, parent, title) - self.lim = lim +import numpy as np +from sciapp.action import Filter - def para_check(self, para, key): - if key=='thr1':para['thr2'] = max(para['thr1'], para['thr2']) - if key=='thr2':para['thr1'] = min(para['thr1'], para['thr2']) - lim1 = 1.0 * (para['thr1'] - self.lim[0])/(self.lim[1]-self.lim[0]) - lim2 = 1.0 * (para['thr2'] - self.lim[0])/(self.lim[1]-self.lim[0]) - self.ctrl_dic['hist'].set_lim(lim1*255, lim2*255) - self.reset() - return True - class Plugin(Filter): modal = False title = 'Threshold' @@ -31,46 +14,35 @@ class Plugin(Filter): arange = (0,255) def load(self, ips): - hist = np.histogram(self.ips.lookup(),list(range(257)))[0] - if ips.imgtype == '8-bit': - self.para = {'thr1':0, 'thr2':255} - self.view = [('hist', 'hist', hist), - ('slide', 'thr1', (0,255), 0, 'Low'), - ('slide', 'thr2', (0,255), 0, 'High')] + hist = ips.histogram(chans='all', step=512) + if ips.dtype == np.uint8: + self.para = {'thre_lh':(0, 255)} + self.view = [('hist', 'thre_lh', 'lh', hist, (0,255), 0)] else : - self.para = {'thr1':ips.range[0], 'thr2':ips.range[1]} - self.view = [('hist', 'hist', hist,), - ('slide', 'thr1', ips.range, 10, 'Low'), - ('slide', 'thr2', ips.range, 10, 'High')] + self.para = {'thre_lh':(ips.range[0], ips.range[1])} + self.view = [('hist', 'thre_lh', 'lh', hist, ips.range, 10)] self.arange = ips.range self.lut = ips.lut ips.lut = self.lut.copy() return True - def show(self, temp=ThresholdDialog): - dialog = lambda win, title, lim = self.ips.range:temp(win, title, lim) - return Filter.show(self, dialog) - def cancel(self, ips): ips.lut = self.lut - ips.update = 'pix' def preview(self, ips, para): ips.lut[:] = self.lut - thr1 = int((para['thr1']-self.arange[0])*( + thr1 = int((para['thre_lh'][0]-self.arange[0])*( 255.0/max(1e-10, self.arange[1]-self.arange[0]))) - thr2 = int((para['thr2']-self.arange[0])*( + thr2 = int((para['thre_lh'][1]-self.arange[0])*( 255.0/max(1e-10, self.arange[1]-self.arange[0]))) # print(thr1, thr2) ips.lut[:thr1] = [0,255,0] ips.lut[thr2:] = [255,0,0] - ips.update = 'pix' - #process def run(self, ips, snap, img, para = None): if para == None: para = self.para ips.lut = self.lut img[:] = 0 - img[snap>=para['thr2']] = 255 - img[snap=para['thre_lh'][1]] = 255 + img[snap=0 and para['num']=0 and para['num']0: - ips.cur-=1 + if ips.cur>0: ips.cur-=1 class Delete(Simple): title = 'Delete Slice' @@ -42,8 +43,7 @@ class Delete(Simple): #process def run(self, ips, imgs, para = None): ips.imgs.pop(ips.cur) - if ips.cur==ips.get_nslices(): - ips.cur -= 1 + if ips.cur==ips.slices: ips.cur -= 1 class Add(Simple): title = 'Add Slice' @@ -53,4 +53,24 @@ class Add(Simple): def run(self, ips, imgs, para = None): ips.imgs.insert(ips.cur, ips.img*0) -plgs = [SetSlice, Next, Pre, Add, Delete] \ No newline at end of file +class Sub(Simple): + title = 'Sub Stack' + modal = False + note = ['all'] + + para = {'start':0, 'end':10} + + + view = [(int, 'start', (0,1e8), 0, 'start', 'slice'), + (int, 'end', (0,1e8), 0, 'end', 'slice')] + + def load(self, ips): + self.view = [(int, 'start', (0,ips.slices-1), 0, 'start', '0~%d'%(ips.slices-1)), + (int, 'end', (0,ips.slices-1), 0, 'end', '0~%d'%(ips.slices-1))] + return True + + def run(self, ips, imgs, para = None): + s, e = para['start'], para['end'] + self.app.show_img(ips.subimg()[s:e], ips.title+'-substack') + +plgs = [SetSlice, Next, Pre, Add, Delete, '-', Sub] \ No newline at end of file diff --git a/imagepy/menus/Image/Transform/Transform_plgs.py b/imagepy/menus/Image/Transform/Transform_plgs.py index f3b93343..8027c8a7 100644 --- a/imagepy/menus/Image/Transform/Transform_plgs.py +++ b/imagepy/menus/Image/Transform/Transform_plgs.py @@ -5,7 +5,8 @@ """ import numpy as np import scipy.ndimage as nimg -from imagepy.core.engine import Filter +from sciapp.action import Filter, Simple +from imagepy.ipyalg import linear_polar, polar_linear class Rotate(Filter): title = 'Rotate' @@ -16,9 +17,9 @@ class Rotate(Filter): def run(self, ips, snap, img, para = None): if para == None: para = self.para a = para['ang']/180.0*np.pi - o = np.array(ips.size)*0.5 + o = np.array(ips.shape)*0.5 if ips.roi!=None: - box = ips.roi.get_box() + box = ips.roi.box o = np.array([box[1]+box[3],box[0]+box[2]])*0.5 trans = np.array([[np.cos(a),-np.sin(a)],[np.sin(a),np.cos(a)]]) offset = o-trans.dot(o) @@ -33,12 +34,44 @@ class Scale(Filter): def run(self, ips, snap, img, para = None): if para == None: para = self.para k = 1/para['zoom'] - o = np.array(ips.size)*0.5 + o = np.array(ips.shape)*0.5 if ips.roi!=None: - box = ips.roi.get_box() + box = ips.roi.box o = np.array([box[1]+box[3],box[0]+box[2]])*0.5 trans = np.array([[k,0],[0,k]]) offset = o-trans.dot(o) nimg.affine_transform(snap, trans, output=img, offset=offset) -plgs = [Rotate, Scale] \ No newline at end of file +class LinearPolar(Simple): + title = 'Linear To Polar' + note = ['all'] + para = {'ext':'crop', 'order':1, 'slices':False} + view = [(list, 'ext', ['full', 'crop'], str, 'extent', ''), + (int, 'order', (0, 5), 0, 'interpolate', 'order'), + (bool, 'slices', 'slices')] + + def run(self, ips, imgs, para): + if not para['slices']: imgs = [ips.img] + r, rst = min(ips.shape[:2])/2 if para['ext']=='crop' else None, [] + for i in range(len(imgs)): + self.progress(i, len(imgs)) + rst.append(linear_polar(imgs[i], None, r, para['order'])) + self.app.show_img(rst, ips.title + '-polar') + +class PolarLinear(Simple): + title = 'Polar To Linear' + note = ['all'] + para = {'ext':'crop', 'order':1, 'slices':False} + view = [(list, 'ext', ['full', 'crop'], str, 'extent', ''), + (int, 'order', (0, 5), 0, 'interpolate', 'order'), + (bool, 'slices', 'slices')] + + def run(self, ips, imgs, para): + if not para['slices']: imgs = [ips.img] + r, rst = round(ips.shape[0]/(2**0.5 if para['ext']=='crop' else 1)), [] + for i in range(len(imgs)): + self.progress(i, len(imgs)) + rst.append(polar_linear(imgs[i], None, int(r), para['order'])) + self.app.show_img(rst, ips.title + '-polar') + +plgs = [Rotate, Scale, LinearPolar, PolarLinear] \ No newline at end of file diff --git a/imagepy/menus/Image/Type/convert_plg.py b/imagepy/menus/Image/Type/convert_plg.py index f9469414..2241c131 100644 --- a/imagepy/menus/Image/Type/convert_plg.py +++ b/imagepy/menus/Image/Type/convert_plg.py @@ -4,59 +4,55 @@ @author: yxl """ import numpy as np -from imagepy.core.engine import Simple -from imagepy import IPy +from sciapp.action import Simple + +def trans(imgs, shp, cn, sl, rg1, rg2, tp, prog=print): + buf = np.zeros(shp, dtype=np.float32) + (x1, x2), (y1, y2) = rg1, rg2 + if x1 == x2: x1, x2 = x1-1e-8, x2+1e-8 + if y1 == y2: y1, y2 = y1-1e-8, y2+1e-8 + k, b = np.dot(np.linalg.inv([[x1,1],[x2,1]]), [[y1],[y2]]).ravel() + + rst = [None]*sl if isinstance(imgs, list) else np.zeros((sl,)+shp, dtype=tp) + for i in range(sl): + if cn == 1: buf[:] = imgs[i] + else: imgs[i].mean(axis=-1, out=buf) + if rg1 != rg2: + buf *= k + buf += b + if isinstance(imgs, list): + rst[i] = np.clip(buf, y1, y2).astype(tp) + else: np.clip(buf, y1, y2, out=tp) + return rst class To8bit(Simple): title = '8-bit' note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == '8-bit': return - n = ips.get_nslices() - if ips.is3d: - if ips.imgtype == 'rgb': - img8 = np.zeros((n,) + ips.size, dtype=np.uint8) - for i in range(n): - self.progress(i, len(imgs)) - img8[i] = imgs[i].mean(axis=2) - else: - minv, maxv = ips.get_updown() - k = 255.0/(max(1e-8, maxv-minv)) - bf = np.clip(imgs, minv, maxv) - img8 = ((bf - minv) * k).astype(np.uint8) - else: - img8 = [] - minv, maxv = ips.get_updown() - for i in range(n): - self.progress(i, len(imgs)) - if ips.imgtype == 'rgb': - img8.append(imgs[i].mean(axis=2).astype(np.uint8)) - else: - k = 255.0/(max(1e-8, maxv-minv)) - bf = np.clip(imgs[i], minv, maxv) - img8.append(((bf - minv) * k).astype(np.uint8)) - ips.set_imgs(img8) + if ips.dtype == np.uint8 and ips.channels == 1: return + ips.set_imgs(trans(imgs, ips.shape, ips.channels, ips.slices, ips.range, (0,255), np.uint8)) class ToRGB(Simple): title = 'RGB' note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == 'rgb': return - n = ips.get_nslices() - if ips.is3d: + if ips.dtype == np.uint8 and ips.channels == 3: return + n = ips.slices + + if ips.isarray: imgrgb = np.zeros(ips.size+(n,), dtype=np.uint8) if ips.dtype == np.uint8: img8 = imgs else: - minv, maxv = ips.get_updown() + minv, maxv = ips.range k = 255.0/(max(1e-8, maxv-minv)) bf = np.clip(imgs, minv, maxv) img8 = ((bf - minv) * k).astype(np.uint8) rgb = ips.lut[img8] else: rgb = [] - minv, maxv = ips.get_updown() + minv, maxv = ips.range for i in range(n): self.progress(i, len(imgs)) if ips.dtype==np.uint8: @@ -73,95 +69,32 @@ class ToUint16(Simple): note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == '16-bit': return - n = ips.get_nslices() - if ips.is3d: - if ips.imgtype == 'rgb': - img16 = imgs.mean(axis=3, dtype=np.uint16) - else: - img16 = np.clip(imgs, 0, 65535).astype(np.uint16) - else: - img16 = [] - minv, maxv = ips.get_updown() - for i in range(n): - self.progress(i, len(imgs)) - if ips.imgtype == 'rgb': - img16.append(imgs[i].mean(axis=2).astype(np.uint16)) - else: - k = 255.0/(max(1e-10, maxv-minv)) - img16.append(np.clip(imgs[i], 0, 65535).astype(np.uint16)) - ips.set_imgs(img16) + if ips.dtype == np.uint16 and ips.channels == 1: return + ips.set_imgs(trans(imgs, ips.shape, ips.channels, ips.slices, ips.range, (0,65535), np.uint16)) + class ToInt32(Simple): title = '32-bit int' note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == '32-int': return - n = ips.get_nslices() - if ips.is3d: - if ips.imgtype == 'rgb': - img32 = imgs.mean(axis=3, dtype=np.int32) - else: - img32 = imgs.astype(np.int32) - else: - img32 = [] - minv, maxv = ips.get_updown() - for i in range(n): - self.progress(i, len(imgs)) - if ips.imgtype == 'rgb': - img32.append(imgs[i].mean(axis=2).astype(np.int32)) - else: - k = 255.0/(max(1e-10, maxv-minv)) - img32.append(imgs[i].astype(np.int32)) - ips.set_imgs(img32) + if ips.dtype == np.int32 and ips.channels == 1: return + ips.set_imgs(trans(imgs, ips.shape, ips.channels, ips.slices, ips.range, ips.range, np.int32)) class ToFloat32(Simple): title = '32-bit float' note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == '32-float': return - n = ips.get_nslices() - if ips.is3d: - if ips.imgtype == 'rgb': - img32 = imgs.mean(axis=3, dtype=np.float32) - else: - img32 = imgs.astype(np.float32) - else: - img32 = [] - minv, maxv = ips.get_updown() - for i in range(n): - self.progress(i, len(imgs)) - if ips.imgtype == 'rgb': - img32.append(imgs[i].mean(axis=2).astype(np.float32)) - else: - k = 255.0/(max(1e-10, maxv-minv)) - img32.append(imgs[i].astype(np.float32)) - ips.set_imgs(img32) + if ips.dtype == np.float32 and ips.channels == 1: return + ips.set_imgs(trans(imgs, ips.shape, ips.channels, ips.slices, ips.range, ips.range, np.float32)) class ToFloat64(Simple): title = '64-bit float' note = ['all'] def run(self, ips, imgs, para = None): - if ips.imgtype == '64-bit': return - n = ips.get_nslices() - if ips.is3d: - if ips.imgtype == 'rgb': - img64 = imgs.mean(axis=3, dtype=np.float64) - else: - img64 = imgs.astype(np.float64) - else: - img64 = [] - minv, maxv = ips.get_updown() - for i in range(n): - self.progress(i, len(imgs)) - if ips.imgtype == 'rgb': - img64.append(imgs[i].mean(axis=2).astype(np.float64)) - else: - k = 255.0/(max(1e-10, maxv-minv)) - img64.append(imgs[i].astype(np.float64)) - ips.set_imgs(img64) - + if ips.dtype == np.float64 and ips.channels == 1: return + ips.set_imgs(trans(imgs, ips.shape, ips.channels, ips.slices, ips.range, ips.range, np.float64)) + plgs = [To8bit, ToRGB, '-', ToUint16, ToInt32, ToFloat32, ToFloat64] \ No newline at end of file diff --git a/imagepy/menus/Image/Type/tostack_plg.py b/imagepy/menus/Image/Type/tostack_plg.py index 76133ed7..4610b40d 100644 --- a/imagepy/menus/Image/Type/tostack_plg.py +++ b/imagepy/menus/Image/Type/tostack_plg.py @@ -4,17 +4,22 @@ @author: yxl """ import numpy as np -from imagepy.core.engine import Simple +from sciapp.action import Simple + class ToStack(Simple): title = 'Trans to Stack' note = ['all','no_change','req_stack'] def run(self, ips, imgs, para = None): - imgstack = np.zeros((ips.get_nslices(),) + imgs[0].shape, dtype=ips.dtype) + ips.imgs = np.asarray(imgs) + ''' + ips.imgs = np.array() + imgstack = np.zeros((ips.slices,) + ips.shape, dtype=ips.dtype) for i in range(ips.get_nslices()): imgstack[i] = ips.imgs[i] ips.imgs = imgstack ips.is3d = True + ''' class ToList(Simple): title = 'Trans to List' @@ -22,6 +27,5 @@ class ToList(Simple): def run(self, ips, imgs, para = None): ips.imgs = list(imgs) - ips.is3d = False plgs = [ToStack, ToList] \ No newline at end of file diff --git a/imagepy/menus/Image/__init__.py b/imagepy/menus/Image/__init__.py index af735aee..96c9ebb6 100644 --- a/imagepy/menus/Image/__init__.py +++ b/imagepy/menus/Image/__init__.py @@ -1,2 +1,3 @@ -catlog = ['Type', '-', 'Adjust', 'Color', 'Stack', 'Transform', '-', 'duplicate_plg', - 'crop_plg', 'canvassize_plg', 'resize_plg', '-', 'setscale_plg', 'background_plg', 'Lookup table'] \ No newline at end of file +catlog = ['Type', '-', 'Adjust', 'Color', 'Stack', 'Channels', 'Transform', '-', + 'duplicate_plg', 'crop_plg', 'canvassize_plg', 'resize_plg', '-', + 'setscale_plg', 'background_plg', '-', 'Mark', 'Lookup table'] \ No newline at end of file diff --git a/imagepy/menus/Image/background_plg.py b/imagepy/menus/Image/background_plg.py index f0cbd6ab..2899b652 100644 --- a/imagepy/menus/Image/background_plg.py +++ b/imagepy/menus/Image/background_plg.py @@ -1,50 +1,36 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 1 01:22:19 2016 -@author: yxl -""" -from imagepy.core.manager import ImageManager -from imagepy import IPy import numpy as np -from imagepy.core.engine import Simple +from sciapp.object import Image +from sciapp.action import Simple class SetBackground(Simple): - """Calculator Plugin derived from imagepy.core.engine.Simple """ title = 'Set Background' note = ['all'] - para = {'img':'None','op':'Mean', 'k':0.5, 'kill':False} - view = [('img','background', 'img', '8-bit'), - (list, 'op', ['Mean', 'Clip'], str, 'mode', ''), - (float, 'k', (0,1), 1, 'blender', ''), + para = {'img':None,'mode':'msk', 'k':0.5, 'kill':False} + view = [('img','img', 'background', '8-bit'), + (list, 'mode', ['set', 'min', 'max', 'msk', 'ratial'], str, 'mode', ''), + (float, 'k', (0, 1), 1, 'ratial', ''), (bool, 'kill', 'kill')] def run(self, ips, imgs, para = None): - if para['kill']: - ips.backimg = None + if para['kill']: ips.mode, ips.back = 'set', None else: - img = ImageManager.get(para['img']).img - if img.dtype != np.uint8 or img.shape[:2] != ips.img.shape[:2]: - IPy.alert('a background image must be 8-bit and with the same size') - return - ips.backimg = img - ips.backmode = (para['k'], para['op']) - ips.update = 'pix' + ips.back = self.app.get_img(para['img']) + ips.mode = para['k'] if para['mode']=='ratial' else para['mode'] class BackgroundSelf(Simple): - """Calculator Plugin derived from imagepy.core.engine.Simple """ title = 'Background Self' - note = ['8-bit', 'rgb'] - para = {'op':'Mean', 'k':0.5, 'kill':False} - view = [(list, 'op', ['Mean', 'Clip'], str, 'mode', ''), - (float, 'k', (0,1), 1, 'blender', ''), - (bool, 'kill', 'kill')] + note = ['all'] + para = {'mode':'msk', 'k':0.5} + view = [(list, 'mode', ['set', 'min', 'max', 'msk', 'ratial'], str, 'mode', ''), + (float, 'k', (0, 1), 1, 'ratial', '')] def run(self, ips, imgs, para = None): - if para['kill']: - ips.backimg = None - else: - ips.backimg = ips.img.copy() - ips.backmode = (para['k'], para['op']) - ips.update = 'pix' + print(para) + if ips.isarray: imgs = imgs.copy() + else: imgs = [i.copy() for i in imgs] + back = Image(imgs) + back.cn, back.rg = ips.cn, ips.rg + ips.back = back + ips.mode = para['k'] if para['mode']=='ratial' else para['mode'] plgs = [SetBackground, BackgroundSelf] \ No newline at end of file diff --git a/imagepy/menus/Image/canvassize_plg.py b/imagepy/menus/Image/canvassize_plg.py index 29e035c0..25e5bcf3 100644 --- a/imagepy/menus/Image/canvassize_plg.py +++ b/imagepy/menus/Image/canvassize_plg.py @@ -3,10 +3,15 @@ Created on Sun Dec 11 23:43:44 2016 @author: yxl """ -from imagepy.core.engine import Simple +from sciapp.action import Simple import numpy as np -from imagepy.core.pixel import bliter -from imagepy import IPy + +def make_slice(a, b, mode=1): + aa, bb = sorted([a, b]) + if mode == 0: sb = slice(0, aa) + if mode == 1: sb = slice((bb-aa)//2, (bb-aa)//2+aa) + if mode == 2: sb = slice(bb-aa, bb) + return (slice(None), sb)[::(-1,1)[a1:shp = (shp[1], shp[0], chns) - - if para['hor'] == 'left':c=0 - if para['ver'] == 'top':r=0 - if para['hor'] == 'center':c=(shp[1]-old[1])//2 - if para['ver'] == 'center':r=(shp[0]-old[0])//2 - if para['hor'] == 'right':c=shp[1]-old[1] - if para['ver'] == 'bottom':r=shp[0]-old[0] - - if ips.is3d: - s = list(imgs.shape) - s[1], s[2] = shp[0], shp[1] - rst = np.zeros(s, dtype=ips.dtype) - for i in range(len(imgs)): - self.progress(i, len(imgs)) - bliter.blit(rst[i], imgs[i], c, r) - else: - rst = [] - for i in range(len(imgs)): - self.progress(i, len(imgs)) - rst.append(np.zeros(shp, ips.dtype)) - bliter.blit(rst[-1], imgs[i], c, r) - ips.roi = None - ips.set_imgs(rst) - if ips.backimg is None: return - nbc = np.zeros(shp, dtype=np.uint8) - bliter.blit(nbc, ips.backimg, c, r) - ips.backimg = nbc + (o_r, o_c), n, n_r, n_c = ips.shape, ips.channels, para['h'], para['w'] + key = {'left':0, 'center':1, 'right':2, 'top':0, 'bottom':1} + or_sli, nr_sli = make_slice(o_r, n_r, key[para['ver']]) + oc_sli, nc_sli = make_slice(o_c, n_c, key[para['hor']]) + shp = (ips.slices, n_r, n_c, ips.channels)[:3+(ips.channels>1)] + if ips.isarray: buf = np.zeros(shp, dtype=ips.dtype) + else: buf = [np.zeros(shp[1:], dtype=ips.dtype) for i in range(shp[0])] + for i in range(ips.slices): + self.progress(i, ips.slices) + buf[i][nr_sli, nc_sli] = imgs[i][or_sli, oc_sli] + ips.set_imgs(buf) \ No newline at end of file diff --git a/imagepy/menus/Image/crop_plg.py b/imagepy/menus/Image/crop_plg.py deleted file mode 100644 index 9d2e4cb4..00000000 --- a/imagepy/menus/Image/crop_plg.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 7 02:32:31 2016 -@author: yxl -""" - -from imagepy.core.engine import Simple -import numpy as np - -class Plugin(Simple): - title = 'Crop' - note = ['all', 'req_roi'] - - def run(self, ips, imgs, para = None): - sc, sr = ips.get_rect() - if ips.is3d: - imgs = imgs[:, sc, sr].copy() - else: - imgs = [i[sc,sr].copy() for i in imgs] - ips.set_imgs(imgs) - if not ips.backimg is None: - ips.backimg = ips.backimg[sc, sr] - ips.roi = ips.roi.affine(np.eye(2), (-sr.start, -sc.start)) \ No newline at end of file diff --git a/imagepy/menus/Image/duplicate_plg.py b/imagepy/menus/Image/duplicate_plg.py index a7e81299..f154cb4c 100644 --- a/imagepy/menus/Image/duplicate_plg.py +++ b/imagepy/menus/Image/duplicate_plg.py @@ -4,55 +4,71 @@ @author: yxl """ -from imagepy.core.engine import Simple -from imagepy.core import ImagePlus -from imagepy.ui.canvasframe import CanvasFrame +from sciapp.action import Simple +from sciapp.object import Image, ROI +from sciapp.util import offset, mark2shp import numpy as np -from imagepy import IPy -class Plugin(Simple): +class Duplicate(Simple): title = 'Duplicate' note = ['all'] - para = {'name':'Undefined','stack':True} - def load(self, ips): - self.para['name'] = ips.title+'-copy' - self.view = [(str, 'name', 'Name', '')] - if ips.get_nslices()>1: - self.view.append((bool, 'stack', 'duplicate stack')) + if ips.slices > 1: + self.para = {'stack':True} + self.view = [(bool, 'stack', 'duplicate stack')] + else: + self.para = {'stack':True} + self.view = None return True + + def run(self, ips, imgs, para = None): + if not para['stack']: imgs = [ips.img] + sli = ips.rect + imgs = [i[sli].copy() for i in imgs] + if ips.isarray: imgs = np.array(imgs) + new = Image(imgs, ips.name + '-duplicate') + if not ips.roi is None: + new.roi = ROI(mark2shp(ips.roi.to_mark())) + offset(new.roi, new.roi.box[0]*-1, new.roi.box[1]*-1) + new.roi.dirty = True + if not ips.back is None and not ips.back.imgs is None: + back = [i[sli].copy() for i in ips.back.imgs] + if ips.isarray: back = np.array(back) + back = Image(back, ips.back.name+'-duplicate') + back.cn, back.rg, back.mode = ips.back.cn, ips.back.rg, ips.back.mode + new.back, new.mode = back, ips.mode + self.app.show_img(back) + self.app.show_img(new) + +class Crop(Simple): + title = 'Crop' + note = ['all', 'req_roi'] + + def run(self, ips, imgs, para = None): + sc, sr = ips.rect + if ips.isarray: imgs = imgs[:, sc, sr].copy() + else: imgs = [i[sc,sr].copy() for i in imgs] + ips.set_imgs(imgs) + if not ips.back is None: + if ips.back.isarray: imgs = ips.back.imgs[:, sc, sr].copy() + else: imgs = [i[sc,sr].copy() for i in ips.back.imgs] + ips.back.set_imgs(imgs) + offset(ips.roi, ips.roi.box[0]*-1, ips.roi.box[1]*-1) + +class Rename(Simple): + title = 'Rename' + note = ['all'] + + para = {'name':'Undefined'} + view = [(str, 'name', 'name', '')] #process def run(self, ips, imgs, para = None): - name = para['name'] - print('name------------------', name) - if ips.get_nslices()==1 or self.para['stack']==False: - if ips.roi == None: - img = ips.img.copy() - ipsd = ImagePlus([img], name) - ipsd.backimg = ips.backimg - else: - img = ips.get_subimg().copy() - ipsd = ImagePlus([img], name) - box = ips.roi.get_box() - ipsd.roi = ips.roi.affine(np.eye(2), (-box[0], -box[1])) - if not ips.backimg is None: - sr, sc = ips.get_rect() - ipsd.backimg = ips.backimg[sr, sc] - elif ips.get_nslices()>1 and self.para['stack']: - if ips.roi == None: - if ips.is3d:imgs=imgs.copy() - else:imgs = [i.copy() for i in imgs] - backimg = ips.backimg - else: - sc, sr = ips.get_rect() - if ips.is3d: imgs=imgs[:, sc, sr].copy() - else: imgs = [i[sc,sr].copy() for i in imgs] - if not ips.backimg is None: - backimg = ips.backimg[sr, sr] - ipsd = ImagePlus(imgs, name) - if ips.roi != None: - ipsd.roi = ips.roi.affine(np.eye(2), (-sr.start, -sc.start)) - if not ips.backimg is None: ipsd.backimg = backimg - ipsd.backmode = ips.backmode - IPy.show_ips(ipsd) \ No newline at end of file + win = self.app.wimg_manager.get(ips.name) + self.app.img_manager.remove(ips.name) + self.app.wimg_manager.remove(ips.name) + ips.name = self.app.img_manager.name(para['name']) + self.app.img_manager.add(ips.name, ips) + self.app.wimg_manager.add(ips.name, win) + +plgs = [Rename, Duplicate, Crop] \ No newline at end of file diff --git a/imagepy/menus/Image/resize_plg.py b/imagepy/menus/Image/resize_plg.py index d893de30..db7a8666 100644 --- a/imagepy/menus/Image/resize_plg.py +++ b/imagepy/menus/Image/resize_plg.py @@ -4,51 +4,70 @@ @author: yxl """ -from imagepy.core.engine import Simple +from sciapp.action import Simple import scipy.ndimage as ndimg import numpy as np -from imagepy import IPy class Plugin(Simple): title = 'Resize' note = ['all'] - para = {'kx':0.5, 'ky':0.5, 'kz':1} - view = [(float, 'kx', (0.1,10), 1, 'kx', '0.1~10'), - (float, 'ky', (0.1,10), 1, 'ky', '0.1~10'), - (float, 'kz', (0.1,10), 1, 'kz', '0.1~10'), + para = {'kx':0.5, 'ky':0.5, 'kz':1,'order':3} + view = [(float, 'kx', (0.1,10), 2, 'kx', '0.1~10'), + (float, 'ky', (0.1,10), 2, 'ky', '0.1~10'), + (float, 'kz', (0.1,10), 2, 'kz', '0.1~10'), + (int, 'order', (0,5), 0, 'accu', '0-5'), ('lab', None, 'the kz only works on stack!')] def run(self, ips, imgs, para = None): kx, ky, kz = [para[i] for i in ('ky','kx','kz')] - size = np.round([ips.width*kx, ips.height*ky]) - w, h = size.astype(np.uint16) - if ips.is3d: - if ips.get_nchannels()>1: + size = np.round([ips.slices*kz, ips.shape[1]*kx, ips.shape[0]*ky]) + n, w, h = size.astype(np.uint16) + + buf = np.zeros((n, h, w, ips.channels), dtype=ips.dtype) + if kz==1: + for i in range(ips.slices): + img = imgs[i].reshape(ips.shape+(-1,)) + for c in range(ips.channels): + ndimg.zoom(img[:,:,c], (ky, kx), output=buf[i,:,:,c], order=para['order']) + else: + for c in range(ips.channels): + imgsc = [i.reshape(i.shape[:2]+(-1,))[:,:,c] for i in imgs] + ndimg.zoom(imgsc, (kz, kx, ky), order=para['order'], output=buf[:,:,:,c]) + + if ips.channels == 1: buf.shape = (buf.shape[:3]) + if n == 1: buf = [buf.reshape(buf.shape[1:])] + ips.set_imgs(buf) + ''' + else: + + if ips.slice>1: + if ips.channels>1: new = np.zeros(np.multiply(imgs.shape, (kz, kx, ky, 1)).round().astype(np.uint32), dtype=imgs.dtype) - for i in range(ips.get_nchannels()): - ndimg.zoom(imgs[:,:,:,i], (kz, kx, ky), output=new[:,:,:,i]) + for i in range(ips.channels): + ndimg.zoom(imgs[:,:,:,i], (kz, kx, ky), output=new[:,:,:,i], order=para['order']) else : - new = ndimg.zoom(imgs, (kz, kx, ky)) + new = ndimg.zoom(imgs, (kz, kx, ky), order=para['order']) else: - if ips.get_nchannels()>1: + if ips.channels>1: new = [] for i in range(len(imgs)): self.progress(i, len(imgs)) arr = np.zeros(np.multiply(imgs[i].shape, (kx, ky, 1)).round().astype(np.uint32), dtype=imgs[i].dtype) - for n in range(ips.get_nchannels()): - ndimg.zoom(imgs[i][:,:,n], (kx, ky), output=arr[:,:,n]) + for n in range(ips.channels: + ndimg.zoom(imgs[i][:,:,n], (kx, ky), output=arr[:,:,n], order=para['order']) new.append(arr) else : new = [] for i in range(len(imgs)): self.progress(i, len(imgs)) - arr = ndimg.zoom(imgs[i], (kx, ky)) + arr = ndimg.zoom(imgs[i], (kx, ky), order=para['order']) new.append(arr) ips.set_imgs(new) + return backimg = ips.backimg if backimg is None:return if backimg.ndim == 3: @@ -60,4 +79,5 @@ def run(self, ips, imgs, para = None): else : nbc = ndimg.zoom(backimg, (kz, kx)) print(nbc.dtype) - ips.backimg = nbc \ No newline at end of file + ips.backimg = nbc + ''' \ No newline at end of file diff --git a/imagepy/menus/Image/setscale_plg.py b/imagepy/menus/Image/setscale_plg.py index b121adf7..2a139480 100644 --- a/imagepy/menus/Image/setscale_plg.py +++ b/imagepy/menus/Image/setscale_plg.py @@ -1,59 +1,16 @@ -# -*- coding: utf-8 -*- - -from imagepy.core.engine import Simple -from imagepy.ui.canvasframe import CanvasFrame -from imagepy.core.manager import ConfigManager -from imagepy.ui.panelconfig import ParaDialog +from sciapp.action import Simple import numpy as np -from imagepy import IPy - -def add(recent, v): - if v in recent: - idx = recent.index(v) - recent.insert(0, recent.pop(idx)) - else: - recent.insert(0, v) - if len(recent)>5: - del recent[5:] - -class ScaleDialog(ParaDialog): - def para_check(self, para, key): - if key=='recent' and para[key] != 'Recent': - k, u = para[key].split(' - ') - para['k'], para['unit'] = float(k), u - self.reset() class Plugin(Simple): title = 'Scale And Unit' note = ['all'] - recent = [] para = {'k':1.0, 'unit':'pix', 'kill':False, 'recent':'Recent'} view = [(float, 'k', (0,1000000), 2, 'per', 'pix'), (str, 'unit', 'unit', ''), - (list, 'recent', [], str, 'commen', ''), (bool, 'kill', 'kill scale')] - def show(self, temp=ScaleDialog): - return Simple.show(self, temp) - - def load(self, ips): - self.recent = ConfigManager.get('recent-units') - if self.recent == None : self.recent = ['Recent'] - else: self.recent.insert(0, 'Recent') - self.view[2] = (list, 'recent', self.recent, str, 'commen', '') - if ips.unit==None: - self.para['K'],self.para['unit'] = (1, 'pix') - else: self.para['k'], self.para['unit'] = ips.unit - return True def run(self, ips, imgs, para = None): - if para['kill'] : - ips.unit=(1,'pix') - print('huhuhu') - else : - print('hahaha') - ips.unit = (para['k'], para['unit']) - self.recent.pop(0) - add(self.recent, '%s - %s'%(para['k'], para['unit'])) - ConfigManager.set('recent-units', self.recent) \ No newline at end of file + if para['kill'] : ips.unit = 1, 'pix' + else : ips.unit = para['k'], para['unit'] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Analysis 3D/pixelstatistic_plgs.py b/imagepy/menus/Kit3D/Analysis 3D/pixelstatistic_plgs.py index cf0a0c98..f1935120 100644 --- a/imagepy/menus/Kit3D/Analysis 3D/pixelstatistic_plgs.py +++ b/imagepy/menus/Kit3D/Analysis 3D/pixelstatistic_plgs.py @@ -1,6 +1,5 @@ -from imagepy import IPy import numpy as np -from imagepy.core.engine import Simple, Filter +from sciapp.action import Simple, Filter import pandas as pd class Statistic(Simple): @@ -32,7 +31,7 @@ def run(self, ips, imgs, para = None): titles = [i for i in titles if para[key[i]]] if para['nozero']: imgs = imgs[imgs!=0] data = self.count(imgs, para) - IPy.show_table(pd.DataFrame(data, columns=titles), ips.title+'-statistic') + self.app.show_table(pd.DataFrame(data, columns=titles), ips.title+'-statistic') class Frequence(Simple): title = 'Frequence 3D' @@ -52,6 +51,6 @@ def run(self, ips, imgs, para = None): dt = [bins[:-1].round(2), ct, (ct/ct.sum()).round(4)] dt = list(zip(*dt)) - IPy.show_table(pd.DataFrame(dt, columns=titles), ips.title+'-histogram') + self.app.show_table(pd.DataFrame(dt, columns=titles), ips.title+'-histogram') plgs = [Statistic, Frequence] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Analysis 3D/regionprops3d_plgs.py b/imagepy/menus/Kit3D/Analysis 3D/regionprops3d_plgs.py index 1bc1ff84..ac29f79a 100644 --- a/imagepy/menus/Kit3D/Analysis 3D/regionprops3d_plgs.py +++ b/imagepy/menus/Kit3D/Analysis 3D/regionprops3d_plgs.py @@ -1,7 +1,8 @@ -from imagepy import IPy import numpy as np -from imagepy.core.engine import Simple, Filter +from sciapp.action import Simple, Filter from scipy.ndimage import label, generate_binary_structure +from skimage.measure import marching_cubes, mesh_surface_area +from skimage.segmentation import find_boundaries from skimage.measure import regionprops from numpy.linalg import norm import pandas as pd @@ -16,7 +17,7 @@ class RegionLabel(Simple): #process def run(self, ips, imgs, para = None): - buf = imgs.astype(np.uint16) + buf = imgs.astype(np.int32) strc = generate_binary_structure(3, 1 if para['con']=='4-connect' else 2) label(imgs, strc, output=buf) IPy.show_img(buf, ips.title+'-label') @@ -27,14 +28,16 @@ class RegionCounter(Simple): note = ['8-bit', '16-bit', 'stack3d'] para = {'con':'8-connect', 'center':True, 'extent':False, 'vol':True, - 'ed':False, 'holes':False, 'fa':False} + 'ed':False, 'holes':False, 'fa':False, 'cov':False, 'surf':True} view = [(list, 'con', ['4-connect', '8-connect'], str, 'conection', 'pix'), ('lab', None, '========= indecate ========='), (bool, 'center', 'center'), (bool, 'vol', 'volume'), + (bool, 'surf', 'surface area'), (bool, 'extent', 'extent'), - (bool, 'ed', 'equivalent diameter')] + (bool, 'ed', 'equivalent diameter'), + (bool, 'cov', 'eigen values')] #process def run(self, ips, imgs, para = None): @@ -42,14 +45,15 @@ def run(self, ips, imgs, para = None): titles = ['ID'] if para['center']:titles.extend(['Center-X','Center-Y','Center-Z']) + if para['surf']:titles.append('Surface') if para['vol']:titles.append('Volume') if para['extent']:titles.extend(['Min-Z','Min-Y','Min-X','Max-Z','Max-Y','Max-X']) if para['ed']:titles.extend(['Diameter']) if para['fa']:titles.extend(['FilledArea']) + if para['cov']:titles.extend(['Axis1', 'Axis2', 'Axis3']) - buf = imgs.astype(np.uint16) strc = generate_binary_structure(3, 1 if para['con']=='4-connect' else 2) - label(imgs, strc, output=buf) + buf, n = label(imgs, strc, output=np.uint32) ls = regionprops(buf) dt = [range(len(ls))] @@ -59,6 +63,12 @@ def run(self, ips, imgs, para = None): dt.append([round(i.centroid[1]*k,1) for i in ls]) dt.append([round(i.centroid[0]*k,1) for i in ls]) dt.append([round(i.centroid[2]*k,1) for i in ls]) + if para['surf']: + buf[find_boundaries(buf, mode='outer')] = 0 + vts, fs, ns, cs = marching_cubes_lewiner(buf, level=0) + lst = [[] for i in range(n+1)] + for i in fs: lst[int(cs[i[0]])].append(i) + dt.append([0 if len(i)==0 else mesh_surface_area(vts, np.array(i))*k**2 for i in lst][1:]) if para['vol']: dt.append([i.area*k**3 for i in ls]) if para['extent']: @@ -68,7 +78,11 @@ def run(self, ips, imgs, para = None): dt.append([round(i.equivalent_diameter*k, 1) for i in ls]) if para['fa']: dt.append([i.filled_area*k**3 for i in ls]) - IPy.show_table(pd.DataFrame(list(zip(*dt)), columns=titles), ips.title+'-region') + if para['cov']: + ites = np.array([i.inertia_tensor_eigvals for i in ls]) + rst = np.sqrt(np.clip(ites.sum(axis=1)//2-ites.T, 0, 1e10)) * 4 + for i in rst[::-1]: dt.append(np.abs(i)) + self.app.show_table(pd.DataFrame(list(zip(*dt)), columns=titles), ips.title+'-region') # center, area, l, extent, cov class RegionFilter(Simple): @@ -88,7 +102,7 @@ def run(self, ips, imgs, para = None): k, unit = ips.unit strc = generate_binary_structure(3, 1 if para['con']=='4-connect' else 2) - lab, n = label(imgs==0 if para['inv'] else imgs, strc, output=np.uint16) + lab, n = label(imgs==0 if para['inv'] else imgs, strc, output=np.uint32) idx = (np.ones(n+1)*(0 if para['inv'] else para['front'])).astype(np.uint8) ls = regionprops(lab) diff --git a/imagepy/menus/Kit3D/Analysis 3D/surfacemeasure_plg.py b/imagepy/menus/Kit3D/Analysis 3D/surfacemeasure_plg.py index 8afe184b..c77404fe 100644 --- a/imagepy/menus/Kit3D/Analysis 3D/surfacemeasure_plg.py +++ b/imagepy/menus/Kit3D/Analysis 3D/surfacemeasure_plg.py @@ -1,6 +1,5 @@ -from imagepy.core.engine import Filter -from skimage.measure import marching_cubes_lewiner, mesh_surface_area -from imagepy import IPy +from sciapp.action import Filter +from skimage.measure import marching_cubes, mesh_surface_area import numpy as np import pandas as pd @@ -24,11 +23,11 @@ def load(self, ips): def preview(self, ips, para): ips.lut[:] = self.buflut ips.lut[:para['thr']] = [255,0,0] - ips.update = 'pix' + ips.update() def cancel(self, ips): ips.lut = self.buflut - ips.update = 'pix' + ips.update() def run(self, ips, snap, img, para = None): ips.lut = self.buflut diff --git a/imagepy/menus/Kit3D/Binary 3D/binary3d_plgs.py b/imagepy/menus/Kit3D/Binary 3D/binary3d_plgs.py index 5af5bcfc..3f42cd94 100644 --- a/imagepy/menus/Kit3D/Binary 3D/binary3d_plgs.py +++ b/imagepy/menus/Kit3D/Binary 3D/binary3d_plgs.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -* import scipy.ndimage as ndimg -from imagepy.core.engine import Simple +from sciapp.action import Simple from skimage.morphology import skeletonize_3d from imagepy.ipyalg import find_maximum, watershed +from skimage.filters import apply_hysteresis_threshold +from imagepy.ipyalg import distance_transform_edt import numpy as np class Dilation(Simple): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Dilation 3D' note = ['all', 'stack3d'] para = {'r':3} @@ -18,7 +20,7 @@ def run(self, ips, imgs, para = None): imgs *= 255 class Erosion(Simple): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Erosion 3D' note = ['all', 'stack3d'] para = {'r':3} @@ -30,7 +32,7 @@ def run(self, ips, imgs, para = None): imgs *= 255 class Opening(Simple): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Opening 3D' note = ['all', 'stack3d'] para = {'r':3} @@ -42,7 +44,7 @@ def run(self, ips, imgs, para = None): imgs *= 255 class Closing(Simple): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Closing 3D' note = ['all', 'stack3d'] para = {'r':3} @@ -54,7 +56,7 @@ def run(self, ips, imgs, para = None): imgs *= 255 class FillHole(Simple): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Fill Holes 3D' note = ['all', 'stack3d'] @@ -77,7 +79,9 @@ class Distance3D(Simple): #process def run(self, ips, imgs, para = None): - dismap = ndimg.distance_transform_edt(imgs>0) + imgs[:] = imgs>0 + dtype = imgs.dtype if imgs.dtype in (np.float32, np.float64) else np.uint16 + dismap = distance_transform_edt(imgs, output=dtype) imgs[:] = np.clip(dismap, ips.range[0], ips.range[1]) class Watershed(Simple): @@ -92,12 +96,15 @@ class Watershed(Simple): ## TODO: Fixme! def run(self, ips, imgs, para = None): - dist = -ndimg.distance_transform_edt(imgs) - pts = find_maximum(dist, para['tor'], False) - buf = np.zeros(imgs.shape, dtype=np.uint16) - buf[pts[:,0], pts[:,1], pts[:,2]] = 1 - markers, n = ndimg.label(buf, np.ones((3,3, 3))) - line = watershed(dist, markers, line=True, conn=para['con']+1) - imgs[line==0] = 0 + imgs[:] = imgs > 0 + dist = distance_transform_edt(imgs, output=np.uint16) + pts = find_maximum(dist, para['tor'], True) + buf = np.zeros(imgs.shape, dtype=np.uint32) + buf[pts[:,0], pts[:,1], pts[:,2]] = 2 + imgs[pts[:,0], pts[:,1], pts[:,2]] = 2 + markers, n = ndimg.label(buf, np.ones((3, 3, 3))) + line = watershed(dist, markers, line=True, conn=para['con']+1, up=False) + msk = apply_hysteresis_threshold(imgs, 0, 1) + imgs[:] = imgs>0; imgs *= 255; imgs *= ~((line==0) & msk) plgs = [Dilation, Erosion, Opening, Closing, '-', FillHole, Skeleton3D, '-', Distance3D, Watershed] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Features 3D/__init__.py b/imagepy/menus/Kit3D/Features 3D/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Kit3D/Features 3D/ridge_plgs.py b/imagepy/menus/Kit3D/Features 3D/ridge_plgs.py new file mode 100644 index 00000000..22e779b5 --- /dev/null +++ b/imagepy/menus/Kit3D/Features 3D/ridge_plgs.py @@ -0,0 +1,69 @@ +from skimage.filters import frangi, sato, hessian ,meijering +from sciapp.action import Filter, Simple + +class Frangi(Simple): + title = 'Frangi 3D' + note = ['float', 'auto_msk', 'auto_snap', 'stack3d'] + para = {'start':1, 'end':10, 'step':2, 'alpha':0.5, 'beta':0.5, 'gamma':15, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (float, 'alpha', (0.1, 1), 1, 'alpha',''), + (float, 'beta', (0.1, 1), 1, 'beta',''), + (float, 'gamma', (1,30), 1, 'gamma',''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, imgs, para = None): + IPy.show_img(frangi(imgs, range(para['start'], para['end'], para['step']), + alpha=para['alpha'], beta=para['beta'], gamma=para['gamma'], + black_ridges=para['bridges']), ips.title+'-frangi') + + +class Meijering(Simple): + title = 'Meijering 3D' + note = ['float', 'auto_msk', 'auto_snap','stack3d'] + para = {'start':1, 'end':10, 'step':2, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, imgs, para=None): + IPy.show_img(meijering(imgs, range(para['start'], para['end'], para['step']), + black_ridges=para['bridges']), ips.title+'-meijering') + +class Sato(Simple): + title = 'Sato 3D' + note = ['float', 'auto_msk', 'auto_snap', 'stack3d'] + para = {'start':1, 'end':10, 'step':2, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, imgs, para=None): + IPy.show_img(sato(imgs, range(para['start'], para['end'], para['step']), + black_ridges=para['bridges']), ips.title+'-sato') + +class Hessian(Simple): + title = 'Hessian 3D' + note = ['float', 'auto_msk', 'auto_snap','stack3d'] + para = {'start':1, 'end':10, 'step':2, 'alpha':0.5, 'beta':0.5, 'gamma':15, 'bridges':True} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (float, 'alpha', (0.1, 1), 1, 'alpha',''), + (float, 'beta', (0.1, 1), 1, 'beta',''), + (float, 'gamma', (1,30), 1, 'gamma',''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, imgs, para = None): + IPy.show_img(hessian(imgs, range(para['start'], para['end'], para['step']), + alpha=para['alpha'], beta=para['beta'], gamma=para['gamma'], + black_ridges=para['bridges']), ips.title+'-hessian') + +plgs = [Frangi, Meijering, Sato, Hessian] diff --git a/imagepy/menus/Kit3D/Filters 3D/filters3d_plgs.py b/imagepy/menus/Kit3D/Filters 3D/filters3d_plgs.py index 64888367..596be745 100644 --- a/imagepy/menus/Kit3D/Filters 3D/filters3d_plgs.py +++ b/imagepy/menus/Kit3D/Filters 3D/filters3d_plgs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -* import scipy.ndimage as ndimg -from imagepy.core.engine import Filter, Simple +from sciapp.action import Filter, Simple #from skimage.morphology import watershed from imagepy.ipyalg import watershed import numpy as np @@ -76,15 +76,14 @@ def load(self, ips): def cancel(self, ips): ips.lut = self.buflut - ips.update = 'pix' + ips.update() def preview(self, ips, para): ips.lut[:] = self.buflut ips.lut[:para['thr1']] = [0,255,0] ips.lut[para['thr2']:] = [255,0,0] - ips.update = 'pix' + ips.update() - #process def run(self, ips, snap, img, para = None): imgs = ips.imgs gradient = np.zeros(imgs.shape, dtype=np.float32) diff --git a/imagepy/menus/Kit3D/IO 3D/__init__.py b/imagepy/menus/Kit3D/IO 3D/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Kit3D/Network 3D/toolkit3d_plgs.py b/imagepy/menus/Kit3D/Network 3D/toolkit3d_plgs.py index 30e9a93e..b722ff14 100644 --- a/imagepy/menus/Kit3D/Network 3D/toolkit3d_plgs.py +++ b/imagepy/menus/Kit3D/Network 3D/toolkit3d_plgs.py @@ -1,23 +1,21 @@ -from imagepy.core.engine import Filter, Simple +from sciapp.action import Filter, Simple from imagepy.ipyalg.graph import sknw from skimage.morphology import skeletonize_3d from itertools import combinations - -from imagepy.core import myvi -from imagepy import IPy import networkx as nx import numpy as np import pandas as pd +from sciapp.object import Mesh, TextSet +from sciapp.util import meshutil norm = np.linalg.norm - class Skeleton3D(Simple): title = 'Skeleton 3D' note = ['8-bit', 'stack3d'] #process def run(self, ips, imgs, para = None): - imgs[:] = skeletonize_3d(imgs>0) + imgs[skeletonize_3d(imgs>0)==0] = 0 class BuildGraph(Simple): title = 'Build Graph 3D' @@ -32,29 +30,22 @@ class Show3DGraph(Simple): title = 'Show Graph 3D' note = ['8-bit', 'stack3d'] - para = {'r':1, 'ncolor':(255,0,0), 'lcolor':(0,0,255)} + para = {'r':1, 'ncolor':(255,0,0), 'lcolor':(0,0,255), 'pcolor':(0,255,0)} view = [(int, 'r', (1,100), 0, 'radius', 'pix'), ('color', 'ncolor', 'node', 'rgb'), - ('color', 'lcolor', 'line', 'rgb')] - - def load(self, ips): - if not isinstance(ips.data, nx.MultiGraph): - IPy.alert("Please build graph!"); - return False; - self.frame = myvi.Frame3D.figure(IPy.curapp, title='3D Canvas') - return True; + ('color', 'lcolor', 'line', 'rgb'), + ('color', 'pcolor', 'path', 'rgb')] - #process def run(self, ips, imgs, para = None): balls, ids, rs, graph = [], [], [], ips.data for idx in graph.nodes(): ids.append(idx) - balls.append(graph.node[idx]['o']) + balls.append(graph.nodes[idx]['o']) xs, ys, zs = [], [], [] lxs, lys, lzs = [], [], [] for (s, e) in graph.edges(): eds = graph[s][e] - st, ed = graph.node[s]['o'], graph.node[e]['o'] + st, ed = graph.nodes[s]['o'], graph.nodes[e]['o'] lxs.append([st[0],ed[0]]) lys.append([st[1],ed[1]]) lzs.append([st[2],ed[2]]) @@ -66,19 +57,75 @@ def run(self, ips, imgs, para = None): rs = [para['r']] * len(balls) cs = tuple(np.array(para['ncolor'])/255.0) - vts, fs, ns, cs = myvi.build_balls(balls, rs, cs) - self.frame.viewer.add_surf_asyn('balls', vts, fs, ns, cs) + vts, fs, cs = meshutil.create_balls(balls, rs, cs) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=cs), 'balls') - vts, fs, pos, h, color = myvi.build_marks(['ID:%s'%i for i in ids], balls, para['r'], para['r'], (1,1,1)) - self.frame.viewer.add_mark_asyn('txt', vts, fs, pos, h, color) + cs = tuple(np.array(para['lcolor'])/255.0) + vts, fs, cs = meshutil.create_lines(xs, ys, zs, cs) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=cs, mode='grid'), 'path') + + cs = tuple(np.array(para['pcolor'])/255.0) + vts, fs, cs = meshutil.create_lines(lxs, lys, lzs, cs) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=cs, mode='grid'), 'lines') + + self.app.show_mesh(TextSet(['ID:%s'%i for i in ids], verts=balls, size=para['r']*256, colors=(1,1,1)), 'txt') + + + +class Show3DGraphR(Simple): + title = 'Show Graph R 3D' + note = ['8-bit', 'stack3d'] + + para = {'dis':None, 'ncolor':(255,0,0), 'lcolor':(0,0,255), 'pcolor':(0,255,0)} + view = [('img', 'dis', 'distance', 'map'), + ('color', 'ncolor', 'node', 'rgb'), + ('color', 'lcolor', 'line', 'rgb'), + ('color', 'pcolor', 'path', 'rgb')] + #process + def run(self, ips, imgs, para = None): + dis = self.app.get_img(para['dis']).imgs + balls, ids, rs, graph = [], [], [], ips.data + for idx in graph.nodes(): + ids.append(idx) + balls.append(graph.nodes[idx]['o']) + + xs, ys, zs = [], [], [] + v1s, v2s = [], [] + for (s, e) in graph.edges(): + eds = graph[s][e] + st, ed = graph.nodes[s]['o'], graph.nodes[e]['o'] + v1s.append(st) + v2s.append(ed) + for i in eds: + pts = eds[i]['pts'] + xs.append(pts[:,0]) + ys.append(pts[:,1]) + zs.append(pts[:,2]) + + rs1 = dis[list(np.array(v1s).astype(np.int16).T)] + rs2 = dis[list(np.array(v2s).astype(np.int16).T)] + rs1 = list(np.clip(rs1, 2, 1e4)*0.5) + rs2 = list(np.clip(rs2, 2, 1e4)*0.5) + rs = dis[list(np.array(balls).astype(np.int16).T)] + rs = list(np.clip(rs, 2, 1e4)) + + print(balls, rs1, rs2, rs) + + cs = tuple(np.array(para['ncolor'])/255.0) + vts, fs, ns, cs = surfutil.build_balls(balls, rs, cs) + self.app.show_mesh(Surface(vts, fs, ns, cs), 'balls') + + meansize = sum(rs)/len(rs) + vts, fs, pos, h, color = surfutil.build_marks(['ID:%s'%i for i in ids], balls, rs, meansize, (1,1,1)) + self.app.show_mesh(MarkText(vts, fs, pos, h, color), 'txt') cs = tuple(np.array(para['lcolor'])/255.0) - vts, fs, ns, cs = myvi.build_lines(xs, ys, zs, cs) - self.frame.viewer.add_surf_asyn('paths', vts, fs, ns, cs, mode='grid') - vts, fs, ns, cs = myvi.build_lines(lxs, lys, lzs, (0,1,0)) - self.frame.viewer.add_surf_asyn('lines', vts, fs, ns, cs, mode='grid') - self.frame.Raise() - self.frame = None + vts, fs, ns, cs = surfutil.build_lines(xs, ys, zs, cs) + self.app.show_mesh(Surface(vts, fs, ns, cs, mode='grid'), 'path') + + cs = tuple(np.array(para['pcolor'])/255.0) + vts, fs, ns, cs = surfutil.build_arrows(v1s, v2s, rs1, rs2, 0, 0, cs) + self.app.show_mesh(Surface(vts, fs, ns, cs), 'lines') class Statistic(Simple): title = 'Graph Statistic 3D' @@ -98,13 +145,13 @@ def run(self, ips, imgs, para = None): comid = 0 for g in nx.connected_component_subgraphs(ips.data, False): for idx in g.nodes(): - o = g.node[idx]['o'] + o = g.nodes[idx]['o'] nodes.append([comid, idx, g.degree(idx), round(o[1]*k,2), round(o[0]*k,2), round(o[2])]) for (s, e) in g.edges(): eds = g[s][e] for i in eds: l = round(eds[i]['weight']*k, 2) - dis = round(np.linalg.norm(g.node[s]['o']-g.node[e]['o'])*k, 2) + dis = round(np.linalg.norm(g.nodes[s]['o']-g.nodes[e]['o'])*k, 2) edges.append([comid, s, e, l, dis]) comid += 1 @@ -156,7 +203,7 @@ def run(self, ips, imgs, para = None): graph = ips.data datas = [] for s in graph.nodes(): - o = graph.node[s]['o'] + o = graph.nodes[s]['o'] x = graph[s] if len(x)<=1: continue rst = [] @@ -199,7 +246,7 @@ def run(self, ips, imgs, para = None): rm = [] for i in g.nodes(): if g.degree(i)!=1:continue - s,e = g.edges(i)[0] + s,e = list(g.edges(i))[0] if g[s][e][0]['weight']*k<=para['lim']: rm.append(i) g.remove_nodes_from(rm) @@ -219,9 +266,37 @@ def load(self, ips): def run(self, ips, imgs, para = None): g = ips.data - for n in g.nodes(): + for n in list(g.nodes()): if len(g[n])==0: g.remove_node(n) imgs *= 0 sknw.draw_graph(imgs, g) -plgs = [Skeleton3D, BuildGraph, '-', CutBranch, RemoveIsolate, '-', Statistic, Sumerise, '-', Show3DGraph] \ No newline at end of file +class Remove2Node(Simple): + title = 'Remove 2Path Node 3D' + note = ['all'] + + def load(self, ips): + if not isinstance(ips.data, nx.MultiGraph): + IPy.alert("Please build graph!"); + return False; + return True; + + def run(self, ips, imgs, para = None): + g = ips.data + for n in list(g.nodes()): + if len(g[n])!=2 or n in g[n]: continue + (k1, e1), (k2, e2) = g[n].items() + if isinstance(g, nx.MultiGraph): + if len(e1)!=1 or len(e2)!=1: continue + e1, e2 = e1[0], e2[0] + l1, l2 = e1['pts'], e2['pts'] + d1 = norm(l1[0]-g.nodes[n]['o']) > norm(l1[-1]-g.nodes[n]['o']) + d2 = norm(l2[0]-g.nodes[n]['o']) < norm(l2[-1]-g.nodes[n]['o']) + pts = np.vstack((l1[::[-1,1][d1]], l2[::[-1,1][d2]])) + l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum() + g.remove_node(n) + g.add_edge(k1, k2, pts=pts, weight=l) + imgs *= 0 + sknw.draw_graph(imgs, g) + +plgs = [Skeleton3D, BuildGraph, '-', CutBranch, RemoveIsolate, Remove2Node, '-', Statistic, Sumerise, '-', Show3DGraph, Show3DGraphR] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/2DSurface Demo.mc b/imagepy/menus/Kit3D/Viewer 3D/2DSurface Demo.mc index e174005c..eb263ab3 100644 --- a/imagepy/menus/Kit3D/Viewer 3D/2DSurface Demo.mc +++ b/imagepy/menus/Kit3D/Viewer 3D/2DSurface Demo.mc @@ -1,2 +1,2 @@ -Open Url>{'url': 'http://data.imagepy.org/testdata/dem.jpg'} -2D Surface>{'name': 'dem', 'scale': 1, 'sigma': 1, 'h': 0.3} \ No newline at end of file +moon>None +2D Surface>{'name': 'moon', 'sample': 2, 'sigma': 0.7, 'h': 0.3, 'cm': 'Green_Fire_Blue'} \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/__init__.py b/imagepy/menus/Kit3D/Viewer 3D/__init__.py index 7ece0d88..051f9c97 100644 --- a/imagepy/menus/Kit3D/Viewer 3D/__init__.py +++ b/imagepy/menus/Kit3D/Viewer 3D/__init__.py @@ -1,2 +1 @@ -catlog = ['surface2d_plg', 'surface3d_plg', - '-', '2DSurface Demo.mc'] \ No newline at end of file +catlog = ['surface_plgs', '-', 'colorpts_plg', 'tablepoints_plg', '-', '2DSurface Demo.mc'] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/colorpts_plg.py b/imagepy/menus/Kit3D/Viewer 3D/colorpts_plg.py new file mode 100644 index 00000000..3fdcbedd --- /dev/null +++ b/imagepy/menus/Kit3D/Viewer 3D/colorpts_plg.py @@ -0,0 +1,30 @@ +from sciapp.action import Simple +from sciapp.object import Mesh +from sciapp.util import meshutil +import numpy as np + +class Plugin(Simple): + title = 'RGB Points Cloud' + note = ['rgb'] + para = {'name':'undifine', 'num':100, 'r':1} + view = [(str, 'name', 'Name', ''), + (int, 'num', (10,10240), 0, 'number', 'points'), + (float, 'r', (0.1,30), 1, 'radius', '')] + + def run(self, ips, imgs, para = None): + num,r = para['num'], para['r'] + if ips.roi != None: pts = ips.img[ips.get_msk()] + else: pts = ips.img.reshape((-1,3)) + pts = pts[::len(pts)//num] + vts, fs, cs = meshutil.create_balls(pts, np.ones(len(pts))*r, pts/255) + self.app.show_mesh(Mesh(pts, colors=pts/255), para['name']) + (r1,g1,b1),(r2,g2,b2) = (0,0,0),(1,1,1) + rs = (r1,r2,r2,r1,r1,r1,r1,r1,r1,r2,r2,r1,r2,r2,r2,r2) + gs = (g1,g1,g1,g1,g1,g2,g2,g1,g2,g2,g2,g2,g2,g1,g1,g2) + bs = (b1,b1,b2,b2,b1,b1,b2,b2,b2,b2,b1,b1,b1,b1,b2,b2) + vts, fs, ls = meshutil.create_cube((0,0,0),(255,255,255)) + cs = np.array(list(zip(rs,gs,bs))) + self.app.show_mesh(Mesh(vts, ls, colors=vts/255, mode='grid'), 'cube') + +if __name__ == '__main__': + pass \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/demo_plgs.py b/imagepy/menus/Kit3D/Viewer 3D/demo_plgs.py index c88cff64..43d24624 100644 --- a/imagepy/menus/Kit3D/Viewer 3D/demo_plgs.py +++ b/imagepy/menus/Kit3D/Viewer 3D/demo_plgs.py @@ -1,11 +1,10 @@ -from imagepy.core.engine import Free -from imagepy.core import myvi -from imagepy import IPy +from sciapp.action import Free +from sciapp.object import Mesh +from sciapp.util import meshutil import numpy as np class Decoration(Free): title = 'Decoration Demo' - asyn = False def run(self, para=None): dphi, dtheta = np.pi/20.0, np.pi/20.0 @@ -14,23 +13,15 @@ def run(self, para=None): r = np.sin(m0*phi)**m1 + np.cos(m2*phi)**m3 + np.sin(m4*theta)**m5 + np.cos(m6*theta)**m7 x = r*np.sin(phi)*np.cos(theta) y = r*np.cos(phi) - z = r*np.sin(phi)*np.sin(theta) - vts, fs, ns, cs = myvi.build_mesh(x, y, z) - cs[:] = myvi.util.auto_lookup(vts[:,2], myvi.util.linear_color('jet'))/255 - - manager = myvi.Manager() - manager.add_surf('mesh', vts, fs, ns, cs) - myvi.Frame3D(IPy.curapp, 'Decoration Demo', manager).Show() + z = r*np.sin(phi)*np.sin(theta) + vts, fs = meshutil.create_grid_mesh(x, y, z) + mesh = Mesh(vts, fs.astype(np.uint32), vts[:,2], mode='grid', cmap='jet') + self.app.show_mesh(mesh, 'decoration') class Lines(Free): title = 'Lines Demo' - asyn = False def run(self, para=None): - vts = np.array([(0,0,0),(1,1,0),(2,1,0),(1,0,0)], dtype=np.float32) - fs = np.array([(0,1,2),(1,2,3)], dtype=np.uint32) - ns = np.ones((4,3), dtype=np.float32) - n_mer, n_long = 6, 11 pi = np.pi dphi = pi / 1000.0 @@ -40,27 +31,21 @@ def run(self, para=None): y = np.sin(mu) * (1 + np.cos(n_long * mu / n_mer) * 0.5) z = np.sin(n_long * mu / n_mer) * 0.5 - vts, fs, ns, cs = myvi.build_line(x, y, z, (1, 0, 0)) - cs[:] = myvi.auto_lookup(vts[:,2], myvi.linear_color('jet'))/255 - - manager = myvi.Manager() - obj = manager.add_surf('line', vts, fs, ns, cs) - obj.set_style(mode='grid') - myvi.Frame3D(IPy.curapp, 'Colorful Lines Demo', manager).Show() + vts = np.array([x, y, z]).T.astype(np.float32) + fs = np.arange(len(vts), dtype=np.uint32) + fs = np.array([fs[:-1], fs[1:]]).T + mesh = Mesh(vts, fs, vts[:,2], cmap='jet', mode='grid') + self.app.show_mesh(mesh, 'line') class Balls(Free): title = 'Random Balls Demo' - asyn = False def run(self, para=None): os = np.random.rand(30).reshape((-1,3)) - rs = np.random.rand(10)/5 - cs = (np.random.rand(10)*255).astype(np.uint8) - cs = myvi.linear_color('jet')[cs]/255 - - vts, fs, ns, cs = myvi.build_balls(os, rs, cs) - manager = myvi.Manager() - manager.add_surf('balls', vts, fs, ns, cs) - myvi.Frame3D(IPy.curapp, 'Random Balls Demo', manager).Show() + rs = np.random.rand(10)/7+0.05 + cs = np.random.rand(10) + vts_b, fs_b, cs_b = meshutil.create_balls(os, rs, cs) + mesh = Mesh(verts=vts_b, faces=fs_b, colors=cs_b, cmap='jet') + self.app.show_mesh(mesh, 'balls') plgs = [Lines, Balls, Decoration] \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/surface2d_plg.py b/imagepy/menus/Kit3D/Viewer 3D/surface2d_plg.py deleted file mode 100644 index b57003cf..00000000 --- a/imagepy/menus/Kit3D/Viewer 3D/surface2d_plg.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Jan 10 22:33:33 2017 - -@author: yxl -""" -from imagepy import IPy -from imagepy.core.engine import Simple -from scipy.ndimage.filters import gaussian_filter -from imagepy.core import myvi - -class Plugin(Simple): - title = '2D Surface' - note = ['8-bit', '16-bit'] - para = {'name':'undifine', 'scale':2, 'sigma':2,'h':1} - view = [(str, 'name', 'Name', ''), - (int, 'scale', (1,5), 0, 'down scale', 'pix'), - (int, 'sigma', (0,30), 0, 'sigma', ''), - (float, 'h', (0.1,10), 1, 'scale z', '')] - - def load(self, para): - self.frame = myvi.Frame3D.figure(IPy.curapp, title='3D Canvas') - return True - - def run(self, ips, imgs, para = None): - ds, sigma = para['scale'], para['sigma'] - vts, fs, ns, cs = myvi.build_surf2d(ips.img, ds=ds, sigma=para['sigma'], k=para['h']) - self.frame.viewer.add_surf_asyn(para['name'], vts, fs, ns, cs) - self.frame.Raise() - self.frame = None - #self.frame.add_surf2d('dem', ips.img, ips.lut, scale, sigma) - -if __name__ == '__main__': - pass \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/surface3d_plg.py b/imagepy/menus/Kit3D/Viewer 3D/surface3d_plg.py deleted file mode 100644 index debd9a3e..00000000 --- a/imagepy/menus/Kit3D/Viewer 3D/surface3d_plg.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Jan 12 00:42:18 2017 - -@author: yxl -""" -from imagepy.core.engine import Filter -from imagepy import IPy -from imagepy.core import myvi -import numpy as np - -class Plugin(Filter): - modal = False - title = '3D Surface' - note = ['8-bit', 'not_slice', 'not_channel', 'preview'] - para = {'name':'undifine', 'ds':2, 'thr':128, 'step':1, 'color':(0,255,0)} - view = [(str, 'name', 'Name', ''), - ('slide', 'thr', (0,255), 0, 'threshold'), - (int, 'ds', (1,20), 0, 'down scale', 'pix'), - (int, 'step', (1,20), 0, 'march step', 'pix'), - ('color', 'color', 'color', 'rgb')] - - def load(self, ips): - if not ips.is3d: - IPy.alert('stack3d required!') - return False - self.frame = myvi.Frame3D.figure(IPy.curapp, title='3D Canvas') - self.buflut = ips.lut - ips.lut = ips.lut.copy() - return True - - def preview(self, ips, para): - ips.lut[:] = self.buflut - ips.lut[:para['thr']] = [255,0,0] - ips.update = 'pix' - - def run(self, ips, snap, img, para = None): - imgs = ips.imgs - - def cancel(self, ips): - ips.lut = self.buflut - ips.update = 'pix' - - def run(self, ips, snap, img, para = None): - ips.lut = self.buflut - print('------------', para['color']) - cs = tuple([int(i/255.0) for i in para['color']]) - vts, fs, ns, cs = myvi.build_surf3d(ips.imgs, para['ds'], para['thr'], para['step'], cs) - self.frame.viewer.add_surf_asyn(para['name'], vts, fs, ns, cs) - self.frame.Raise() - self.frame = None - - - ''' - def run(self, ips, imgs, para = None): - from mayavi import mlab - volume = mlab.pipeline.scalar_field(ips.imgs) - if para['sigma']!=0: - volume = mlab.pipeline.user_defined(volume, filter='ImageGaussianSmooth') - volume.filter.standard_deviations = [para['sigma']]*3 - c = tuple([i/255.0 for i in para['color']]) - contour = mlab.pipeline.iso_surface(volume, contours=[para['thr']], - color=c, opacity=para['opa']) - mlab.show() - ''' -if __name__ == '__main__': - pass \ No newline at end of file diff --git a/imagepy/menus/Kit3D/Viewer 3D/surface_plgs.py b/imagepy/menus/Kit3D/Viewer 3D/surface_plgs.py new file mode 100644 index 00000000..2f64180b --- /dev/null +++ b/imagepy/menus/Kit3D/Viewer 3D/surface_plgs.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 10 22:33:33 2017 + +@author: yxl +""" +from sciapp.action import Simple, Filter, Free +from scipy.ndimage.filters import gaussian_filter +from sciapp.object import Mesh, Scene, Surface2d, Surface3d, Volume3d +from imagepy.app import ColorManager +from sciapp.util import meshutil + +class Show(Free): + title = 'Show Viewer 3D' + para = {'name':'Scene', 'bg':(0,0,0)} + view = [(str, 'name', 'name', ''), + ('color', 'bg', 'background', 'color')] + + def run(self, para): + scene = Scene(bg_color=[i/255 for i in para['bg']]) + self.app.show_mesh(scene, para['name']) + +class Surface2D(Simple): + title = '2D Surface' + note = ['8-bit', '16-bit', 'float'] + para = {'name':'undifine', 'sample':2, 'sigma':2,'h':0.3, 'cm':'gray'} + view = [(str, 'name', 'Name', ''), + (int, 'sample', (1,10), 0, 'down sample', 'pix'), + (int, 'sigma', (0,30), 0, 'sigma', ''), + (float, 'h', (0.1,10), 1, 'scale z', ''), + ('cmap', 'cm', 'color map')] + + def run(self, ips, imgs, para = None): + ds, sigma, cm = para['sample'], para['sigma'], ColorManager.get(para['cm']) + mesh = Surface2d(ips.img, sample=ds, sigma=sigma, k=para['h'], cmap=cm) + self.app.show_mesh(mesh, para['name']) + +class Surface3D(Simple): + modal = False + title = '3D Surface' + note = ['8-bit', 'stack3d', 'preview'] + para = {'name':'undifine', 'ds':2, 'thr':128, 'step':1, 'color':(0,255,0)} + view = [(str, 'name', 'Name', ''), + ('slide', 'thr', (0,255), 0, 'threshold'), + (int, 'ds', (1,20), 0, 'down scale', 'pix'), + (int, 'step', (1,20), 0, 'march step', 'pix'), + ('color', 'color', 'color', 'rgb')] + + def load(self, ips): + self.buflut = ips.lut + ips.lut = ips.lut.copy() + return True + + def preview(self, ips, para): + ips.lut[:] = self.buflut + ips.lut[:para['thr']] = [255,0,0] + + def cancel(self, ips): + ips.lut = self.buflut + + def run(self, ips, imgs, para = None): + ips.lut = self.buflut + cs = tuple([int(i/255.0) for i in para['color']]) + surf3d = Surface3d(imgs=ips.imgs, level=para['thr'], sample=para['ds'], step=para['step'], colors=cs) + self.app.show_mesh(surf3d, para['name']) + +class ImageCube(Simple): + modal = False + title = '3D Image Cube' + note = ['8-bit', 'rgb', 'stack3d'] + para = {'name':'undifine', 'ds':1, 'color':(0,255,0), 'surface':True, 'box':False} + view = [(str, 'name', 'Name', 'xxx-surface'), + (bool, 'surface', 'show surface'), + (int, 'ds', (1,20), 0, 'down scale', 'pix'), + (bool, 'box', 'show box'), + ('color', 'color', 'box color', 'rgb')] + + def run(self, ips, imgs, para = None): + if para['surface']: + vts, fs, ns, cs = surfutil.build_img_cube(imgs, para['ds']) + self.app.show_mesh(Surface(vts, fs, ns, cs), para['name']+'-surface') + if para['box']: + vts, fs, ns, cs = surfutil.build_img_box(imgs, para['color']) + self.app.show_mesh(Surface(vts, fs, ns, cs, mode='grid'), para['name']+'-box') + +class Volume3D(Simple): + modal = False + title = '3D Volume' + note = ['8-bit', 'stack3d'] + para = {'name':'undifine', 'step':1, 'cm':'gray', 'cube':True} + view = [(str, 'name', 'Name', ''), + (int, 'step', (1,10), 0, 'march step', 'pix'), + ('cmap', 'cm', 'color map'), + (bool, 'cube', 'draw outline cube')] + + def run(self, ips, imgs, para = None): + cmap = ColorManager.get(para['cm']) + self.app.show_mesh(Volume3d(imgs, step=para['step'], cmap=cmap), para['name']) + if para['cube']: + vts, fs = meshutil.create_bound((0,0,0), imgs.shape) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=(1,1,1), mode='grid'), 'box') + +plgs = [Show, Surface2D, Surface3D, ImageCube, Volume3D] diff --git a/imagepy/menus/Kit3D/Viewer 3D/tablepoints_plg.py b/imagepy/menus/Kit3D/Viewer 3D/tablepoints_plg.py new file mode 100644 index 00000000..efe472ab --- /dev/null +++ b/imagepy/menus/Kit3D/Viewer 3D/tablepoints_plg.py @@ -0,0 +1,73 @@ +from sciapp.action import Table +from imagepy.app import ColorManager +from sciapp.util import meshutil +from sciapp.object import Mesh +import numpy as np + +''' +class Plugin(Table): + title = 'Table Point Cloud' + + para = {'name':'undefined', 'x':None, 'y':None, 'z':None, 'r':5, 'rs':None, 'c':(0,0,255), + 'cs':None, 'cm':None, 'cube':False} + + view = [(str, 'name', 'name', ''), + ('field', 'x', 'x data', ''), + ('field', 'y', 'y data', ''), + ('field', 'z', 'z data', ''), + (float, 'r', (0, 1024), 3, 'radius', 'pix'), + ('lab', 'lab', '== if set the radius would becom factor =='), + ('field', 'rs', 'radius', 'column'), + ('color', 'c', 'color', ''), + ('lab', 'lab', '== if set the color upon would disable =='), + ('field', 'cs', 'color', 'column'), + ('cmap', 'cm', 'color map when color column is set'), + (bool, 'cube', 'draw outline cube')] + + + def run(self, tps, snap, data, para = None): + pts = np.array(data[[para['x'], para['y'], para['z']]]) + rs = data[para['rs']]*para['r'] if para['rs'] != 'None' else np.ones(len(pts))*para['r'] + cm = ColorManager.get(para['cm'])/255.0 + clip = lambda x : (x-x.min())/(x.max()-x.min())*255 + if para['cs'] == 'None': cs = tuple(np.array(para['c'])/255) + else: cs = data[para['cs']] + print(pts, rs, cs) + vts, fs, cs = meshutil.create_balls(pts, rs, cs) + mesh = Mesh(verts=vts, faces=fs, colors=cs, cmap=cm) + self.app.show_mesh(mesh, para['name']) + if para['cube']: + p1 = data[[para['x'], para['y'], para['z']]].min(axis=0) + p2 = data[[para['x'], para['y'], para['z']]].max(axis=0) + vts, fs = meshutil.create_bound(p1, p2) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=(1,1,1), mode='grid'), 'box') +''' + +class Plugin(Table): + title = 'Table Point Cloud' + + para = {'name':'undefined', 'x':None, 'y':None, 'z':None, 'ref':None, 'c':(0,0,255), 'cm':None, 'cube':False} + + view = [(str, 'name', 'name', ''), + ('field', 'x', 'x data', ''), + ('field', 'y', 'y data', ''), + ('field', 'z', 'z data', ''), + ('field', 'ref', 'reflectivity', 'column'), + ('cmap', 'cm', 'color map for reflectivity'), + ('color', 'c', 'color', 'when no ref'), + (bool, 'cube', 'draw outline cube')] + + + def run(self, tps, snap, data, para = None): + pts = np.array(data[[para['x'], para['y'], para['z']]]) + cm = ColorManager.get(para['cm'])/255.0 + clip = lambda x : (x-x.min())/(x.max()-x.min())*255 + if para['ref'] == 'None': cs = tuple(np.array(para['c'])/255) + else: cs = data[para['ref']] + mesh = Mesh(verts=pts, colors=cs, cmap=cm, mode='points') + self.app.show_mesh(mesh, para['name']) + if para['cube']: + p1 = data[[para['x'], para['y'], para['z']]].min(axis=0) + p2 = data[[para['x'], para['y'], para['z']]].max(axis=0) + vts, fs = meshutil.create_bound(p1, p2) + self.app.show_mesh(Mesh(verts=vts, faces=fs, colors=(1,1,1), mode='grid'), 'box') \ No newline at end of file diff --git a/imagepy/menus/Kit3D/__init__.py b/imagepy/menus/Kit3D/__init__.py index b4d4bd10..2b061e35 100644 --- a/imagepy/menus/Kit3D/__init__.py +++ b/imagepy/menus/Kit3D/__init__.py @@ -1 +1 @@ -catlog = ['Filters 3D', 'Binary 3D', '-', 'Analysis 3D', 'Network 3D', '-', 'Viewer 3D'] \ No newline at end of file +catlog = ['Filters 3D', 'Features 3D', 'Binary 3D', '-', 'Analysis 3D', 'Network 3D', '-', 'Viewer 3D'] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Coins Report.rpt b/imagepy/menus/Plugins/Coins Report.rpt new file mode 100644 index 00000000..3dc3d615 Binary files /dev/null and b/imagepy/menus/Plugins/Coins Report.rpt differ diff --git a/imagepy/menus/Plugins/Contribute/Contribute Document.md b/imagepy/menus/Plugins/Contribute/Contribute Document.md new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Plugins/Contribute/Contributions/CellPose Planer.md b/imagepy/menus/Plugins/Contribute/Contributions/CellPose Planer.md new file mode 100644 index 00000000..370538c0 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/CellPose Planer.md @@ -0,0 +1,196 @@ +# cellpose-planer +**Path:** https://gitee.com/imagepy/cellpose-planer + +**Version:** 0.1 + +**Author:** YXDragon, Y.Dong + +**Email:** yxdragon@imagepy.org + +**Keyword:** cellpose, segment, unet + +**Description:** cellpose on planer framework for imagepy. + + +[Cellpose](https://github.com/MouseLand/cellpose) is a generalist algorithm for cellular segmentation, Which written by Carsen Stringer and Marius Pachitariu. + +[Planer](https://github.com/Image-Py/planer) is a light-weight CNN framework implemented in pure Numpy-like interface. It can run only with Numpy. Or change different backends. (Cupy accelerated with CUDA, ClPy accelerated with OpenCL). + +So Cellpose-Planer is the **cellpose** models on **planer** framework. We generate onnx from torch models, then deduce it to planer model. + +**We just use cellpose's models, but we rewrite all the pre-after processing and render algorithm, So the result is not same as the official one** + +## Features +* cellpose-planer is very light, only depend on [Numpy](https://github.com/numpy/numpy) and Scipy. +* cellpose-planer can be accelerated with [Cupy](https://github.com/cupy/cupy). +* without ui, with out object or class, pure function oriented designed. +* optimize cellpose 's pre-after processing and render algorithm, having a better performance and result. + +## Install +**pip install cellpose-planer** + +Option: *pip install cupy-cuda101* on envidia gpu, install cuda and cupy would get a large acceleration. + +# Usage +```python +import cellpose_planer as cellpp +from skimage.data import coins + +img = coins() +x = img.astype(np.float32)/255 + +net = cellpp.load_model('cyto_0') +flowpb, style = cellpp.get_flow(net, x, size=480) +lab = cellpp.flow2msk(flowpb, level=0.2) + +flowpb = cellpp.asnumpy(flowpb) +lab = cellpp.asnumpy(lab) +cellpp.show(img, flowpb, lab) +``` +![demo](https://user-images.githubusercontent.com/24822467/111028247-4d549580-83aa-11eb-9bf4-2cb87332530e.png) + +## first time: search and download models +search the models you need, and download them. (just one time) +```python +>>> import cellpose_planer as cellpp +>>> cellpp.search_models() +cyto_0 : -- +cyto_1 : -- +cyto_2 : -- +cyto_3 : -- +nuclei_0 : -- +nuclei_1 : -- +nuclei_2 : -- +nuclei_3 : -- + +>>> cellpp.download(['cyto_0', 'cyto_1', 'cyto_2', 'cyto_3']) +download cyto_0 from http://release.imagepy.org/cellpose-planer/cyto_0.npy +100%|█████████████████████████████████████| 100/100 [00:10<00:00, 2.37it/s] +download cyto_1 from http://release.imagepy.org/cellpose-planer/cyto_1.npy +100%|█████████████████████████████████████| 100/100 [00:10<00:00, 2.37it/s] +download cyto_2 from http://release.imagepy.org/cellpose-planer/cyto_2.npy +100%|█████████████████████████████████████| 100/100 [00:10<00:00, 2.37it/s] +download cyto_3 from http://release.imagepy.org/cellpose-planer/cyto_3.npy +100%|█████████████████████████████████████| 100/100 [00:10<00:00, 2.37it/s] + +>>> cellpp.list_models() +['cyto_0', 'cyto_1', 'cyto_2', 'cyto_3'] +``` + +## 1. load models +you can load one model or more, when multi models, you would get a mean output. +```python +nets = cellpp.load_model('cyto_0') +nets = cellpp.load_model(['cyto_0', 'cyto_1', 'cyto_2', 'cyto_3']) +``` + +## 2. get flow image +**def get_flow(nets, img, cn=[0,0], sample=1, size=512, tile=True, work=1, callback=progress)** + +* *nets:* the nets loaded upon. + +* *img:* the image to process + +* *cn:* the cytoplasm and nucleus channels + +* *sample:* if not 1, we scale it. (only avalible when tile==True) + +* *size:* when tile==True, this is the tile size, when tile==False, we scale the image to size. + +* *tile:* if True, method try to process image in tiles. else resize the image. + +* *work:* open multi-thread to process the image. (GPU not recommend) +```python +flowpb, style = cellpp.get_flow(net, coins(), [0,0], work=4) +``` + +## 3. flow to mask +**def flow2msk(flowpb, level=0.5, grad=0.5, area=None, volume=None)** + +* *flowpb:* get_flow 's output + +* *level:* below level means background, where water can not flow. So level decide the outline. + +* *grad:* if the flow gradient is smaller than this value, we set it 0. became a watershed. bigger gradient threshold could suppress the over-segmentation. especially in narrow-long area. + +* *area:* at end of the flow process, every watershed should be small enough. (volume), default is 0 (auto). + +```python +msk = cellpp.flow2msk(flowpb, level=0.5, grad=0.5, area=None, volume=None) +``` +## 4. render +cellpose-planer implements some render styles. +```python +import cellpose_planer as cellpp + +# get edge from label msask +edge = cellpp.msk2edge(lab) +# get build flow as hsv 2 rgb +hsv = cellpp.flow2hsv(flow) +# 5 colors render (different in neighborhood) +rgb = cellpp.rgb_mask(img, lab) +# draw edge as red line +line = cellpp.draw_edge(img, lab) +``` +![cell](https://user-images.githubusercontent.com/24822467/111029250-93acf300-83b0-11eb-9e83-41bc0cf045dd.png) + +## 5. backend and performance +Planer can run with numpy or cupy backend, by default, cellpose-planer try to use cupy backend, if failed, use numpy backend. But we can change the backend manually. (if you switch backend, the net loaded befor would be useless, reload them pleanse) +```python +import cellpose-planer as cellpp + +# use numpy and scipy as backend +import numpy as np +import scipy.ndimage as ndimg +cellpp.engine(np, ndimg) + +# use cupy and cupy.scipy as backend +import cupy as cp +import cupyx.scipy.ndimage as cpimg +cellpp.engine(cp, cpimg) +``` +here we time a 1024x1024 image on I7 CPU and 2070 GPU. +``` +user switch engine: numpy + net cost: 11.590 + flow cost: 0.0797 + +user switch engine: cupy + net cost: 0.0139 + flow cost: 0.009 +``` + +# Model deducing and releasing +Planer only has forward, so we need train the models in torch. then deduc it in planer. + +## deduce from torch +```python +# train in cellpose with torch, and export torch as onnx file. +from planer import onnx2planer +onnx2planer(xxx.onnx) +``` +then you would get a json file (graph structure), and a npy file (weights). + +## model releasing +if you want to share your model in cellpose-planer, just upload the json and npy file generated upon to any public container, then append a record in the **models list** tabel below, and give a pull request. +*infact, when we call cellpp.search_models, cellpp pull the text below and parse them.* + +## models list +| model name | auther | description | url | +| --- | --- | --- | --- | +| cyto_0 | carsen-stringer | [for cell cyto segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/cyto_0.npy) | +| cyto_1 | carsen-stringer | [for cell cyto segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/cyto_1.npy) | +| cyto_2 | carsen-stringer | [for cell cyto segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/cyto_2.npy) | +| cyto_3 | carsen-stringer | [for cell cyto segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/cyto_3.npy) | +| nuclei_0 | carsen-stringer | [for cell nuclear segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/nuclei_0.npy) | +| nuclei_1 | carsen-stringer | [for cell nuclear segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/nuclei_1.npy) | +| nuclei_2 | carsen-stringer | [for cell nuclear segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/nuclei_2.npy) | +| nuclei_3 | carsen-stringer | [for cell nuclear segmentation](http://www.cellpose.org/) | [download](http://release.imagepy.org/cellpose-planer/nuclei_3.npy) | + + *cellpp.search_models function pull the text below and parse them, welcom to release your models here!* + + ## Use cellpose-planer as ImagePy plugins + cellpose-planer can also start as ImagePy's plugins. supporting interactive and bat processing. + ![image](https://user-images.githubusercontent.com/24822467/111069844-ce339000-8483-11eb-9dce-caa8f6ab80af.png) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/CellPose.md b/imagepy/menus/Plugins/Contribute/Contributions/CellPose.md new file mode 100644 index 00000000..b2d2b2e3 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/CellPose.md @@ -0,0 +1,70 @@ +# cellpose-plgs + +**Path:** https://github.com/Image-Py/cellpose-plgs + +**Version:** 0.1 + +**Author:** YXDragon, Carsen-Stringer + +**Email:** yxdragon@imagepy.org + +**Keyword:** cellpose, segment, unet + +**Description:** cellpose is a generalist algorithm for cell and nucleus segmentation, this is a cellpose plugin for imagepy. + + + +## Document +### Install + +there are two method to install cellpose-plgs, Install Plugins or use Plugins Manager + +1. **Menus: Plugins > Install > Install Plugins** then input https://github.com/Image-Py/cellpose-plgs + + ![](http://skimgplgs.imagepy.org/cellpose/install.png) + + + +2. **Menus: Plugins > Manager > Plugins Manager**, befor you open the manager, you should update the software, (to getting the newest plugins catlog). for git version, please pull, for release version, please use *Menus: Plugins > Updata Software*. + + ![](http://skimgplgs.imagepy.org/cellpose/manager.png) + +when the cellpose-plgs is installed seccessfully, The cellpose would appears below the **Plugins** menus. + + + +### Usage + +**Menus: File > Open** to Open a Image, you can also use **Menus: File > Import > Import Sequence** to open a sequence. Then **Menus: Plugins > Cell Pose > Cell Pose Eval**, you would got a parameter dialog, Setting the parameter, then OK. + + + +**Parameters:** + +**model:** select model + +**cytoplasm:** select the cytomplasm channel. 0: gray, 1:red, 2:green, 3:blue + +**nucleus:** select the nucleus channel, if there is no, select 0. + +**show color flow:** the color flow result would be shown when checked. + +**show diams tabel:** the diams result would be shown when checked. + +**slice:** process the current image or all images when it is a sequence. + +![](http://skimgplgs.imagepy.org/cellpose/cellpose.png) + + + +### Analysis + +**Menus: Analysis > Region Analysis > Geometry Analysis** do a region analysis. And we can do many other things in ImagePy, such as set a colormap, or overlay the mask on the original image. + +![](http://skimgplgs.imagepy.org/cellpose/region.png) + + + +### Reference + +Thanks for CellPose and the supporting from Carsen Stringer and Marius Pachitariu. More detail about CellPose please read the [paper](https://t.co/4HFsxDezAP?amp=1) or watch the [talk](https://t.co/JChCsTD0SK?amp=1). \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/Demo Repo.md b/imagepy/menus/Plugins/Contribute/Contributions/Demo Repo.md new file mode 100644 index 00000000..d49a55c7 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/Demo Repo.md @@ -0,0 +1,141 @@ +# Demo Plugin + +**Path:** https://gitee.com/imagepy/demoplugin + +**Version:** 0.1 + +**Author:** YXDragon + +**Email:** yxdragon@imagepy.org + +**Keyword:** demo, tutorial + +**Description:** a friendly development tutorial. + +**[English Document](README.md) | [中文文档](READMECN.md)** + +*This is a demo project to show How to write ImagePy plugin. Including the usage of all kinds of plugin, with document wrote in detail. Developers can take this project as example.* + + + +## Install + +ImagePy Menu:`Plugins > Manager > Plugins Manager` input `demo`, and select the `Demo Plugin`, then click `Install/Update`. When complete the installing, the user interface would be changed. New plugins' menu, tool, and widget would be loaded in place. + +![06](http://idoc.imagepy.org/demoplugin/06.png) +
Install DemoPlugin

+ +## [Basic](doc/start.md) + +**[Start here](doc/start.md)** + +1. [What is plugin](doc/start.md#What-is-plugin) +2. [Hello World(my first plugin)](doc/start.md#Hello-World) +3. [Who Are You(interactive)](doc/start.md#Who-Are-You) +4. [Questionnaire(parameter dialog in detail)](doc/start.md#Questionnaire) +5. [Multi plugin in one file](doc/start.md#Implement-multi-plugins-in-one-file) + + + +## Plugin development + +**[Markdown: document](doc/markdown.md)** + +1. [Markdown Demo](doc/markdown.md#MarkDown-Demo) + +**[Macros: serialise existing function](doc/macros.md#Macros)** + +1. [Gaussian blur - Invert](doc/macros.md#Gaussian-Blur-Then-Invert) +2. [Coins Segmentation Macros](doc/macros.md#Coins-Segmentation) + +**[Workflow: interactive macros](doc/workflow.md)** + +1. [Coins Segment Workflow](doc/workflow.md#Coins-Segmentation-Workflow) + +**[Report: generate report](doc/report.md)** + +1. [Personal Information](doc/report.md#Personal-Information) +2. [Coins Report: report for coins segment](doc/report.md#Coins-Segmentation) +3. [Rule of Report design](doc/report.md#Report-template-design-principles) + +**[Filter: image filter in 2d](doc/filter.md)** + +1. [Invert Demo: without parameter](doc/filter.md#Invert) +2. [Gaussian Demo: with parameter](doc/filter.md#Gaussian) +3. [Filter operating mechanism](doc/filter.md#Filter-operating-mechanism) + +**[Simple: treat sequence and other attributes](doc/simple.md)** + +1. [Gaussian 3D Demo: filter in 3d](doc/simple.md#Gaussian3D) +2. [Red Lut Demo: operate color lookup table](doc/simple.md#SetLUT) +3. [ROI Inflate Demo: operate ROI](doc/simple.md#Inflate-ROI) +4. [Unit Demo: set unit and scale](doc/simple.md#Set-Scale-And-Unit) +5. [Draw Mark Demo: Set Mark](doc/simple.md#Mark) +6. [Simple operating mechanism](doc/simple.md#Simple-operating-mechanism) + +**[Table: treat dataframe](doc/table.md)** + +1. [Generate Table Demo: generate table](doc/table.md#Generate-score-list) +2. [Sort By Key Demo: sort](doc/table.md#Sort-by-field) +3. [Table Plot Demo: plot](doc/table.md#Bar-Chart) +4. [Table operation mechanism](doc/table.md#Table-operation-mechanism) + +**[Free: depend on nothing](doc/free.md)** + +1. [New Image Demo: creat image](doc/free.md#Create-image) +2. [About Demo: the about dialog](doc/free.md#About-dialog-box) +3. [Close Demo: quit program](doc/free.md#Quit) +4. [Free operating mechanism](doc/free.md#Free-operating-mechanism) + +**[Tool: mouse interaction](doc/tool.md)** + +1. [Painter Demo: draw with mouse](doc/tool.md#Brush-Tool) +2. [Tool operating mechanism](doc/tool.md#Tool-operating-mechanism) + +**[Widget: customed panel](doc/widget.md)** + +1. [Widget Demo](doc/widget.md#Widget-Demo) +2. [Widget opterating mechanism](doc/widget.md#Widget-opterating-mechanism) + + + +## [Plugin Release](doc/publish.md) + +**[Function Organization](doc/publish.md#Function-organization)** + +1. [Functional partitioning](doc/publish.md#Function-organization) +2. [Set Order](doc/publish.md#Set-Order) + +**[Plugin project creation](doc/publish.md#Plugin-project-creation)** + +1. [Create a plugin project repository](doc/publish.md#Plugin-project-creation) +2. [Write requirements](doc/publish.md#Plugin-project-creation) +3. [Write readme](doc/publish.md#Plugin-project-creation) +4. [Install Plugin](doc/publish.md#Plugin-project-creation) + +**[Release to ImagePy](doc/publish.md#Release-to-ImagePy)** + +1. [Send Pull Request to ImagePy](doc/publish.md#Release-to-ImagePy) +2. [About the top-level menu](doc/publish.md#Release-to-ImagePy) + + + +## [Write Document](doc/document.md) + +**[Write The Opteration Manual](doc/document.md#Write-The-Opteration-Manual)** + +**[View The Operation Manual](doc/document.md#View-Operation-Manual)** + + + +## [Attention](doc/attention.md#注意事项) + +**[User Friendliness](doc/attention.md#User-Friendliness)** + +**[Developer Friendliness](doc/attention.md#Developer-Friendliness)** + +**[Communicate Timely](doc/attention.md#Communicate-Timely)** + + + +**This document introduces how to write ImagePy plugin. More questions not exhaustive here,please post in [forum.Image.sc](https://forum.image.sc/)** \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/FluidSurface.md b/imagepy/menus/Plugins/Contribute/Contributions/FluidSurface.md new file mode 100644 index 00000000..206be6a1 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/FluidSurface.md @@ -0,0 +1,16 @@ +# FluidSurface + +**Path:** https://github.com/Image-Py/FluidSurface.git + +**Version:** 0.1 + +**Author:** YXDragon、Prevalenter + +**Email:** yxdragon@imagepy.org + +**Keyword:** predict, fluid + +**Description:** The predict plug-ins of imagepy + +## Document +... \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/IBook.md b/imagepy/menus/Plugins/Contribute/Contributions/IBook.md new file mode 100644 index 00000000..62e9be80 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/IBook.md @@ -0,0 +1,18 @@ +# IBook + +**Path:** https://gitee.com/imagepy/IBook + +**Version:** 0.1 + +**Author:** YXDragon + +**Email:** yxdragon@imagepy.org + +**Keyword:** book, tutorial + +**Description:** ImagePy's plugins to show some image processing method, which is friendly to beginner. + +you must fill the information upon, and you can not remove or insert line, you can write free below. + +## Document +... \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/OpenCV.md b/imagepy/menus/Plugins/Contribute/Contributions/OpenCV.md new file mode 100644 index 00000000..5d738cd9 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/OpenCV.md @@ -0,0 +1,293 @@ +# opencv-plgs + +**Path:** https://gitee.com/imagepy/opencv-plgs + +**Version:** 0.1 + +**Author:** YXDragon + +**Email:** yxdragon@imagepy.org + +**Keyword:** opencv + +**Description:** OpenCV plugin set for ImagePy + +you must fill the information upon, and you can not remove or insert line, you can write free below. + + +**Introduction:**opencv need not much introductions, It is a famous computer vision library. ImagePy is a interactive image processing framework which can wrap any numpy based library esaily. And supporting multi-channels, imagestack, lookuptable, roi, macros recorder...It is a Plugin system(just like ImageJ but more convenient). This project is a wrapper of opencv for ImagePy plugins! + +**Now It is just a start, I wrap little of opencv's algrism, aimed to introduct how to wrote ImagePy plugin, The Demo in this document is representative.** + +License +------- +I know many numpy based project has a BSD license, but, sorry, I use wxpython as ui framework, so, must be under LGPL. + +MainFrame +--------- +![mainframe](http://opencvplgs.imagepy.org/mainframe.png) + +It is ImagePy's MainFrame, like ImageJ. And ImagePy has contains many common function, such as open image, save image, do some filter, do a roi, draw with pencil... It requires wxpython as ui, Numpy as base structure, shapely to treat the roi, and scipy.ndimage to so dome common filter. But this project devotes to **do a wrapper for opencv**. + +Do a simple filter +------------------ +![simplefilter](http://opencvplgs.imagepy.org/laplacian.png) +```python +# -*- coding: utf-8 -* +import cv2 +from sciapp.action import Filter + +class Plugin(Filter): + title = 'Laplacian' + note = ['all', 'auto_msk', 'auto_snap'] + + def run(self, ips, snap, img, para = None): + return cv2.Laplacian(img, -1) +``` +### These gradient operator is simplest filter with no parameter. +1. class name must be Plugin +2. title is necessary, be the plugin's id, show in menus. +3. set the note, which tells ImagePy what to do for you. +4. overwrite run method return the result + +Filter is one of engines, means need a image, then do some change on it, It has a run method in such type: +* **ips** is the wrapper of image with some other information (lookup table, roi...) +* **snap** is a snapshot of the image, if 'auto_snap' in note, ImagePy will copy the image to snap befor run. (for many filter method must be implemented in a buffer) +* **img** is the current image you are processing. +* **para** is the parameter you got interactive. (there is no here) +### note is very important + +* **all** means this plugin works for all type image. +* **auto_snap** means ImagePy do a snapshot befor processing, then you can use Undo. +* **auto_msk** means when there is a roi on the image, Plugin will only influnce the pixel in. +* more detail information please see [ImagePy's README](https://github.com/Image-Py/imagepy)! + + +![colorfilter](http://opencvplgs.imagepy.org/colorfilter.png) +You see, we didnot write code to treat the color image, but it works, and We can draw a ROI on the image, only the ROI area be changed! And we can undo the lasted operation. **Even if it is a imagestack, ImagePy will ask you if run every slice!!** + +Filter with parameter +--------------------- +![canny](http://opencvplgs.imagepy.org/canny.png) +```python +# -*- coding: utf-8 -* +import cv2 +from sciapp.action import Filter + +class Plugin(Filter): + title = 'Canny' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'sigma':2, 'low':10, 'high':20} + view = [(float, (0,10), 1, 'sigma', 'sigma', 'pix'), + ('slide',(0,255), 0, 'low_threshold', 'low'), + ('slide',(0,255), 0, 'high_threshold', 'high')] + + def run(self, ips, snap, img, para = None): + l = int(para['sigma']*2.5)*2+1 + cv2.GaussianBlur(snap, (l, l), para['sigma'], dst=img) + return cv2.Canny(img, para['low'], para['high']) +``` +Many Filter need some parameter. Just like Canny. We just need do a little more. +1. **para** is a dict object, which contains the parameter you need. +2. **view** tell ImagePy how to interact when this plugin run, **(float, (0,10), 1, 'sigma', 'sigma', 'pix')** means it is a float between 0 and 10, title is sigma, corresponding to the sigma parameter with unit pix. +More detail information please see [ImagePy's README](https://github.com/Image-Py/imagepy)! + +**Add 'preview' in note, then when you adjust the parameter, ImagePy run this plugin immediately** + +![canny](http://opencvplgs.imagepy.org/threshold.png) +```python +# -*- coding: utf-8 -*- +from imagepy import IPy +import numpy as np, cv2 +from sciapp.action import Filter + +class Plugin(Filter): + title = 'Adaptive Threshold' + note = ['8-bit', 'auto_msk', 'auto_snap', 'preview'] + para = {'max':255, 'med':'mean', 'size':9, 'offset':2, 'inv':False} + view = [(int, (0, 255), 0, 'maxvalue', 'max', ''), + (list, ['mean', 'gauss'], str, 'method', 'med', ''), + (int, (3, 31), 0, 'blocksize', 'size', 'pix'), + (int, (0, 50), 0, 'offset', 'offset', ''), + (bool, 'binary invert', 'inv')] + + #process + def run(self, ips, snap, img, para = None): + med = cv2.ADAPTIVE_THRESH_MEAN_C if para['med']=='mean' else cv2.ADAPTIVE_THRESH_GAUSSIAN_C + mtype = cv2.THRESH_BINARY_INV if para['inv'] else cv2.THRESH_BINARY + cv2.adaptiveThreshold(snap, para['max'], med, para['inv'], para['size'], para['offset'], dst=img) +``` +Adaptive Threshold demo shows more data type, (choice, bool) + +**some method has a output parameter dst, just give the img and need not return(That will save memory, In fact ImagePy will copy the return to image, then update view)** + +Watershed with interactive marker +--------------------------------- +![interactive watershed](http://opencvplgs.imagepy.org/watershed.png) +```python +# -*- coding: utf-8 -* +from sciapp.action import Filter +import numpy as np, cv2 + +class Plugin(Filter): + title = 'Active Watershed' + note = ['rgb', 'req_roi', 'not_slice', 'auto_snap'] + + def run(self, ips, snap, img, para = None): + a, msk = cv2.connectedComponents(ips.get_msk().astype(np.uint8)) + msk = cv2.watershed(img, msk)==-1 + img //= 2 + img[msk] = 255 +``` +ImagePy support ROI, you can use tool to draw a roi(point, line, polygon...), And We can use ips.roi access it, and ips.get_msk(mode='in') to get the roi mask image. **mode can be 'in','out',or int means a sketch with specific width.** Then use the mask as marker to do a watershed, + +1. **req_roi** means this plugin need a roi, ImagePy will check for you, if ther is not, interrupt the plugin. +2. **not_slice** tells Imagepy need not to iterate slices if it is a stack, because this interactive is ok for specific image, there is no need to go through. + +**we can do watershed and get the result, then we can add some stroke where missed segment. then use Undo, and watershed again, we got a perfect result!** + +Interactive Grabcut +------------------- +this demo use build a Tool, and call a plugin in the tool's event. +![grabcut](http://opencvplgs.imagepy.org/grabcut.png) + +### Mark +mark is a overlay drawn on a image, It has draw method with parameter: +1. **dc** a wx dc contex. +2. **f** project from image coordinate to canvas coordinate. +3. **key** other parameter such as slice number. +```python +# -*- coding: utf-8 -* +from sciapp.action import Tool, Filter +import numpy as np, wx, cv2 + +class Mark(): + def __init__(self): + self.foreline, self.backline = [], [] + + def draw(self, dc, f, **key): + dc.SetPen(wx.Pen((255,0,0), width=2, style=wx.SOLID)) + for line in self.foreline: dc.DrawLines([f(*i) for i in line]) + dc.SetPen(wx.Pen((0,0,255), width=2, style=wx.SOLID)) + for line in self.backline: dc.DrawLines([f(*i) for i in line]) + + def line(self, img, line, color): + x0, y0 = line[0] + for x, y in line[1:]: + cv2.line(img, (int(x0), int(y0)), (int(x), int(y)), color, 2) + x0, y0 = x, y + + def buildmsk(self, shape): + img = np.zeros(shape[:2], dtype=np.uint8) + img[:] = 3 + for line in self.foreline: self.line(img, line, 1) + for line in self.backline: self.line(img, line, 0) + return img +``` +1. **draw** we need a foreground list and a background list, then draw in diffrent colors +2. **buildmsk** in the grabcut we need a method to build a mask. + +### Grabcut +```python +class GrabCut(Filter): + title = 'Grab Cut' + note = ['rgb', 'not_slice', 'auto_snap', 'not_channel'] + + def run(self, ips, snap, img, para = None): + msk = ips.mark.buildmsk(img.shape) + bgdModel = np.zeros((1,65),np.float64) + fgdModel = np.zeros((1,65),np.float64) + msk, bgdModel, fgdModel = cv2.grabCut(snap, msk,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK) + img[msk%2 == 0] //= 3 +``` +this is a Filter do the grabcut method, It get mask from the mark. + +### Tool +Tool is one of ImagePy's engines. you need implements these method: +1. **mouse_down** when mousedown, youcan got the current imageplus, the x, y. and which button is down, and some other informationg from key,(if ctrl, alt, shift is pressed...) +2. **mouse_up** like mouse_down +3. **mouse_move** like mouse_down +4. **mouse_wheel** like mouse_down + +```python +class Plugin(Tool): + title = 'Grabcut' + """FreeLinebuf class plugin with events callbacks""" + def __init__(self): + self.status = -1 + + def mouse_down(self, ips, x, y, btn, **key): + if not isinstance(ips.mark, Mark): + ips.mark = Mark() + if btn==1 and not key['ctrl']: + self.status = 1 + self.cur = [(x, y)] + ips.mark.foreline.append(self.cur) + if btn==1 and key['ctrl']: + del ips.mark.foreline[:] + del ips.mark.backline[:] + if btn==3 and not key['ctrl']: + self.status = 0 + self.cur = [(x, y)] + ips.mark.backline.append(self.cur) + if btn==3 and key['ctrl']: + GrabCut().start() + ips.update() + + def mouse_up(self, ips, x, y, btn, **key): + if self.status==1 and len(self.cur)==1: + ips.mark.foreline.remove(self.cur) + if self.status==0 and len(self.cur)==1: + ips.mark.backline.remove(self.cur) + self.status = -1 + ips.update() + + def mouse_move(self, ips, x, y, btn, **key): + if self.status!=-1: + self.cur.append((x, y)) + ips.update() + + def mouse_wheel(self, ips, x, y, d, **key): + pass +``` +**here we do these:** +1. put track in foreground list when move whith left button pressed. +2. put track in background list when move whith right button pressed. +3. clear foreground and background list if left click with ctrl pressed. +4. do grabcut when right click with ctrl pressed. + +**Tool files are stored in the sub-folder of tools, with a generated 16 * 16 thumbnail icon. The icon and the tool are stored in the same name as the gif file** + +Surf Demo +--------- +continued from the interactive threshold watershed demo +![fragment](http://opencvplgs.imagepy.org/surf.png) +this demo use surf feature to match two points and find the homo Matrix.(not in this project but in imagepy>plugin>surf) + +**we can use IPy.write, IPy.table to generate text log and data grid conviniently** + + +Macros +------ +![record](http://opencvplgs.imagepy.org/macros.png) + +**Macros** is one of engines, It is a text file with every line as: +**"PluginID > {parameter}"**, If the parameter is None and the Plugin need parameter, IPy will show dialog to interact, if the parameter is given, ImagePy just run use the given parameter. + +We can Open the **Plugin > Macros > Macros Recorder** to record the operate. Then save as a file with **.mc** extent under the menus folder. It will be parsed as a menu when started next. This is Macros, We never need to implements ourself. + +Then we Try the **Surf Demo** macros, Wow!, It run the command sequence automatically! + +**We can use Macros to do some bat processing, what more? It can be used as a good tutorial, We just implement the baseic method, and use macros to show this method can solve such problem!!!** + +About the plugin's order +------------------------ +![catlog](http://opencvplgs.imagepy.org/catlog.png) + +ImagePy is a plugin framework. The Catlog will be parsed as the corresponding menus. You just copy package under the menus folder or it's sub folder. But the question is, Our function will be in a disordered order. **So we can add a list called catlog under every init file.** + +![plugins](http://opencvplgs.imagepy.org/order.png) + +Now OpenCV menu is before the Help, and the Threshold is the first Item, then Filter, Segmentation, last is Demo. and if we put '-' in catlog, It will be parsed as a spliter line. + +**OK! That is a start, I want more developer can join. I think it is significative to let Opencv be esaier to approach, Benifit more scientists who does not master programming. But I cannot do it by myself, My English is not so good, and have little spare time, But I will do my best!** \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/Sea Ice.md b/imagepy/menus/Plugins/Contribute/Contributions/Sea Ice.md new file mode 100644 index 00000000..ef37c96f --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/Sea Ice.md @@ -0,0 +1,241 @@ +# Sea Ice Image Analysis + +**Path:** https://github.com/Image-Py/seaice + +**Version:** 0.1 + +**Author:** YXDragon + +**Email:** yxdragon@imagepy.org + +**Keyword:** seaice, rs + +**Description:** a toolkit developed for sea ice rs image processing + +you must fill the information upon, and you can not remove or insert line, you can write free below. + +[ImagePy](https://github.com/Image-Py/imagepy) is an image processing framework developed in Python. We can extend it with plugin esaily, This project is a toolkit developed for sea ice rs image processing. Please install [ImagePy](https://github.com/Image-Py/imagepy) first, then use **Plugins > Install > Install Plugins** then input this project's git address. Enter, then ImagePy will install this toolkit automotely.(maybe the gdal lib will got some trouble, please use whl [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#gdal)) + + + +**Paper About ImagePy on Bioinformatics**: + +[ImagePy: an open-source, Python-based and platform-independent software package for bioimage analysis](https://academic.oup.com/bioinformatics/advance-article/doi/10.1093/bioinformatics/bty313/4989871) + + + +## High Definition Segmentation + +This is a HD RS Image, Now we try to segmet it from each bright line and recognise each fragment is water or ice. You can follow the method below, And you can also use **Ice > HD Segment Demo**, you can got a macros like an animation. + +![](http://idoc.imagepy.org/ice/30.gif) + + + +**File > Open** to open a HD sea ice image, or you can use **Ice > HD Ice Image** to open our demo image below. + +![](http://idoc.imagepy.org/ice/22.png) + + + +**Process > Filter > Sobel** direction=both, then we got a edge gradient image. + +![](http://idoc.imagepy.org/ice/23.png) + + + +**Process > Filter > Gaussian** sigma=3, do a gaussian filter to smooth the gradient image. + +![](http://idoc.imagepy.org/ice/24.png) + + + +**Process > Math > Multiply** multiply 3, bright the image. + +![](http://idoc.imagepy.org/ice/25.png) + + + +**Process > Hydrology > Find Minimum** tolerance=10, find the local minimum, and use this points as rood for watershed. + +![](http://idoc.imagepy.org/ice/26.png) + + + +**Process > Hydrology > Watershed With ROI** got the watershed edge. + +![](http://idoc.imagepy.org/ice/27.png) + + + +**Edit > Invert** invert it then the region are in white. + +![](http://idoc.imagepy.org/ice/28.png) + + + +**Analysis > Region Analysis > Intensity Filter** use the segmentation image as result, and do a Intensity Analysis with the original image, because the ice is brighter then water, so we use every fragment's mean value to check if it is ice. + +![](http://idoc.imagepy.org/ice/29.png) + + + +then overlay the line on the original image, and set a color map, we got the result below. And we can also export the result as a gif animation. ImagePy can do these esaily, but it is not our key here. + +![](http://idoc.imagepy.org/ice/31.png) + + + +## Modis Image Segmentation + +Here we do segment with modis data. Modis data are saved as tiff file, which contains the geo project information. + +**Ice > Geo Tiff Open** you must use geo tiff open to open the modis data, if you use normal open method, you would not got the geo information. **Ice > Modis Test Data** can open some demo data. + +![](http://idoc.imagepy.org/ice/1.png) + + + +**Rectangle Select Tool** the first tool in toolbar, make a rectangle selection. + +![](http://idoc.imagepy.org/ice/2.png) + + + +**Ice > Duplicate With Projection** if use normal duplicate method, the geo information will be lost. + +![](http://idoc.imagepy.org/ice/3.png) + + + +**Ice > Load Geo Roi** the Geo ROI is the landedge, if you has a shapefile, you can use the landedge tu clip the image, if you did not have, I think it doesnot matter, the interactive segment method below can also exclude the land area. **Ice > Bohai Landedge** is a landedge acrroding the demo data. + +![](http://idoc.imagepy.org/ice/4.png) + + + +**Image > Clear Out** clear the land area + +![](http://idoc.imagepy.org/ice/5.png) + + + +**Grab Tool** which has a scissors icon, this tool is a wraper with opencv's grabcut, **move mouse, use left key to mark the ice(red), and left key to mark the water(blue) then click left key with ctrl pressed** we can got the red edge, this is our segmentation result. + +![](http://idoc.imagepy.org/ice/6.png) + + + +If we did not satisfy the result, we can use **ctrl + z** to undo, then repair the mark, then give a **left click with ctrl** again, util the result is OK. + +![](http://idoc.imagepy.org/ice/7.png) + + + +then **undo** again, and give a **left click with alt**, program will clear the water area. + +![](http://idoc.imagepy.org/ice/8.png) + + + +**Image > Type > 8-bit** transform to 8 bit gray image. + +![](http://idoc.imagepy.org/ice/9.png) + + + +**Process > Math > Max** bright the background, make it same as the water. + +![](http://idoc.imagepy.org/ice/10.png) + + + +**Process > Filter > UnSharp Mask** do a Unsharm Mask Enhance. + +![](http://idoc.imagepy.org/ice/11.png) + + + +**Image > Adjust > Threshold** do a threshold, then we got the binary mask. + +![](http://idoc.imagepy.org/ice/12.png) + + + +**Process > Binary > Binary Watershed** do a binary watershed, segment the binary mask in fragments. + +![](http://idoc.imagepy.org/ice/14.png) + + + +**Ice > Ice Statisticnt** statistic the all fragments, draw a area/Frequence graph. + +![](http://idoc.imagepy.org/ice/15.png) + + + +**Ice > Show Resultl** after the segmentation, we can make a colorful report, which show the Ice distribution. + +![](http://idoc.imagepy.org/ice/16.png) + +you can also use **Ice Export To Shapefile**, **Ice Export To WKT** to save the result as a shapefile or wkt file. + + + +## Geo Match and Differece Analysis + +**Ice > Geo Match** because we has geo project information, so we can match two images exactly. + +![](http://idoc.imagepy.org/ice/18.png) + + + +**then extract the ice area from the two images just like befor**. + +![](http://idoc.imagepy.org/ice/19.png) + + + +**Ice > Ice Differenc** we got a new image, which has 4 colors. both water, both ice, the new ice, water->ice, ice->water. + +![](http://idoc.imagepy.org/ice/20.png) + + + +choose a blue color map + +![](http://idoc.imagepy.org/ice/21.png) + + + +## Moving Detect + +Here are some thunder time series image data, we need detect the water's direction and velocity from the time series. You can follow the method below, And you can also use **Ice > Move Detect Demo**, you can got a macros like an animation. + +![](http://idoc.imagepy.org/ice/36.gif) + + + +**File > Import Sequence** to import image sequence, or you can use **Ice > Thunder Sequence** open the demo data. + +![](http://idoc.imagepy.org/ice/33.png) + + + +**Ice > Move Detec** moving detect use scikit-image's orb feature descripter do match within each slice, sample means the down sample scale, sigma means do a gaussian befor the feature extract, and std means the limit when count the affine matrix. + +![](http://idoc.imagepy.org/ice/34.png) + + + +then we got a table, each row means one transformation, and mark the current velocity and direction on the image. + +![](http://idoc.imagepy.org/ice/35.png) + + + +## ImagePy + +ImagePy could do many other things, the basic mathematical operations, filters, pixel statistics, 3D reconstruction and other functions, It is useful in biology, material, industry... here is a brief gallery. + +![](http://idoc.imagepy.org/ice/37.jpg) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Contributions/SimpleITK.md b/imagepy/menus/Plugins/Contribute/Contributions/SimpleITK.md new file mode 100644 index 00000000..f5b4ab4b --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Contributions/SimpleITK.md @@ -0,0 +1,260 @@ +# itk-plgs + +**Path:** https://gitee.com/imagepy/itk-plgs + +**Version:** 0.1 + +**Author:** YXDragon + +**Email:** yxdragon@imagepy.org + +**Keyword:** itk, segment + +**Description:** SimpleITK plugin set for ImagePy + +you must fill the information upon, and you can not remove or insert line, you can write free below. + +## Document + +**Introduction:** itk need not much introductions, It is a 2D/3D image segment library. ImagePy is a interactive image processing framework which can wrap any numpy based library esaily. And supporting multi-channels, imagestack, lookuptable, roi, macros recorder...It is a Plugin system(just like ImageJ but more convenient). This project is a wrapper of itk for ImagePy plugins! + +**Now It is just a start, I wrap little of itk's algrism, aimed to introduct how to wrote ImagePy plugin, The Demo in this document is representative.** + +License +------- +I know many numpy based project has a BSD license, but, sorry, I use SimpleITK, so must be under LGPL. + +MainFrame +--------- +![mainframe](http://idoc.imagepy.org/itk/mainframe.png) + +It is ImagePy's MainFrame, like ImageJ. And ImagePy has contains many common function, such as open image, save image, do some filter, do a roi, draw with pencil... It requires wxpython as ui, Numpy as base structure, shapely to treat the roi, and scipy.ndimage to so dome common filter. But this project devotes to **do a wrapper for itk** + +Add reader and writer +------------------------ +Itk supports many medical format, such as dicom, nii... Now let's add reader and writer plugins for ImagePy. +### first we add read, write function +as itk read any format as a image sequence, but sometimes we need read one slice. so we write a **readall**, then write a **read**. +```python +import SimpleITK as sitk +import numpy as np + +def readall(path): + image = sitk.ReadImage(path) + arr = sitk.GetArrayFromImage(image) + if arr.dtype == np.int16: + arr = arr.astype(np.int32) + return arr + +def read(path):return readall(path)[0] + +def write(path, img): + sitk.WriteImage(sitk.GetImageFromArray(img), path) +``` +### register reader and writer to the io manager +```python +from sciapp.action import dataio + +# add dicom reader and writer +dataio.add_reader(['dcm'], read) +dataio.add_writer(['dcm'], write) + +class OpenDCM(dataio.Reader): + title = 'DICOM Open' + filt = ['DCM'] + +class SaveDCM(dataio.Writer): + title = 'DICOM Save' + filt = ['DCM'] + +# add nii reader and writer, because nii is a sequence, so ruse read all, and give as a tuple. +dataio.add_reader(['nii'], (readall,)) +dataio.add_writer(['nii'], (write,)) + +class OpenNII(dataio.Reader): + title = 'NII Open' + filt = ['NII'] + +class SaveNII(dataio.Reader): + title = 'NII Save' + filt = ['NII'] + +plgs = [OpenDCM, SaveDCM, '-', OpenNII, SaveNII] +``` + +Do a simple filter +------------------ +![gradient](http://idoc.imagepy.org/itk/gradient.png) +```python +import SimpleITK as sitk +from sciapp.action import Filter + +class Plugin(Filter): + title = 'ITK Gradient Magnitude' + note = ['all', 'auto_msk', 'auto_snap'] + + def run(self, ips, snap, img, para = None): + img = sitk.GetImageFromArray(img) + img = sitk.GradientMagnitude(img) + return sitk.GetArrayFromImage(img) +``` +### These gradient operator is simplest filter with no parameter. +1. class name must be Plugin +2. title is necessary, be the plugin's id, show in menus. +3. set the note, which tells ImagePy what to do for you. +4. overwrite run method return the result + +Filter is one of engines, means need a image, then do some change on it, It has a run method in such type: +* **ips** is the wrapper of image with some other information (lookup table, roi...) +* **snap** is a snapshot of the image, if 'auto_snap' in note, ImagePy will copy the image to snap befor run. (for many filter method must be implemented in a buffer) +* **img** is the current image you are processing. +* **para** is the parameter you got interactive. (there is no here) +### note is very important + +* **all** means this plugin works for all type image. +* **auto_snap** means ImagePy do a snapshot befor processing, then you can use Undo. +* **auto_msk** means when there is a roi on the image, Plugin will only influnce the pixel in. +* more detail information please see [ImagePy's README](https://github.com/Image-Py/imagepy)! + +Filter with parameter +--------------------- +![gaussian](http://idoc.imagepy.org/itk/gaussian.png) +```python +# -*- coding: utf-8 -* +import SimpleITK as sitk +from sciapp.action import Filter + +class Plugin(Filter): + title = 'ITK Discrete Gaussian' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'sigma':1.0} + view = [(float, (0,10), 1, 'sigma', 'sigma', 'pix')] + + def run(self, ips, snap, img, para = None): + itkimg = sitk.GetImageFromArray(snap) + itkimg = sitk.DiscreteGaussian(itkimg, para['sigma']) + return sitk.GetArrayFromImage(itkimg) +``` +Many Filter need some parameter. Just like Gaussian. We just need do a little more. +1. **para** is a dict object, which contains the parameter you need. +2. **view** tell ImagePy how to interact when this plugin run, **(float, (0,10), 1, 'sigma', 'sigma', 'pix')** means it is a float between 0 and 10, title is sigma, corresponding to the sigma parameter with unit pix. +More detail information please see [ImagePy's README](https://github.com/Image-Py/imagepy)! + +**Add 'preview' in note, then when you adjust the parameter, ImagePy run this plugin immediately** + +Write 3D Filter +---------------- +![gaussian3d](http://idoc.imagepy.org/itk/gaussian3d.png) +```python +import SimpleITK as sitk +from sciapp.action import Simple + +class Plugin(Simple): + title = 'ITK Gradient Magnitude 3D' + note = ['all', 'stack3d'] + + def run(self, ips, imgs, para = None): + itkimgs = sitk.GetImageFromArray(imgs) + itkimgs = sitk.GradientMagnitude(itkimgs) + imgs[:] = sitk.GetArrayFromImage(itkimgs) +``` +when there is a image sequence, If you run a gaussian filter, it will ask if you want to process every slice. If ok, it will process slice by slice. But a 3d gaussian filter will blur the image sequence by **X, Y and Z axis**. + +**Filter** aimed at treat a single slice, but if you want to process the whole images, please extends a **Simple**. It also can process other information. eg. set the look up table, or treat the roi, or save the current image/image sequence. + +Treat ROI and ColorImage +----------------------------- +![roicolor](http://idoc.imagepy.org/itk/roicolor.png) +```python +import SimpleITK as sitk +from sciapp.action import Filter, Simple +import numpy as np + +class Plugin(Filter): + title = 'ITK Canny EdgeDetection' + note = ['all', 'auto_msk', 'auto_snap', '2float', 'preview'] + para = {'sigma':1.0, 'low_threshold':10, 'high_threshold':20} + view = [(float, (0,10), 1, 'sigma', 'sigma', 'pix'), + ('slide',(0,50), 'low_threshold', 'low_threshold',''), + ('slide',(0,50), 'high_threshold', 'high_threshold','')] + + def run(self, ips, snap, img, para = None): + img = sitk.GetImageFromArray(snap) + img = sitk.CannyEdgeDetection(img, para['low_threshold'], para['high_threshold'], [para['sigma']]*2) + return sitk.GetArrayFromImage(img)*ips.range[1] +``` +You see, we did not write code to treat the color image, but it works, and We can draw a ROI on the image, only the ROI area be changed! And we can undo the lasted operation. **Even if it is a imagestack, ImagePy will ask you if run every slice!!** + +Watershed With ROI +---------------------- +![roiwatershed](http://idoc.imagepy.org/itk/roiwatershed.png) +```python +import SimpleITK as sitk +from sciapp.action import Filter, Simple +import numpy as np + +class Plugin(Filter): + title = 'ITK Watershed Manual Marker' + note = ['8-bit', 'not_slice', 'auto_snap', 'req_roi'] + + para = {'sigma':2} + view = [(int, (0,10), 0, 'sigma', 'sigma', 'pix')] + + def run(self, ips, snap, img, para = None): + itkimg = sitk.GetImageFromArray(img) + itkimg = sitk.DiscreteGaussian(itkimg, para['sigma']) + itkimg = sitk.GradientMagnitude(itkimg) + itkmarker = sitk.GetImageFromArray(ips.get_msk().astype(np.uint16)) + itkmarker = sitk.ConnectedComponent(itkmarker, fullyConnected=True) + lineimg = sitk.MorphologicalWatershedFromMarkers(itkimg, itkmarker, markWatershedLine=True) + labels = sitk.GetArrayFromImage(lineimg) + return np.where(labels==0, ips.range[1], 0) +``` + +ImagePy support ROI, you can use tool to draw a roi(point, line, polygon...), And We can use ips.roi access it, and ips.get_msk(mode='in') to get the roi mask image. **mode can be 'in','out',or int means a sketch with specific width.** Then use the mask as marker to do a watershed, + +1. **req_roi** means this plugin need a roi, ImagePy will check for you, if ther is not, interrupt the plugin. +2. **not_slice** tells Imagepy need not to iterate slices if it is a stack, because this interactive is just ok for specific image, there is no need to go through. + +Watershed3D And Surface Reconstruct +------------------------------------------ +![surface](http://idoc.imagepy.org/itk/3dsurface.png) + +we can also do a 3d watershed, **mark the up and down as two markers**, then **watershed on the 3d gradient image**. we can get a perfect mask. Then do a 3d surface reconstruction with **vtk(mayavi)**. + +Macros +-------- +![macros](http://idoc.imagepy.org/itk/macros.png) + +**Macros** is one of engines, It is a text file with every line as: +**"PluginID > {parameter}"**, If the parameter is None and the Plugin need parameter, IPy will show dialog to interact, if the parameter is given, ImagePy just run use the given parameter. + +We can Open the **Plugin > Macros > Macros Recorder** to record the operate. Then save as a file with **.mc** extent under the menus folder. It will be parsed as a menu when started next. This is Macros, We never need to implements ourself. + +Then we Try the **Find And Mark Coins** macros, Wow!, It run the command sequence automatically! + +**We can use Macros to do some bat processing, what more? It can be used as a good tutorial, We just implement the basic method, and use macros to show this method can solve such problem!!!** + +MarkDown +------------ +you can also write a markdown file, and lay it under any sub folder of menus, when ImagePy setup, It will be loaded as a menus too, when click it, the markdown page will show. + +About the plugin's order +------------------------ +![order](http://idoc.imagepy.org/itk/order.png) + +ImagePy is a plugin framework. The Catlog will be parsed as the corresponding menus. You just copy package under the menus folder or it's sub folder. But the question is, Our function will be in a disordered order. **So we can add a list called catlog under every init file.** + +Now ITK menu is before the Help, and the IO is the first Item, then Filters, Features, Segmentation. and if we put '-' in catlog, It will be parsed as a spliter line. + +What can ImagePy do +------------------------ +![view](http://idoc.imagepy.org/itk/view.png) + +**ImagePy** can wrap any numpy based libraries, can generate table esaily. In the basic version, It contains scikit-image, and I want to build a opencv-plgs and a itk-plgs. then integrate them, **It will be powerful than Fiji**, and be more esaily to extend. + +**OK! That is a start, I want more developer can join. I think it is significative to let ITK be esaier to approach, Benifit more scientists who does not master programming. But I cannot do it by myself, My English is not so good, and have little spare time, But I will do my best!** + +Something Imperfect +------------------- +**as simple itk cannot treat numpy array directly, so we must use GetImageFromArray and GetArrayFromImage, Did you have any better method?** \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/Site Plugins List.md b/imagepy/menus/Plugins/Contribute/Site Plugins List.md new file mode 100644 index 00000000..817d547a --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/Site Plugins List.md @@ -0,0 +1,9 @@ +# Site Plugins Catlog + +| name | version | author | mail | keyword | description | +| --- | --- | --- | --- | --- | --- | +| [IBook](https://github.com/Image-Py/IBook) | 0.1 | yxdragon | yxdragon@imagepy.org | book, tutorial | ImagePy's plugins to show some image processing method, which is friendly to beginner. | +| [cellpose-planer](https://github.com/Image-Py/cellpose-planer) | 0.1 | yxdragon | yxdragon@imagepy.org | cellpose, segment | generalist algorithm for cell and nucleus segmentation | +| [Demo Plugin](https://github.com/Image-Py/demoplugin) | 0.1 | yxdragon | yxdragon@imagepy.org | demo, tutorial | This is a demo project to show How to write ImagePy plugin. Including the usage of all kinds of plugin, with document wrote in detail. Developers can take this project as example. | +| [OpenCV](https://github.com/Image-Py/opencv-plgs) | 0.1 | yxdragon | yxdragon@imagepy.org | opencv | OpenCV plugin set for ImagePy | +| [SimpleITK](https://github.com/Image-Py/itk-plgs) | 0.1 | yxdragon | yxdragon@imagepy.org | itk, segment | SimpleITK plugin set for ImagePy | \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/__init__.py b/imagepy/menus/Plugins/Contribute/__init__.py new file mode 100644 index 00000000..e66d945b --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/__init__.py @@ -0,0 +1 @@ +catlog = ['Contribute Document', 'Site Plugins List', 'update_plg', '-', 'pmanager_wgt', 'Contributions'] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/pmanager_wgt.py b/imagepy/menus/Plugins/Contribute/pmanager_wgt.py new file mode 100644 index 00000000..5955bad5 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/pmanager_wgt.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Jan 7 16:01:14 2017 + +@author: yxl +""" +import wx, os, glob, shutil, random +from imagepy import root_dir +from sciwx.text import MDPad +from sciapp.action import Macros +#from imagepy.ui.mkdownwindow import HtmlPanel, md2html + +class VirtualListCtrl(wx.ListCtrl): + def __init__(self, parent, title, data=[]): + wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL) + self.title, self.data = title, data + #self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems) + for col, text in enumerate(title): + self.InsertColumn(col, text) + self.set_data(data) + + def OnGetItemText(self, row, col): + return self.data[row][col] + + def OnGetItemAttr(self, item): return None + + def OnGetItemImage(self, item): return -1 + + def set_data(self, data): + self.data = data + self.SetItemCount(len(data)) + + def refresh(self): + self.SetItemCount(len(self.data)) + +def parse(path): + f = open(path, encoding='utf-8') + body = {'file':path} + try: + line = f.readline() + if line[0] == '#':body['name'] = line.split('#')[-1].strip() + while line: + line = f.readline() + if line.startswith('**Path:'): body['path'] = line.split('**')[-1].strip() + if line.startswith('**Version:'): body['version'] = line.split('**')[-1].strip() + if line.startswith('**Author:'): body['author'] = line.split('**')[-1].strip() + if line.startswith('**Email:'): body['email'] = line.split('**')[-1].strip() + if line.startswith('**Keyword:'): body['keyword'] = line.split('**')[-1].strip() + if line.startswith('**Description'): body['Description'] = line.split('**')[-1].strip() + f.close() + except: body = [0] + finally: f.close() + return None if len(body)!=8 else body + +class Plugin( wx.Panel ): + title = 'Plugins Manager' + single = None + def __init__( self, parent, app=None): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, + pos = wx.DefaultPosition, size = wx.Size( 600,300 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer( wx.HORIZONTAL) + bSizer1 = wx.BoxSizer( wx.VERTICAL ) + bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) + self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "Search:", + wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText1.Wrap( -1 ) + bSizer2.Add( self.m_staticText1, 0, wx.ALL|wx.EXPAND, 5 ) + self.txt_search = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, + wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer2.Add( self.txt_search, 1, wx.ALL, 5 ) + self.btn_update = wx.Button( self, wx.ID_ANY, 'Refresh List Online', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + bSizer2.Add( self.btn_update, 0, wx.ALL, 5 ) + + bSizer3 = wx.BoxSizer( wx.HORIZONTAL ) + self.btn_install = wx.Button( self, wx.ID_ANY, 'Install/Update', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_uninstall = wx.Button( self, wx.ID_ANY, 'Remove', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.chk_has = wx.CheckBox( self, wx.ID_ANY, 'only installed', wx.DefaultPosition, wx.DefaultSize, 0 ) + + bSizer3.Add(self.chk_has, 0, wx.ALL|wx.EXPAND, 5) + bSizer3.AddStretchSpacer(1) + bSizer3.Add( self.btn_install, 0, wx.ALL, 5) + bSizer3.Add( self.btn_uninstall, 0, wx.ALL, 5) + bSizer1.Add( bSizer2, 0, wx.EXPAND, 5) + self.lst_plgs = VirtualListCtrl( self, ['Name', 'Author', 'Version', 'Status']) + self.lst_plgs.SetColumnWidth(0,100) + self.lst_plgs.SetColumnWidth(1,100) + self.lst_plgs.SetColumnWidth(2,60) + self.lst_plgs.SetColumnWidth(3,60) + self.htmlpanel = MDPad(self) + bSizer1.Add( self.lst_plgs, 1, wx.LEFT|wx.RIGHT|wx.EXPAND, 5 ) + bSizer1.Add( bSizer3, 0, wx.EXPAND, 5 ) + sizer.Add(bSizer1, 0, wx.ALL|wx.EXPAND, 0) + sizer.Add(self.htmlpanel, 1, wx.ALL|wx.EXPAND, 5 ) + + self.SetSizer( sizer ) + self.Layout() + self.Centre( wx.BOTH ) + # Connect Events + self.txt_search.Bind( wx.EVT_TEXT, self.on_search) + self.lst_plgs.Bind( wx.EVT_LIST_ITEM_SELECTED, self.on_run) + self.btn_update.Bind(wx.EVT_BUTTON, self.on_update) + self.btn_install.Bind(wx.EVT_BUTTON, self.on_install) + self.btn_uninstall.Bind(wx.EVT_BUTTON, self.on_remove) + self.chk_has.Bind( wx.EVT_CHECKBOX, self.on_check) + self.app = app + self.load() + + #def list_plg(self, lst, items + def load(self): + here = os.path.abspath(os.path.dirname(__file__)) + has = glob.glob(os.path.join(root_dir,'plugins/*/*.md')) + fs = glob.glob(here+'/Contributions/*.md') + prjs = [p for p in [parse(i) for i in fs] if not p is None] + has = [p for p in [parse(i) for i in has] if not p is None] + keys = set([i['path'] for i in prjs]) + for i in has: + if not i['path'] in keys: prjs.append(i) + prjs = sorted([(i['name']+str(random.random()), i) for i in prjs]) + self.prjs = [i[1] for i in prjs] + + for i in self.prjs: + for j in has: + if i['path'] == j['path']: + i['old'] = j['version'] + i['folder'] = os.path.split(j['file'])[0] + self.on_search(None) + + # Virtual event handlers, overide them in your derived class + def on_search( self, event ): + wd = self.txt_search.GetValue() + f = lambda x: '' if not 'old' in x else ['update', 'installed'][x['old']==x['version']] + self.buf = [[i['name'], i['author'], i['version'], f(i), i] + for i in self.prjs if wd.lower() in str(i).lower()] + if self.chk_has.GetValue(): self.buf = [i for i in self.buf if i[3]!=''] + self.lst_plgs.set_data(self.buf) + self.lst_plgs.Refresh() + + def on_update(self, event): + Macros('', ['Update Plugins List>None']).start(self.app, callafter=self.load) + + def on_run(self, event): + f = open(self.buf[event.GetIndex()][-1]['file'], encoding='utf-8') + cont = f.read() + f.close() + cont = '\n'.join([i.strip() for i in cont.split('\n')]) + self.htmlpanel.set_cont(cont) + + def on_install(self, event): + i = self.lst_plgs.GetFirstSelected() + if i==-1: return + path = self.buf[i][-1]['path'] + self.app.plugin_manager.get('Install Plugins').para['repo'] = path + self.app.plugin_manager.get('Install Plugins')().start( + self.app, None, self.load) + + def on_remove(self, event): + i = self.lst_plgs.GetFirstSelected() + if i==-1: return + shutil.rmtree(self.buf[i][-1]['folder']) + self.app.load_all() + self.load() + + def on_check(self, event): self.load() + +if __name__ == '__main__': + from glob import glob + fs = glob('Contributions/*.md') + for i in fs: print(parse(i)) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Contribute/update_plg.py b/imagepy/menus/Plugins/Contribute/update_plg.py new file mode 100644 index 00000000..81275d87 --- /dev/null +++ b/imagepy/menus/Plugins/Contribute/update_plg.py @@ -0,0 +1,19 @@ +from sciapp.action import Free +import sys, re, os.path as osp +from urllib.request import urlretrieve, urlopen + +class Plugin(Free): + title = 'Update Plugins List' + + def run(self, para = None): + try: + here = osp.abspath(osp.dirname(__file__)) + url = 'https://gitee.com/imagepy/imagepy/tree/master/imagepy/menus/Plugins/Contribute' + temp = re.compile('imagepy/imagepy/blob/master/imagepy/menus/Plugins/Contribute/Contributions/.*?md') + rst = urlopen(url+'/Contributions').read().decode('utf-8') + records = ['https://gitee.com/'+i.replace('blob', 'raw') for i in temp.findall(rst)] + for i in records: urlretrieve(i, osp.join(here, 'Contributions', osp.split(i)[-1].replace('%20',' '))) + urlretrieve(url.replace('tree', 'raw')+'/Site%20Plugins%20List.md', osp.join(here, 'Site Plugins List.md')) + self.app.alert('site plugins list updated!') + except Exception as e: + self.app.alert('update failed!\tErrof:%s'%sys.exc_info()[1]) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Cyclic Wave/__init__.py b/imagepy/menus/Plugins/Cyclic Wave/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Plugins/Cyclic Wave/dewave_plg.py b/imagepy/menus/Plugins/Cyclic Wave/dewave_plg.py new file mode 100644 index 00000000..e5b3233c --- /dev/null +++ b/imagepy/menus/Plugins/Cyclic Wave/dewave_plg.py @@ -0,0 +1,25 @@ +import numpy as np +from sciapp.action import Filter +from imagepy.ipyalg.transform import transform +from numpy.fft import fft2, ifft2, fftshift, ifftshift + +class Plugin(Filter): + title = 'Depress Cyclic Wave' + note = ['all', 'auto_msk', 'auto_snap','preview'] + para = {'gap':0.01, 'lim':0.01} + view = [(float, 'lim', (0.001, 0.1), 3, 'limit', 'width'), + (float, 'gap', (0.001, 0.1), 3, 'gap', 'width')] + + def run(self, ips, snap, img, para = None): + lim, gap = para['lim'], para['gap'] + poimg = transform.linear_polar(snap) + h, w = poimg.shape[:2] + lim = max(1, int(w*lim)) + gap = max(1, int(gap*h)) + fftpoimg = fftshift(fft2(poimg)) + fftpoimg[:h//2-gap,w//2-lim:w//2+lim] = 0 + fftpoimg[h//2+gap:,w//2-lim:w//2+lim] = 0 + poimg = ifft2(ifftshift(fftpoimg)) + poimg = np.clip(poimg.real, snap.min(), snap.max()) + poimg = poimg.astype(snap.dtype) + transform.polar_linear(poimg, output=img[:,:,None]) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Games/crossstick_plg.py b/imagepy/menus/Plugins/Games/crossstick_plg.py new file mode 100644 index 00000000..ba45a200 --- /dev/null +++ b/imagepy/menus/Plugins/Games/crossstick_plg.py @@ -0,0 +1,24 @@ +from sciapp.action import Filter +import numpy as np +from scipy.cluster.vq import kmeans, vq + +class Plugin(Filter): + title = 'Cross Stick' + + note = ['rgb', 'not_channel', 'auto_msk', 'auto_snap', 'preview'] + para = {'block':8, 'k':12, 'grid':False} + + view = [(int, 'k', (1,100), 0, 'k', ''), + (int, 'block', (5, 50), 0, 'block', ''), + (bool, 'grid', 'show grid')] + + def run(self, ips, snap, img, para = None): + k, block = para['k'], para['block'] + buf = snap[::block,::block].copy() + pts = buf.reshape((-1,3)).astype(np.float32) + ms = kmeans(pts, k)[0] + buf[:] = ms[vq(pts, ms)[0]].reshape(buf.shape) + xs, ys = np.where(img[:,:,0]>-1) + img[xs, ys] = buf[xs//block, ys//block] + if para['grid']: + img[::block], img[:,::block] = 0, 0 \ No newline at end of file diff --git a/imagepy/menus/Plugins/Games/drawstep_plg.py b/imagepy/menus/Plugins/Games/drawstep_plg.py index 19b514f3..423486d9 100644 --- a/imagepy/menus/Plugins/Games/drawstep_plg.py +++ b/imagepy/menus/Plugins/Games/drawstep_plg.py @@ -1,11 +1,11 @@ -from scipy.misc import imread +from skimage.io import imread import matplotlib.pyplot as plt from skimage.morphology import skeletonize -from scipy.ndimage import distance_transform_edt +from imagepy.ipyalg import distance_transform_edt import numpy as np from imagepy.ipyalg.graph import sknw -from imagepy.core.engine import Simple -from imagepy import IPy +from sciapp.action import Simple + def draw_pixs(img, xs, ys, color=None): mskx = (xs>=0) * (xsips.img.shape[0]: return + if c<0 or c>ips.img.shape[1]: return + if ips.img[r,c] == 0: return + lab, n = label(ips.img>0) + ips.img[lab==lab[r,c]] = (128,255)[ips.img[r,c]!=255] + ips.update() + if btn == 3: + img = getscr(ips.img, self.size) + for i in range(len(ips.imgs)): + ips.imgs[i][:] = generate(img, self.size) + img = run(img) + self.app.alert('Complete!') + +class Plugin(Free): + title = 'Game Of Life' + para = {'name':'Game01','width':15, 'height':15, 'size':30,'slice':30} + view = [(str, 'name', 'name', ''), + (int, 'width', (1,2048), 0, 'width', 'pix'), + (int, 'height', (1,2048), 0, 'height', 'pix'), + (int, 'size', (10, 50), 0, 'size', ''), + (int, 'slice', (1,100), 0, 'slice', '')] + + #process + def run(self, para = None): + first = generate(np.zeros((para['height'], para['width'])), para['size']) + imgs = [first.copy() for i in range(para['slice'])] + ips = Image(imgs, para['name']) + ips.tool = Painter(para['size']).start(self.app, 'local') + self.app.show_img(ips) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Install/installpkg_plgs.py b/imagepy/menus/Plugins/Install/installpkg_plgs.py index 793b093c..87cecc0f 100644 --- a/imagepy/menus/Plugins/Install/installpkg_plgs.py +++ b/imagepy/menus/Plugins/Install/installpkg_plgs.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from imagepy import IPy -from imagepy.core.engine import Free +from sciapp.action import Free import subprocess, sys import pandas as pd @@ -22,7 +21,7 @@ def run(self, para=None): p = subprocess.Popen('%s -m pip list'%sys.executable, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True) lst = str(p.stdout.read(), encoding='utf-8').replace('\r\n', '\n').split('\n') - IPy.show_table(pd.pandas([[i] for i in lst], columns=['Packages']), 'Packages') + self.app.show_table(pd.DataFrame([[i] for i in lst], columns=['Packages']), 'Packages') plgs = [Install, List] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Install/installplg_plgs.py b/imagepy/menus/Plugins/Install/installplg_plgs.py index a5752f63..8cb34c16 100644 --- a/imagepy/menus/Plugins/Install/installplg_plgs.py +++ b/imagepy/menus/Plugins/Install/installplg_plgs.py @@ -1,65 +1,32 @@ # -*- coding: utf-8 -*- -from imagepy import IPy, root_dir -from imagepy.core.engine import Free -import os, subprocess, zipfile, shutil - -import zipfile, sys, urllib -path = 'https://github.com/Image-Py/imagepy/archive/master.zip' - -if sys.version_info[0]==2: - from urllib import urlretrieve - from cStringIO import StringIO -else: - from urllib.request import urlretrieve - from io import BytesIO as StringIO +from imagepy import root_dir +from sciapp.action import Free +import os, shutil, sys, subprocess +from dulwich import porcelain path_plgs = os.path.join(root_dir, 'plugins') -path_cache = os.path.join(path_plgs, 'cache') -if not os.path.exists(path_plgs): - os.mkdir(path_plgs) -if not os.path.exists(path_cache): - os.mkdir(path_cache) +if not os.path.exists(path_plgs): os.mkdir(path_plgs) def Schedule(a,b,c, plg): per = 100.0 * a * b / c if per > 100 : per = 100 - print('%-3d%%'%per) plg.progress(int(per), 100) + if c==-1: plg.prgs = None class Install(Free): title = 'Install Plugins' - para = {'pkg':''} - prgs = (0, 100) - view = [('lab', None, 'input a zipfile url or github url as http://github.com/username/project'), - (str, 'pkg', 'package', '')] + para = {'repo':'https://github.com/Image-Py/IBook'} + view = [('lab', None, 'input git url as http://github.com/username/project'), + (str, 'repo', 'package', '')] def run(self, para=None): - url = para['pkg'] - if 'github.com' in url: - if url[-4:] == '.git': - url = url.replace('.git', '/archive/master.zip') - elif url[-4:] != '.zip': - url = url + '/archive/master.zip' - domain, name = url.split('/')[-4:-2] - else: - domain, name = (url[:-4].replace('.','-')).split('/')[-2:] - domain, name = domain.replace('_', '-'), name.replace('_', '-') - - IPy.set_info('downloading plugin from %s'%para['pkg']) - urlretrieve(url, os.path.join(path_cache, domain+'_'+name+'.zip'), - lambda a,b,c, p=self: Schedule(a,b,c,p)) - zipf = zipfile.ZipFile(os.path.join(path_cache, domain+'_'+name+'.zip')) - folder = zipf.namelist()[0] - zipf.extractall(path_cache) - destpath = os.path.join(path_plgs, domain+'_'+folder).replace('-master','') - if os.path.exists(destpath): shutil.rmtree(destpath) - os.rename(os.path.join(path_cache, folder), destpath) - zipf.close() - IPy.set_info('installing requirement liberies') - self.prgs = (None, 1) - cmds = [sys.executable, '-m', 'pip', 'install', '-r', '%s/requirements.txt'%destpath] + path = os.path.join(path_plgs, os.path.split(para['repo'])[-1]) + porcelain.clone(para['repo'], path, depth=1).close() + shutil.rmtree(os.path.join(path, '.git')) + self.app.info('installing requirement liberies') + cmds = [sys.executable, '-m', 'pip', 'install', '-r', '%s/requirements.txt'%path] subprocess.call(cmds) - IPy.reload_plgs(True, True, True, True) + self.app.load_all() class List(Free): title = 'List Plugins' diff --git a/imagepy/menus/Plugins/Macros/recorder_plg.py b/imagepy/menus/Plugins/Macros/recorder_plg.py index cca7a29c..c34a9f82 100644 --- a/imagepy/menus/Plugins/Macros/recorder_plg.py +++ b/imagepy/menus/Plugins/Macros/recorder_plg.py @@ -1,23 +1,24 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 28 23:24:43 2016 +from sciapp.action import dataio -@author: yxl -""" -import wx -from imagepy.core.engine import Free, Macros -from imagepy import IPy -class Plugin(Free): +def readmc(path): + with open(path) as f: return f.readlines() + +dataio.ReaderManager.add('mc', readmc, 'mc') + +class Macros(dataio.Reader): title = 'Run Macros' - para = {'path':''} - - def show(self): - filt = 'Macros files (*.mc)|*.mc' - return IPy.getpath('open..', filt, 'open', self.para) - - def run(self, para = None): - f = open(para['path']) - lines = f.readlines() - f.close() - Macros('noname', lines).start() \ No newline at end of file + tag = 'mc' + filt = ['MC'] + +def readwf(path): + with open(path) as f: return f.read() + +dataio.ReaderManager.add('wf', readwf, 'wf') + +class WorkFlow(dataio.Reader): + title = 'Run WorkFlow' + tag = 'wf' + filt = ['wf'] + +plgs = [Macros, WorkFlow] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Macros/recorder_wgt.py b/imagepy/menus/Plugins/Macros/recorder_wgt.py index c6877000..a8c32a8f 100644 --- a/imagepy/menus/Plugins/Macros/recorder_wgt.py +++ b/imagepy/menus/Plugins/Macros/recorder_wgt.py @@ -1,11 +1,11 @@ import wx, weakref -from imagepy.core.engine import Macros +from sciapp.action import Macros import os.path as osp class Plugin ( wx.Panel ): title = 'Macros Recorder' - def __init__( self, parent ): + def __init__( self, parent, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(300, 200), style = wx.TAB_TRAVERSAL ) bSizer1 = wx.BoxSizer( wx.VERTICAL ) @@ -61,15 +61,12 @@ def __init__( self, parent ): self.Bind( wx.EVT_TOOL, self.on_runlines, id = self.tol_runlines.GetId() ) - self.recording = True - - def __del__(self): - print('Recoder closed!') - + self.recording = True # Virtual event handlers, overide them in your derived class def on_open( self, event ): - dialog=wx.FileDialog(None,'wxpython Notebook(o)',style=wx.FD_OPEN) + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in ['mc']]) + dialog=wx.FileDialog(None, 'Open Macros', '', '', filt, style=wx.FD_OPEN) if dialog.ShowModal()==wx.ID_OK: self.file=dialog.GetPath() file=open(self.file) @@ -78,9 +75,9 @@ def on_open( self, event ): dialog.Destroy() def on_save( self, event ): - print('save') if self.file=='': - dialog=wx.FileDialog(None,'wxpython Notebook(s)',style=wx.FD_SAVE) + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in ['mc']]) + dialog=wx.FileDialog(None,'Save Macros', '', '', filt, style=wx.FD_SAVE) if dialog.ShowModal()==wx.ID_OK: self.file=dialog.GetPath() self.txt_cont.SaveFile(self.file) @@ -102,7 +99,7 @@ def on_delete( self, event ): def on_run( self, event ): cmds = self.txt_cont.GetValue().split('\n') - Macros(None, cmds).start() + Macros(None, cmds).start(self.GetParent().GetParent()) def on_record( self, event ): self.recording = True @@ -112,7 +109,7 @@ def on_pause( self, event ): def on_runlines( self, event ): cmds = self.txt_cont.GetStringSelection().split('\n') - Macros(None, cmds).start() + Macros(None, cmds).start(self.GetParent().GetParent()) def write(self, cont): if not self.recording: return diff --git a/imagepy/menus/Plugins/Manager/__init__.py b/imagepy/menus/Plugins/Manager/__init__.py index 3ce19795..c90fdd97 100644 --- a/imagepy/menus/Plugins/Manager/__init__.py +++ b/imagepy/menus/Plugins/Manager/__init__.py @@ -1 +1 @@ -catlog = ['plgtree_wgt', 'toltree_wgt', 'plglist_wgt', '-', 'shotcut_wgt'] \ No newline at end of file +catlog = ['plgmanager_wgt', '-', 'plgtree_wgt', 'toltree_wgt', 'plglist_wgt', '-', 'shotcut_wgt'] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Manager/console_wgt.py b/imagepy/menus/Plugins/Manager/console_wgt.py index cf0af6ac..5f673335 100644 --- a/imagepy/menus/Plugins/Manager/console_wgt.py +++ b/imagepy/menus/Plugins/Manager/console_wgt.py @@ -3,54 +3,35 @@ from wx.py.shell import Shell import scipy.ndimage as ndimg import numpy as np -from imagepy import IPy +# from imagepy import IPy +from sciapp.action import Free -from imagepy.core.engine import Free -from imagepy.core.manager import PluginsManager -## There is something wrong! -## To be fixed! - -def get_ips(): - ips = IPy.get_ips() - if ips is None: - print('no image opened!') - return ips - -def update(): - ips = IPy.get_ips() - if not ips is None : - ips.update='pix' +cmds = {'app':'app', 'np':np, 'ndimg':ndimg, 'update':None, 'get_img':None} class Macros(dict): - def __init__(self): - for i in list(PluginsManager.plgs.keys()): + def __init__(self, app): + for i in app.plugin_manager.names(): if not isinstance(i, str) or i == 'Command Line': #print(PluginsManager.plgs[i]) continue name = ''.join(list(filter(str.isalnum, i))) - ### TODO:Fixme! - #exec('self.run_%s = lambda para=None, - # plg=PluginsManager.plgs[i]:plg().start(para)'%name) - #self['run_%s'%i] = lambda para=None, plg=PluginsManager.plgs[i]:plg().start(para) - exec('self.run_%s = lambda para=None, plg=PluginsManager.plgs[i]:plg().start(para)'%name) - #exec('self._%s = PluginsManager.plgs[i]().start'%name) - print(self) - -cmds = {'IPy':IPy, 'ndimg':ndimg, 'update':update, 'curips':get_ips} + exec("self.run_%s = lambda para=None, plg=app.plugin_manager.get(i):plg().start(cmds['app'], para)"%name) class Plugin(wx.Panel): title = 'Command Line' single = None - def __init__(self, parent): + def __init__(self, parent, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + cmds['app'] = app + cmds['get_img'] = lambda name=None, app=self: self.app.get_img() + cmds['update'] = lambda app=self: self.app.get_img().update() shell = Shell(self, locals=cmds) bSizer = wx.BoxSizer( wx.VERTICAL ) bSizer.Add( shell, 1, wx.EXPAND|wx.ALL, 5 ) self.SetSizer(bSizer) - cmds['plgs'] = Macros() - shell.run('# numpy(np) and scipy.ndimage(ndimg) has been imported!\n') + cmds['plgs'] = Macros(app) shell.run('# plgs.run_name() to call a ImagePy plugin.\n') - shell.run('# IPy is avalible here, and curips() to get the current ImagePlus, update() to redraw.\n') \ No newline at end of file + shell.run('# app is avalible here, and get_img() to get the current ImagePlus, update() to redraw.\n') \ No newline at end of file diff --git a/imagepy/menus/Plugins/Manager/plglist_wgt.py b/imagepy/menus/Plugins/Manager/plglist_wgt.py index d384a34a..cca16cca 100644 --- a/imagepy/menus/Plugins/Manager/plglist_wgt.py +++ b/imagepy/menus/Plugins/Manager/plglist_wgt.py @@ -5,8 +5,7 @@ @author: yxl """ import wx, os -from imagepy import IPy, root_dir -from imagepy.core.manager import PluginsManager +#from imagepy import IPy, root_dir class VirtualListCtrl(wx.ListCtrl): def __init__(self, parent, title, data=[]): @@ -35,13 +34,15 @@ def refresh(self): class Plugin( wx.Panel ): title = 'Plugin List View' single = None - def __init__( self, parent,): + + def __init__( self, parent, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + self.app = app bSizer1 = wx.BoxSizer( wx.VERTICAL ) bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) - self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "Search:", + self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "Search", wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText1.Wrap( -1 ) bSizer2.Add( self.m_staticText1, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -66,7 +67,7 @@ def __del__( self ): #def list_plg(self, lst, items def load(self): - lst = list(PluginsManager.plgs.values()) + lst = [i[1] for i in list(self.app.plugin_manager.gets())] self.plgs = [(i.title, i.__module__) for i in lst] self.plgs.sort() self.buf = self.plgs @@ -80,4 +81,5 @@ def on_search( self, event ): self.lst_plgs.Refresh() def on_run(self, event): - PluginsManager.plgs[self.buf[event.GetIndex()][0]]().start() \ No newline at end of file + name=self.buf[event.GetIndex()][0] + self.app.manager('plugin').get(name)().start(self.app) \ No newline at end of file diff --git a/imagepy/menus/Plugins/Manager/plgmanager_wgt.py b/imagepy/menus/Plugins/Manager/plgmanager_wgt.py new file mode 100644 index 00000000..e11b90f5 --- /dev/null +++ b/imagepy/menus/Plugins/Manager/plgmanager_wgt.py @@ -0,0 +1 @@ +from ..Contribute.pmanager_wgt import Plugin \ No newline at end of file diff --git a/imagepy/menus/Plugins/Manager/plgtree_wgt.py b/imagepy/menus/Plugins/Manager/plgtree_wgt.py index df8c1147..0b0ece09 100644 --- a/imagepy/menus/Plugins/Manager/plgtree_wgt.py +++ b/imagepy/menus/Plugins/Manager/plgtree_wgt.py @@ -5,30 +5,33 @@ @author: yxl """ -from imagepy.core.engine import Free +from sciapp.action import Free import wx,os -from imagepy import IPy, root_dir -from imagepy.core.loader import loader +from imagepy import root_dir +from imagepy.app import loader, ConfigManager, DocumentManager from wx.py.editor import EditorFrame +from sciwx.text import MDPad +from glob import glob class Plugin ( wx.Panel ): title = 'Plugin Tree View' single = None - def __init__( self, parent ): + def __init__( self, parent, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + self.app = app bSizer1 = wx.BoxSizer( wx.HORIZONTAL ) self.tre_plugins = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE ) - self.tre_plugins.SetMinSize( wx.Size( 200,-1 ) ) + self.tre_plugins.SetMinSize( wx.Size( 300,-1 ) ) bSizer1.Add( self.tre_plugins, 0, wx.ALL|wx.EXPAND, 5 ) bSizer3 = wx.BoxSizer( wx.VERTICAL ) bSizer4 = wx.BoxSizer( wx.HORIZONTAL ) - self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "Plugin Infomation:", + self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "Plugin Information", wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText2.Wrap( -1 ) bSizer4.Add( self.m_staticText2, 0, wx.ALL, 5 ) @@ -42,8 +45,7 @@ def __init__( self, parent ): bSizer4.Add( self.m_staticText3, 0, wx.ALL, 5 ) bSizer3.Add( bSizer4, 0, wx.EXPAND, 5 ) - self.txt_info = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, - wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + self.txt_info = MDPad( self ) bSizer3.Add( self.txt_info, 1, wx.ALL|wx.EXPAND, 5 ) @@ -72,25 +74,36 @@ def addnode(self, parent, data): self.tre_plugins.SetItemData(item, i) def load(self): - data = loader.build_plugins('menus') + data = loader.build_plugins(root_dir+'/menus') + extends = glob(root_dir+'/plugins/*/menus') + keydata = {} + for i in data[1]: + if isinstance(i, tuple): keydata[i[0].title] = i[1] + for i in extends: + plgs = loader.build_plugins(i) + data[2].extend(plgs[2]) + for j in plgs[1]: + if not isinstance(j, tuple): continue + name = j[0].title + if name in keydata: keydata[name].extend(j[1]) + else: data[1].append(j) root = self.tre_plugins.AddRoot('Plugins') self.addnode(root, data[1]) # Virtual event handlers, overide them in your derived class def on_run( self, event ): plg = self.tre_plugins.GetItemPyData(event.GetItem()) - if hasattr(plg, 'start'):plg().start() + if hasattr(plg, 'start'):plg().start(self.app) def on_select( self, event ): plg = self.tre_plugins.GetItemData(event.GetItem()) - print(type(plg)) if plg!=None: self.plg = plg - if plg.__doc__!=None: - self.txt_info.SetValue(plg.__doc__) - elif hasattr(plg, '__module__'): - self.txt_info.SetValue("plugin at {}".format(plg.__module__)) - else: self.txt_info.SetValue("package at {}".format(plg.__name__)) + name = self.tre_plugins.GetItemText(event.GetItem()) + lang = ConfigManager.get('language') + doc = DocumentManager.get(name, tag=lang) + doc = doc or DocumentManager.get(name, tag='English') + self.txt_info.set_cont(doc or 'No Document!') def on_source(self, event): ## TODO: should it be absolute path ? diff --git a/imagepy/menus/Plugins/Manager/shotcut_wgt.py b/imagepy/menus/Plugins/Manager/shotcut_wgt.py index 9887f23e..a7fb8e64 100644 --- a/imagepy/menus/Plugins/Manager/shotcut_wgt.py +++ b/imagepy/menus/Plugins/Manager/shotcut_wgt.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- import wx, os -from imagepy.core.engine import Free -from imagepy.core.manager import ShotcutManager, PluginsManager -from imagepy import IPy, root_dir +from sciapp.action import Free +from imagepy import root_dir +from imagepy.app import ShortcutManager class VirtualListCtrl(wx.ListCtrl): def __init__(self, parent, title, data=[]): @@ -28,12 +27,14 @@ def refresh(self): self.SetItemCount(len(self.data)) class Plugin( wx.Panel ): - title = 'Shotcut Editor' + title = 'Shortcut Editor' single = None - def __init__( self, parent): + + def __init__( self, parent, app=None): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + self.app = app bSizer1 = wx.BoxSizer( wx.VERTICAL ) bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "Search:", @@ -44,7 +45,7 @@ def __init__( self, parent): wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer2.Add( self.txt_search, 1, wx.ALL, 5 ) bSizer1.Add( bSizer2, 0, wx.EXPAND, 5 ) - self.lst_plgs = VirtualListCtrl( self, ['Name', 'Shotcut']) + self.lst_plgs = VirtualListCtrl( self, ['Name', 'Shortcut']) self.lst_plgs.SetColumnWidth(0,200) self.lst_plgs.SetColumnWidth(1,200) bSizer1.Add( self.lst_plgs, 1, wx.ALL|wx.EXPAND, 5 ) @@ -54,12 +55,16 @@ def __init__( self, parent): # Connect Events self.txt_search.Bind( wx.EVT_TEXT, self.on_search ) self.lst_plgs.Bind(wx.EVT_LIST_KEY_DOWN, self.on_run) + self.lst_plgs.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.on_active) + self.lst_plgs.Bind( wx.EVT_LIST_ITEM_SELECTED, self.on_select) + self.load() + self.active = -1 #def list_plg(self, lst, items def load(self): - lst = list(PluginsManager.plgs.values()) - self.plgs = [[i.title, ShotcutManager.get(i.title)] for i in lst] + lst = self.app.plugin_names() + self.plgs = [[i, ShortcutManager.get(i)] for i in lst] for i in self.plgs: if i[1]==None:i[1]='' self.plgs.sort() @@ -71,7 +76,8 @@ def on_search( self, event ): wd = self.txt_search.GetValue() self.buf = [i for i in self.plgs if wd.lower() in i[0].lower()] self.lst_plgs.set_data(self.buf) - + self.Refresh() + def ist(self, cont, txt): sep = cont.split('-') if txt in sep: sep.remove(txt) @@ -81,12 +87,19 @@ def ist(self, cont, txt): if len(sep)>0:cas.append(sep[-1]) return '-'.join(cas) + def on_active(self, event): + self.active = event.GetIndex() + + def on_select(self, event): + self.active = -1 + def on_run(self, event): + if self.active != event.GetIndex(): + return self.app.alert('please double click to activate an item') code = event.GetKeyCode() title = self.buf[event.GetIndex()][0] txt = self.buf[event.GetIndex()][1] - if code == wx.WXK_DELETE: - txt = '' + if code == wx.WXK_DELETE: txt = '' elif code == wx.WXK_CONTROL: txt = self.ist(txt, 'Ctrl') elif code == wx.WXK_ALT: @@ -101,9 +114,8 @@ def on_run(self, event): if len(txt)>0 and txt[-1]=='-':txt=txt[:-1] self.buf[event.GetIndex()][1] = txt self.lst_plgs.RefreshItem(event.GetIndex()) - if txt=='':ShotcutManager.rm(title) - ShotcutManager.set(title, txt) + if txt!='': ShortcutManager.add(title, txt) #PluginsManager.plgs[self.buf[event.GetIndex()][0]]().start() - def __del__(self): - ShotcutManager.write() \ No newline at end of file + def close(self): + ShortcutManager.write(os.path.join(root_dir,'data/shortcut.json')) diff --git a/imagepy/menus/Plugins/Manager/toltree_wgt.py b/imagepy/menus/Plugins/Manager/toltree_wgt.py index 8b9326dd..dfa801e5 100644 --- a/imagepy/menus/Plugins/Manager/toltree_wgt.py +++ b/imagepy/menus/Plugins/Manager/toltree_wgt.py @@ -5,19 +5,23 @@ @author: yxl """ -from imagepy.core.engine import Free +from sciapp.action import Free import wx,os -from imagepy import IPy, root_dir -from imagepy.core.loader import loader +from imagepy import root_dir +from imagepy.app import loader, ConfigManager, DocumentManager from wx.py.editor import EditorFrame +#from imagepy.ui.mkdownwindow import HtmlPanel, md2html +from sciwx.text import MDPad +from glob import glob class Plugin ( wx.Panel ): title = 'Tool Tree View' single = None - def __init__( self, parent ): + def __init__( self, parent, app=None): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + self.app = app bSizer1 = wx.BoxSizer( wx.HORIZONTAL ) self.tre_plugins = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, @@ -28,7 +32,7 @@ def __init__( self, parent ): bSizer3 = wx.BoxSizer( wx.VERTICAL ) bSizer4 = wx.BoxSizer( wx.HORIZONTAL ) - self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "Tool Infomation:", + self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "Tool Information", wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText2.Wrap( -1 ) bSizer4.Add( self.m_staticText2, 0, wx.ALL, 5 ) @@ -42,8 +46,7 @@ def __init__( self, parent ): bSizer4.Add( self.m_staticText3, 0, wx.ALL, 5 ) bSizer3.Add( bSizer4, 0, wx.EXPAND, 5 ) - self.txt_info = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, - wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + self.txt_info = MDPad( self ) bSizer3.Add( self.txt_info, 1, wx.ALL|wx.EXPAND, 5 ) @@ -61,9 +64,7 @@ def __init__( self, parent ): self.load() def addnode(self, parent, data): - print('aaa', data) for i in data: - print(i) if i=='-':continue if isinstance(i, tuple): item = self.tre_plugins.AppendItem(parent, i[0].title) @@ -74,9 +75,14 @@ def addnode(self, parent, data): self.tre_plugins.SetItemData(item, i[0]) def load(self): - data = loader.build_tools('tools') + datas = loader.build_tools('tools') + extends = glob('plugins/*/tools') + for i in extends: + tols = loader.build_tools(i) + if len(tols)!=0: datas[1].extend(tols[1]) + root = self.tre_plugins.AddRoot('Tools') - for i in data[1]: + for i in datas[1]: item = self.tre_plugins.AppendItem(root, i[0].title) self.tre_plugins.SetItemData(item, i[0]) for j in i[1]: @@ -86,18 +92,17 @@ def load(self): # Virtual event handlers, overide them in your derived class def on_run( self, event ): plg = self.tre_plugins.GetItemData(event.GetItem()) - if hasattr(plg, 'start'):plg().start() + if hasattr(plg, 'start'):plg().start(self.app) def on_select( self, event ): plg = self.tre_plugins.GetItemData(event.GetItem()) - print(type(plg)) if plg!=None: self.plg = plg - if plg.__doc__!=None: - self.txt_info.SetValue(plg.__doc__) - elif hasattr(plg, '__module__'): - self.txt_info.SetValue('plugin at %s'%plg.__module__) - else: self.txt_info.SetValue('package at %s'%plg.__name__) + name = self.tre_plugins.GetItemText(event.GetItem()) + lang = ConfigManager.get('language') + doc = DocumentManager.get(name, tag=lang) + doc = doc or DocumentManager.get(name, tag='English') + self.txt_info.set_cont(doc or 'No Document!') def on_source(self, event): ## TODO: should it be absolute path ? diff --git a/imagepy/menus/Plugins/New/demo_filter.py b/imagepy/menus/Plugins/New/demo_filter.py index b64e647f..9479e17c 100644 --- a/imagepy/menus/Plugins/New/demo_filter.py +++ b/imagepy/menus/Plugins/New/demo_filter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import scipy.ndimage as nimg -from imagepy.core.engine import Filter +from sciapp.action import Filter # this is a Filter Sample, implements the Gaussian Blur class Plugin(Filter): diff --git a/imagepy/menus/Plugins/New/demo_free.py b/imagepy/menus/Plugins/New/demo_free.py index 976b4c4f..9dd86eb8 100644 --- a/imagepy/menus/Plugins/New/demo_free.py +++ b/imagepy/menus/Plugins/New/demo_free.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from imagepy.core.engine import Free +from sciapp.action import Free from imagepy import IPy class Plugin(Free): diff --git a/imagepy/menus/Plugins/New/demo_simple.py b/imagepy/menus/Plugins/New/demo_simple.py index f43cac67..5efc1a41 100644 --- a/imagepy/menus/Plugins/New/demo_simple.py +++ b/imagepy/menus/Plugins/New/demo_simple.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from imagepy.core.engine import Simple +from sciapp.action import Simple # a simple demo implements the next slice class Plugin(Simple): diff --git a/imagepy/menus/Plugins/New/demo_tool.py b/imagepy/menus/Plugins/New/demo_tool.py index 305c5bf1..3f1fbc6e 100644 --- a/imagepy/menus/Plugins/New/demo_tool.py +++ b/imagepy/menus/Plugins/New/demo_tool.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from imagepy.core.draw import paint -from imagepy.core.engine import Tool -import wx +from imagepy.draw import paint +from sciapp.action import Tool # this is a simple tool implements a pencial class Plugin(Tool): @@ -16,7 +15,7 @@ def __init__(self): self.sta = 0 self.paint = paint.Paint() self.paint.color = 255 - self.cursor = wx.CURSOR_CROSS + self.cursor = 'cross' # do it when mouse_down def mouse_down(self, ips, x, y, btn, **key): @@ -35,7 +34,7 @@ def mouse_up(self, ips, x, y, btn, **key): def mouse_move(self, ips, x, y, btn, **key): if self.sta==1: self.paint.lineto(ips.img,x,y, self.cfgp['width']) - ips.update = True + ips.update() # do it when mouse wheel def mouse_wheel(self, ips, x, y, d, **key): diff --git a/imagepy/menus/Plugins/New/new_plg.py b/imagepy/menus/Plugins/New/new_plg.py index 6f501da0..9acda5dd 100644 --- a/imagepy/menus/Plugins/New/new_plg.py +++ b/imagepy/menus/Plugins/New/new_plg.py @@ -5,7 +5,7 @@ @author: yxl """ import os, wx -from imagepy.core.engine import Free +from sciapp.action import Free from wx.py.editor import EditorFrame from imagepy import root_dir diff --git a/imagepy/menus/Plugins/StackReg/StackReg License.md b/imagepy/menus/Plugins/StackReg/StackReg License.md new file mode 100644 index 00000000..9d321c0d --- /dev/null +++ b/imagepy/menus/Plugins/StackReg/StackReg License.md @@ -0,0 +1,12 @@ +# Gratitude for StackReg + +You'll be free to use this software for research purposes, but you +should not redistribute it without our consent. In addition, we expect +you to include a citation or acknowledgment whenever you present or +publish results that are based on it. + +**Additional help available at [http://bigwww.epfl.ch/thevenaz/turboreg](http://bigwww.epfl.ch/thevenaz/turboreg/)** + +![befor](http://bigwww.epfl.ch/thevenaz/turboreg/before.gif) +![after](http://bigwww.epfl.ch/thevenaz/turboreg/after.gif) + diff --git a/imagepy/menus/Plugins/StackReg/__init__.py b/imagepy/menus/Plugins/StackReg/__init__.py new file mode 100644 index 00000000..ab791f79 --- /dev/null +++ b/imagepy/menus/Plugins/StackReg/__init__.py @@ -0,0 +1 @@ +catlog = ['stackreg_plgs', '-', 'StackReg License'] \ No newline at end of file diff --git a/imagepy/menus/Plugins/StackReg/stackreg_plgs.py b/imagepy/menus/Plugins/StackReg/stackreg_plgs.py new file mode 100644 index 00000000..03275ea7 --- /dev/null +++ b/imagepy/menus/Plugins/StackReg/stackreg_plgs.py @@ -0,0 +1,81 @@ +from sciapp.action import Filter, Simple +from pystackreg import StackReg +import numpy as np +import pandas as pd +from skimage import transform as tf +import scipy.ndimage as ndimg + +class Register(Simple): + title = 'Stack Register' + note = ['8-bit', '16-bit', 'int', 'float', 'stack'] + + para = {'trans':'RIGID_BODY', 'ref':'previous', 'tab':False, 'new':'Inplace', 'diag':0, 'sigma':0} + view = [(list, 'trans', ['TRANSLATION', 'RIGID_BODY', 'SCALED_ROTATION', 'AFFINE', 'BILINEAR'], str, 'transform', ''), + (list, 'ref', ['previous', 'first', 'mean'], str, 'reference', ''), + (list, 'new', ['Inplace', 'New', 'None'], str, 'image', ''), + (int, 'diag', (0, 2048), 0, 'diagonal', 'scale'), + (float, 'sigma', (0,30), 1, 'sigma', 'blur'), + (bool, 'tab', 'show table')] + + def run(self, ips, imgs, para = None): + k = para['diag']/np.sqrt((np.array(ips.img.shape)**2).sum()) + size = tuple((np.array(ips.img.shape)*k).astype(np.int16)) + IPy.info('down sample...') + news = [] + for img in imgs: + if k!=0: img = tf.resize(img, size) + if para['sigma']!=0: + img = ndimg.gaussian_filter(img, para['sigma']) + news.append(img) + + IPy.info('register...') + sr = StackReg(eval('StackReg.%s'%para['trans'])) + sr.register_stack(np.array(news), reference=para['ref']) + + mats = sr._tmats.reshape((sr._tmats.shape[0],-1)) + if k!=0: mats[:,[0,1,3,4,6,7]] *= k + if k!=0: mats[:,[0,1,2,3,4,5]] /= k + + if para['tab']: IPy.show_table(pd.DataFrame( + mats, columns=['A%d'%(i+1) for i in range(mats.shape[1])]), title='%s-Tmats'%ips.title) + + if para['new'] == 'None': return + IPy.info('transform...') + for i in range(sr._tmats.shape[0]): + tform = tf.ProjectiveTransform(matrix=sr._tmats[i]) + img = tf.warp(imgs[i], tform) + img -= imgs[i].min(); img *= imgs[i].max() - imgs[i].min() + if para['new'] == 'Inplace': imgs[i][:] = img + if para['new'] == 'New': news[i] = img.astype(ips.img.dtype) + self.progress(i, len(imgs)) + if para['new'] == 'New': IPy.show_img(news, '%s-reg'%ips.title) + +class Transform(Simple): + title = 'Register By Mats' + note = ['all'] + + para = {'mat':None, 'new':True} + view = [('tab', 'mat', 'transfrom', 'matrix'), + (bool, 'new', 'new image')] + + def run(self, ips, imgs, para = None): + mats = TableManager.get(para['mat']).data.values + if len(imgs) != len(mats): + IPy.alert('image stack must has the same length as transfrom mats!') + return + newimgs = [] + img = np.zeros_like(ips.img, dtype=np.float64) + for i in range(len(mats)): + tform = tf.ProjectiveTransform(matrix=mats[i].reshape((3,3))) + if imgs[i].ndim==2: + img[:] = tf.warp(imgs[i], tform) + else: + for c in range(img.shape[2]): + img[:,:,c] = tf.warp(imgs[i][:,:,c], tform) + img -= imgs[i].min(); img *= imgs[i].max() - imgs[i].min() + if para['new']: newimgs.append(img.astype(ips.img.dtype)) + else: imgs[i] = img + self.progress(i, len(mats)) + if para['new']: IPy.show_img(newimgs, '%s-trans'%ips.title) + +plgs = [Register, Transform] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Surf/Surf Demo.mc b/imagepy/menus/Plugins/Surf/Surf Demo.mc deleted file mode 100644 index 51869842..00000000 --- a/imagepy/menus/Plugins/Surf/Surf Demo.mc +++ /dev/null @@ -1,3 +0,0 @@ -Open Url>{'url': u'http://data.imagepy.org/testdata/box.png'} -Open Url>{'url': u'http://data.imagepy.org/testdata/box_in_scene.png'} -Surf Matcher>{'int': 4, 'upright': False, 'img2': u'box_in_scene', 'img1': u'box', 'std': 1, 'style': 'Blue/Yellow', 'log': True, 'thr': 2000, 'ext': False, 'trans': 'Homo', 'oct': 3} diff --git a/imagepy/menus/Plugins/Surf/__init__.py b/imagepy/menus/Plugins/Surf/__init__.py deleted file mode 100644 index 39f3f347..00000000 --- a/imagepy/menus/Plugins/Surf/__init__.py +++ /dev/null @@ -1 +0,0 @@ -catlog = ['surf_plg', '-', 'Surf Demo'] \ No newline at end of file diff --git a/imagepy/menus/Plugins/Surf/matcher.py b/imagepy/menus/Plugins/Surf/matcher.py deleted file mode 100644 index 31d2fc98..00000000 --- a/imagepy/menus/Plugins/Surf/matcher.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Jan 9 03:48:49 2017 - -@author: yxl -""" - -import cv2 -import numpy as np -from numpy.linalg import norm - -class Matcher: - def __init__(self, dim , std): - self.dim, self.std = dim, std - self.V = np.mat(np.zeros((self.dim,1))) - self.Dk = np.mat(np.diag(np.ones(self.dim)*1e6)) - - def match(self, desc1, desc2): - matcher = cv2.BFMatcher(cv2.NORM_L2) - pair = matcher.knnMatch(desc1, trainDescriptors = desc2, k = 1) - lt = [(i[0].distance, i[0].queryIdx, i[0].trainIdx) for i in pair] - return np.array(sorted(lt))[:,1:].astype(np.int16) - - def getT(self, v1, v2): - (x1, y1), (x2, y2) = v1, v2 - if self.dim==6: - return np.mat([[v1[0],v1[1],1,0,0,0], - [0,0,0,v1[0],v1[1],1]]) - if self.dim==8: - return np.mat([[x1, y1, 1, 0, 0, 0, -x1*x2, -y1*x2], - [0, 0, 0, x1, y1, 1, -x1*y2, -y1*y2]]) - - def test(self, v1, v2): - T = self.getT(v1.A1,v2.A1) - goal = T * self.V - o = goal.reshape((1,2)) - d = norm(v2 - o) - dv = (v2 - o)/d - D = T * self.Dk * T.T - s = np.sqrt(dv * D * dv.T + self.std ** 2) - return 3 * s > d - - def accept(self, v1, v2): - L = v2 - Dl = np.mat(np.diag(np.ones(2)))*self.std**2 - T = self.getT(v1.A1,v2.A1) - CX = (self.Dk.I + T.T * Dl.I * T).I - CL = CX * T .T* Dl.I - CV = np.mat(np.diag(np.ones(self.dim))) - CX * T.T * Dl.I * T - self.V = CL * L + CV * self.V - self.Dk = CV * self.Dk * CV.T + CL * Dl * CL.T - - def normalrize(self, pts): - o = pts.mean(axis=0) - l = norm(pts-o, axis=1).mean() - pts[:] = (pts-o)/l - - def filter(self, kpt1, feat1, kpt2, feat2): - kpt1 = np.array([(k.pt[0],k.pt[1]) for k in kpt1]) - kpt2 = np.array([(k.pt[0],k.pt[1]) for k in kpt2]) - self.normalrize(kpt1), self.normalrize(kpt2) - idx = self.match(feat1, feat2) - if self.dim == 0: - return idx, np.ones(len(idx), dtype=np.bool), 1 - mask = [] - for i1, i2 in idx: - v1 = np.mat(kpt1[i1]) - v2 = np.mat(kpt2[i2]) - if self.test(v1, v2): - self.accept(v1.T,v2.T) - mask.append(True) - else: mask.append(False) - mask = np.array(mask) - #print mask - return idx, mask, self.V - - def getTrans(self): - result = np.eye(3) - result[:2] = self.V.reshape((2,3)) - return result - - def checkV(self): - trans = self.getTrans()[:2,:2] - axis = norm(trans,axis=0) - return norm(axis-1)< 0.5 \ No newline at end of file diff --git a/imagepy/menus/Plugins/Surf/surf_plg.py b/imagepy/menus/Plugins/Surf/surf_plg.py deleted file mode 100644 index 5582a042..00000000 --- a/imagepy/menus/Plugins/Surf/surf_plg.py +++ /dev/null @@ -1,177 +0,0 @@ -import cv2, wx -from imagepy.core.engine import Filter, Simple, Tool -from imagepy.core.manager import ImageManager -from .matcher import Matcher -import numpy as np -from imagepy import IPy - -CVSURF = cv2.xfeatures2d.SURF_create if cv2.__version__[0] =="3" else cv2.SURF - -class FeatMark: - def __init__(self, feats): - self.feats = feats - - def draw(self, dc, f, **key): - for i in self.feats: - dc.DrawCircle(f(i.pt), 3) - -class Surf(Filter): - title = 'Surf Detect' - note = ['all', 'not-slice'] - - para = {'upright':False, 'oct':3, 'int':4, 'thr':1000, 'ext':False} - view = [(int, 'oct', (0,5), 0, 'octaves', ''), - (int, 'int', (0,5), 0, 'intervals', ''), - (int, 'thr', (500,2000), 0, 'threshold', '1-100'), - (bool, 'ext', 'extended'), - (bool, 'upright', 'upright')] - - def run(self, ips, snap, img, para): - detector = CVSURF(hessianThreshold=para['thr'], nOctaves=para['oct'], - nOctaveLayers=para['int'], upright=para['upright'],extended=para['ext']) - kps = detector.detect(img) - ips.surf_keypoint = kps - ips.mark = FeatMark(kps) - IPy.write("Detect completed, {} points found!".format(len(kps)), 'Surf') - -class Pick(Tool): - title = 'Key Point Pick Tool' - def __init__(self, pts1, pts2, pair, msk, ips1, ips2, host, style): - self.pts1, self.pts2 = pts1, pts2 - self.ips1, self.ips2 = ips1, ips2 - self.pair, self.msk = pair, msk - self.cur, self.host = -1, host - self.pts = self.pts1 if host else self.pts2 - self.style = style - - def nearest(self, x, y): - mind, mini = 1000, -1 - for i1, i2 in self.pair: - i = i1 if self.host else i2 - d = np.sqrt((x-self.pts[i].pt[0])**2+(y-self.pts[i].pt[1])**2) - if d 100 : per = 100 - print('%-3d%%'%per) - plg.prgs = (int(per), 100) - class Update(Free): title = 'Update Software' def run(self, para=None): - IPy.set_info('update now, waiting...') - self.download_zip() - self.deal_file() - #self.delete_cache() - IPy.alert('imagepy update done!') - - def download_zip(self): - url='https://github.com/Image-Py/imagepy/archive/master.zip' - path=osp.dirname(root_dir) - zipname = osp.join(path, 'imagepy_cache.zip') - print('downloading from %s'%url) - urlretrieve(url, zipname, - lambda a,b,c, p=self: Schedule(a,b,c,p)) - - def deal_file(self): - path = osp.dirname(root_dir) - #remove + self.app.info('update now, waiting...') + url = 'https://gitee.com/imagepy/imagepy' + path = osp.dirname(root_dir); rpath = osp.dirname(path) + newpath = osp.join(rpath, 'imagepy_new') + if osp.exists(newpath): shutil.rmtree(newpath) + porcelain.clone(url, os.path.join(rpath, 'imagepy_new'), depth=1).close() + shutil.rmtree(os.path.join(os.path.join(rpath, 'imagepy_new'), '.git')) + shutil.copytree(osp.join(path, 'imagepy/plugins'), + osp.join(rpath, 'imagepy_new/imagepy/plugins')) + shutil.copyfile(osp.join(path, 'imagepy/data/config.json'), + osp.join(rpath, 'imagepy_new/imagepy/data/config.json')) + shutil.copyfile(osp.join(path, 'imagepy/data/shortcut.json'), + osp.join(rpath, 'imagepy_new/imagepy/data/shortcut.json')) + newpath = os.path.join(rpath, 'imagepy_new') + fs = os.listdir(os.path.join(rpath, 'imagepy_new')) + fs = [i for i in fs if osp.isdir(osp.join(newpath, i))] + fs = [i for i in fs if osp.exists(osp.join(path, i))] + for i in [j for j in fs if j!='imagepy']: shutil.rmtree(osp.join(path, i)) + for i in [j for j in fs if j!='imagepy']: + shutil.copytree(osp.join(newpath, i), osp.join(path, i)) for i in os.listdir(root_dir): - if i in ['plugins', 'preference.cfg', '.gitignore']: continue - if osp.isdir(osp.join(root_dir,i)): shutil.rmtree(osp.join(root_dir, i)) + if osp.isdir(osp.join(root_dir,i)): + shutil.rmtree(osp.join(root_dir, i)) else : os.remove(osp.join(root_dir,i)) - - source = zipfile.ZipFile(osp.join(path, 'imagepy_cache.zip'), 'r') - target = zipfile.ZipFile(BytesIO(), 'w') - for i in source.namelist()[1:]: target.writestr(i[15:], source.read(i)) - target.extractall(path) - target.close() - source.close() - os.remove(osp.join(path,'imagepy_cache.zip')) + newdir = os.path.join(newpath, 'imagepy') + for i in os.listdir(newdir): + if osp.isdir(osp.join(newdir, i)): + shutil.copytree(osp.join(newdir, i), osp.join(root_dir, i)) + else: shutil.copyfile(osp.join(newdir, i), osp.join(root_dir, i)) + shutil.rmtree(newpath) + self.app.alert('imagepy update done!') class Refresh(Free): title = 'Reload Plugins' def run(self, para=None): - IPy.reload_plgs(True, True, True, True) + self.app.load_all() plgs = [Update, Refresh] diff --git a/imagepy/menus/Process/Binary/binary_plgs.py b/imagepy/menus/Process/Binary/binary_plgs.py index bff609b5..378e6a53 100644 --- a/imagepy/menus/Process/Binary/binary_plgs.py +++ b/imagepy/menus/Process/Binary/binary_plgs.py @@ -8,12 +8,13 @@ # -*- coding: utf-8 -* import scipy.ndimage as ndimg import numpy as np -from imagepy.core.engine import Filter +from sciapp.action import Filter from skimage.morphology import convex_hull_object +from skimage.segmentation import clear_border class Closing(Filter): - """Closing: derived from imagepy.core.engine.Filter """ - title = 'Binary Closeing' + """Closing: derived from sciapp.action.Filter """ + title = 'Binary Closing' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] para = {'w':3, 'h':3} view = [(int, 'w', (1,15), 0, 'width', 'pix'), @@ -25,7 +26,7 @@ def run(self, ips, snap, img, para = None): img *= 255 class Opening(Filter): - """Opening: derived from imagepy.core.engine.Filter """ + """Opening: derived from sciapp.action.Filter """ title = 'Binary Opening' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] para = {'w':3, 'h':3} @@ -38,7 +39,7 @@ def run(self, ips, snap, img, para = None): img *= 255 class Dilation(Filter): - """Dilation: derived from imagepy.core.engine.Filter """ + """Dilation: derived from sciapp.action.Filter """ title = 'Binary Dilation' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] para = {'w':3, 'h':3} @@ -51,7 +52,7 @@ def run(self, ips, snap, img, para = None): img *= 255 class Erosion(Filter): - """Erosion: derived from imagepy.core.engine.Filter """ + """Erosion: derived from sciapp.action.Filter """ title = 'Binary Erosion' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] para = {'w':3, 'h':3} @@ -64,7 +65,7 @@ def run(self, ips, snap, img, para = None): img *= 255 class Outline(Filter): - """Outline: derived from imagepy.core.engine.Filter """ + """Outline: derived from sciapp.action.Filter """ title = 'Binary Outline' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] @@ -74,7 +75,7 @@ def run(self, ips, snap, img, para = None): img -= snap class FillHoles(Filter): - """FillHoles: derived from imagepy.core.engine.Filter """ + """FillHoles: derived from sciapp.action.Filter """ title = 'Fill Holes' note = ['8-bit', 'auto_msk', 'auto_snap','preview'] @@ -82,13 +83,20 @@ def run(self, ips, snap, img, para = None): ndimg.binary_fill_holes(snap, output=img) img *= 255 +class ClearBorder(Filter): + title = 'Clear Border' + note = ['8-bit', 'auto_msk', 'auto_snap'] + + def run(self, ips, snap, img, para = None): + clear_border(img, in_place=True) + + class Convex(Filter): title = 'Binary ConvexHull' note = ['8-bit', 'auto_msk', 'auto_snap'] - #process def run(self, ips, snap, img, para = None): img[convex_hull_object(snap)] = 255 -plgs = [Dilation, Erosion, '-', Closing, Opening, '-', Outline, FillHoles, Convex] \ No newline at end of file +plgs = [Dilation, Erosion, '-', Closing, Opening, '-', Outline, FillHoles, ClearBorder, Convex] \ No newline at end of file diff --git a/imagepy/menus/Process/Binary/distance_plgs.py b/imagepy/menus/Process/Binary/distance_plgs.py index a82d1c5c..211d579e 100644 --- a/imagepy/menus/Process/Binary/distance_plgs.py +++ b/imagepy/menus/Process/Binary/distance_plgs.py @@ -7,9 +7,9 @@ from skimage.morphology import skeletonize from skimage.morphology import medial_axis from imagepy.ipyalg.graph import skel2d -from imagepy.core.engine import Filter -from imagepy.ipyalg import find_maximum, watershed -#from skimage.morphology import watershed +from sciapp.action import Filter +from imagepy.ipyalg import find_maximum, watershed, distance_transform_edt +from skimage.filters import apply_hysteresis_threshold import scipy.ndimage as ndimg class Skeleton(Filter): @@ -21,12 +21,12 @@ def run(self, ips, snap, img, para = None): img *= 255 class EDT(Filter): - """EDT: derived from imagepy.core.engine.Filter """ + """EDT: derived from sciapp.action.Filter """ title = 'Distance Transform' note = ['all', 'auto_msk', 'auto_snap','preview'] def run(self, ips, snap, img, para = None): - return ndimg.distance_transform_edt(snap) + return distance_transform_edt(snap) class MedialAxis(Filter): title = 'Medial Axis' @@ -54,14 +54,15 @@ class Watershed(Filter): ## TODO: Fixme! def run(self, ips, snap, img, para = None): - img[:] = snap - dist = -ndimg.distance_transform_edt(snap) - pts = find_maximum(dist, para['tor'], False) - buf = np.zeros(ips.size, dtype=np.uint16) - buf[pts[:,0], pts[:,1]] = 1 + img[:] = snap>0 + dist = distance_transform_edt(snap, output=np.uint16) + pts = find_maximum(dist, para['tor'], True) + buf = np.zeros(ips.shape, dtype=np.uint32) + buf[pts[:,0], pts[:,1]] = img[pts[:,0], pts[:,1]] = 2 markers, n = ndimg.label(buf, np.ones((3,3))) - line = watershed(dist, markers, line=True, conn=para['con']+1) - img[line==0] = 0 + line = watershed(dist, markers, line=True, conn=para['con']+1, up=False) + msk = apply_hysteresis_threshold(img, 0, 1) + img[:] = snap * ~((line==0) & msk) class Voronoi(Filter): """Mark class plugin with events callback functions""" @@ -72,7 +73,7 @@ class Voronoi(Filter): view = [(list, 'type', ['segment with ori', 'segment only', 'white line', 'gray line'], str, 'output', '')] ## TODO: Fixme! def run(self, ips, snap, img, para = None): - dist = ndimg.distance_transform_edt(snap) + dist = distance_transform_edt(snap, output=np.uint16) markers, n = ndimg.label(snap==0, np.ones((3,3))) line = watershed(dist, markers, line=True) @@ -85,5 +86,4 @@ def run(self, ips, snap, img, para = None): if para['type']=='gray line': img[:] = np.where(line==0, dist, 0) - plgs = [Skeleton, MedialAxis, '-', EDT, Watershed, Voronoi] \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/__init__.py b/imagepy/menus/Process/Classify/__init__.py new file mode 100644 index 00000000..6ce5e66a --- /dev/null +++ b/imagepy/menus/Process/Classify/__init__.py @@ -0,0 +1 @@ +catlog = ['label_wgt', 'classify_wgt', '-', 'classify_plgs', '-', 'predict_plg'] \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/classify_plgs.py b/imagepy/menus/Process/Classify/classify_plgs.py new file mode 100644 index 00000000..06fe78e8 --- /dev/null +++ b/imagepy/menus/Process/Classify/classify_plgs.py @@ -0,0 +1,204 @@ +from sciapp.action import Filter, Simple +from sciapp.object import Image + +import numpy as np +from sklearn.ensemble import RandomForestClassifier, \ + AdaBoostClassifier, BaggingClassifier, ExtraTreesClassifier,\ + GradientBoostingClassifier, VotingClassifier +from sklearn.dummy import DummyClassifier +from imagepy.ipyalg import feature + +model_para = None + +class Base(Simple): + """Closing: derived from sciapp.action.Filter """ + def load(self, ips): + print("len(ips.imgs) = ", len(ips.imgs)) + if len(ips.imgs)==1: ips.snapshot() + return True + + def preview(self, ips, para): + if len(ips.imgs)==1: + ips.img[:] = ips.snap + self.run(ips, ips.imgs, para, True) + return True + + def cancel(self, ips): + if len(ips.imgs)==1: ips.swap() + + def classify(self, para): pass + + def run(self, ips, imgs, para = None, preview=False): + if len(ips.imgs)==1: ips.img[:] = ips.snap + key = {'chans':None, 'grade':para['grade'], 'w':para['w']} + key['items'] = [i for i in ['ori', 'blr', 'sob', 'eig'] if para[i]] + slir, slic = ips.rect + labs = [i[slir, slic] for i in imgs] + ori = ips.back.imgs + oris = [i[slir, slic] for i in ori] + + self.app.info('extract features...') + feat, lab, key = feature.get_feature(oris, labs, key, callback=self.progress) + + self.app.info('training data...') + # self.progress(None, 1) + model = self.classify(para) + model.fit(feat, lab) + + self.app.info('predict data...') + if preview: + return feature.get_predict(oris, model, key, labs, callback=self.progress) + if len(imgs) == 1: ips.swap() + outs = feature.get_predict(oris, model, key, callback=self.progress) + nips = Image(outs, ips.title+'rst') + nips.range, nips.lut = ips.range, ips.lut + nips.back, nips.mode = ips.back, 0.4 + self.app.show_img(nips) + global model_para + model_para = model, key + +class RandomForest(Base): + title = 'Random Forest Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':100, 'max_features':'sqrt', 'max_depth':0} + view = [('lab', None, '===== Classifier Parameter ====='), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (int, 'max_depth', (0,64), 0, 'depth', 'max'), + (list, 'max_features', ['sqrt', 'log2', 'None'], str, 'features', 'max'), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + feat_dic = {'sqrt':'sqrt', 'log2':'log2', 'None':None} + max_depth = None if para['max_depth']==0 else para['max_depth'] + return RandomForestClassifier(n_estimators=para['n_estimators'], + max_features = feat_dic[para['max_features']], max_depth=max_depth) + +class AdaBoost(Base): + """Closing: derived from sciapp.action.Filter """ + title = 'AdaBoost Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':50, 'learning_rate':1, 'algorithm':'SAMME.R'} + view = [('lab', None, '===== Classifier Parameter ====='), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (float, 'learning_rate', (0.1, 10), 1, 'learn', 'rate'), + (list, 'algorithm', ['SAMME', 'SAMME.R'], str, 'algorithm', ''), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + return AdaBoostClassifier(n_estimators=para['n_estimators'], + learning_rate = para['learning_rate'], algorithm=para['algorithm']) + +class Bagging(Base): + """Closing: derived from sciapp.action.Filter """ + title = 'Bagging Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':50, 'max_features':1.0} + view = [('lab', None, '===== Classifier Parameter ====='), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (float, 'max_features', (0.2, 1), 1, 'features', 'k'), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + return BaggingClassifier(n_estimators=para['n_estimators'], + max_features = para['max_features']) + +class ExtraTrees(Base): + """Closing: derived from sciapp.action.Filter """ + title = 'ExtraTrees Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':10, 'max_features':'sqrt', 'max_depth':0} + view = [('lab', None, '===== Classifier Parameter ====='), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (int, 'max_depth', (0,64), 0, 'depth', 'max'), + (list, 'max_features', ['sqrt', 'log2', 'None'], str, 'features', 'max'), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + feat_dic = {'sqrt':'sqrt', 'log2':'log2', 'None':None} + max_depth = None if para['max_depth']==0 else para['max_depth'] + return ExtraTreesClassifier(n_estimators=para['n_estimators'], + max_features = feat_dic[para['max_features']], max_depth=max_depth) + +class GradientBoosting(Base): + """Closing: derived from sciapp.action.Filter """ + title = 'Gradient Boosting Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':100, 'max_features':'sqrt', + 'max_depth':3, 'learning_rate':0.1, 'loss':'deviance'} + view = [('lab', None, '===== Classifier Parameter ====='), + (list, 'loss', ['deviance', 'exponential'], str, 'loss', ''), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (int, 'max_depth', (1,10), 0, 'depth', 'max'), + (float, 'learning_rate', (0.1, 10), 1, 'learn', 'rate'), + (list, 'max_features', ['sqrt', 'log2', 'None'], str, 'features', 'max'), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + feat_dic = {'sqrt':'sqrt', 'log2':'log2', 'None':None} + return GradientBoostingClassifier(n_estimators=para['n_estimators'], + loss=para['loss'], max_features = feat_dic[para['max_features']], + max_depth=para['max_depth'], learning_rate=para['learning_rate']) + +class Voting(Base): + """Closing: derived from sciapp.action.Filter """ + title = 'Voting Classify' + note = ['8-bit', 'auto_msk', 'not_slice', 'auto_snap', 'preview'] + para = {'grade':3, 'w':1, 'ori':True, 'blr':True, 'sob':True, + 'eig':True, 'n_estimators':100, 'max_features':'sqrt', + 'max_depth':3, 'learning_rate':0.1, 'loss':'deviance'} + view = [('lab', None, '===== Classifier Parameter ====='), + (list, 'loss', ['deviance', 'exponential'], str, 'loss', ''), + (int, 'n_estimators', (10,1024), 0, 'estimators', 'n'), + (int, 'max_depth', (1,10), 0, 'depth', 'max'), + (float, 'learning_rate', (0.1, 10), 1, 'learn', 'rate'), + (list, 'max_features', ['sqrt', 'log2', 'None'], str, 'features', 'max'), + ('lab', None, '===== Feature Parameter ====='), + (int, 'grade', (1,7), 0, 'grade', ''), + (int, 'w', (0,5), 0, 'sigma', 'tensor'), + (bool, 'ori', 'add image feature'), + (bool, 'blr', 'add blur feature'), + (bool, 'sob', 'add sobel feature'), + (bool, 'eig', 'add eig feature')] + + def classify(self, para): + feat_dic = {'sqrt':'sqrt', 'log2':'log2', 'None':None} + return VotingClassifier(n_estimators=para['n_estimators'], + loss=para['loss'], max_features = feat_dic[para['max_features']], + max_depth=para['max_depth'], learning_rate=para['learning_rate']) + +plgs = [RandomForest, ExtraTrees, Bagging, AdaBoost, GradientBoosting] \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/classify_wgt.py b/imagepy/menus/Process/Classify/classify_wgt.py new file mode 100644 index 00000000..3af4a821 --- /dev/null +++ b/imagepy/menus/Process/Classify/classify_wgt.py @@ -0,0 +1,121 @@ +import wx, joblib, os, shutil, os.path as osp +from glob import glob +#from imagepy.core.manager import RoiManager, ImageManager, ReaderManager, ViewerManager +from . import classify_plgs as manager +from .predict_plg import Plugin as FCL +from imagepy import root_dir + +#ReaderManager.add('fcl', lambda x:x, 'fcl') +#ViewerManager.add('fcl', lambda x,n:wx.CallAfter(FCL(path=x).start)) + +class Plugin( wx.Panel ): + title = 'Feature Classify Panel' + + def __init__( self, parent, app=None): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(256, 256), style = wx.TAB_TRAVERSAL ) + self.app = app + sizer = wx.BoxSizer( wx.HORIZONTAL ) + + sizer_btns = wx.BoxSizer( wx.VERTICAL ) + + self.btn_save = wx.Button( self, wx.ID_ANY, u"Save", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_save, 0, wx.ALL, 5 ) + + self.btn_saveas = wx.Button( self, wx.ID_ANY, u"Save As", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_saveas, 0, wx.ALL, 5 ) + + self.btn_export = wx.Button( self, wx.ID_ANY, u"Export", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_export, 0, wx.ALL, 5 ) + + self.btn_run = wx.Button( self, wx.ID_ANY, u"Run", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_run, 0, wx.ALL, 5 ) + + self.btn_rename = wx.Button( self, wx.ID_ANY, u"Rename", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_rename, 0, wx.ALL, 5 ) + + self.btn_remove = wx.Button( self, wx.ID_ANY, u"Remove", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_btns.Add( self.btn_remove, 0, wx.ALL, 5 ) + + sizer.Add( sizer_btns, 0, wx.EXPAND, 5 ) + + sizer_list = wx.BoxSizer( wx.VERTICAL ) + + self.lst_model = wx.ListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, ['a', 'b'], 0 ) + self.lst_model.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + sizer_list.Add( self.lst_model, 1, wx.ALL|wx.EXPAND, 5 ) + + sizer.Add( sizer_list, 1, wx.EXPAND, 5 ) + self.models = [] + self.AddEvent() + self.LoadModel() + + self.SetSizer( sizer ) + self.Layout() + + def LoadModel(self): + fs = glob(osp.join(root_dir, 'plugins/ilastik/*.fcl')) + self.models = [osp.split(i)[1] for i in fs] + self.lst_model.SetItems(self.models) + + def AddEvent(self): + self.btn_save.Bind(wx.EVT_BUTTON, self.on_save) + self.btn_saveas.Bind(wx.EVT_BUTTON, self.on_saveas) + self.btn_export.Bind(wx.EVT_BUTTON, self.on_export) + self.btn_rename.Bind(wx.EVT_BUTTON, self.on_rename) + self.btn_remove.Bind(wx.EVT_BUTTON, self.on_remove) + self.btn_run.Bind(wx.EVT_BUTTON, self.on_run) + + + def on_save(self, event): + if manager.model_para is None: + return self.app.alert('you must train your model first!') + para = {'name':'New Model'} + if not self.app.show_para('name', para, [(str, 'name', 'model', 'name')]): return + if not osp.exists(osp.join(root_dir, 'plugins/ilastik')): + os.mkdir(osp.join(root_dir, 'plugins/ilastik')) + joblib.dump( manager.model_para, osp.join(root_dir, 'plugins/ilastik/%s.fcl'%para['name'])) + self.LoadModel() + + def on_saveas(self, event): + if manager.model_para is None: + return self.app.alert('you must train your model first!') + para = {'path':'', 'name':''} + filt = ['fcl'] + print("filt = ", filt) + para['path'] = self.app.get_path('Save..', filt, 'save', para['name']) + joblib.dump( manager.model_para, para['path']) + + def on_export(self, event): + idx = self.lst_model.GetSelection() + if idx==-1: return self.app.alert('no model selected!') + para = {'path':'', 'name':''} + filt = ['fcl'] + para['path'] = self.app.get_path('Save..', filt, 'save', para['name']) + oldname = osp.join(root_dir, 'plugins/ilastik/%s'%self.lst_model.GetStringSelection()) + print(para['path']) + shutil.copyfile(oldname, para['path']) + self.LoadModel() + + def on_rename(self, event): + idx = self.lst_model.GetSelection() + if idx==-1: return self.app.alert('no model selected!') + para = {'name':'New Model'} + if not self.app.show_para('name', para, [(str, 'name', 'model', 'name')]): return + oldname = osp.join(root_dir, 'plugins/ilastik/%s'%self.lst_model.GetStringSelection()) + os.rename(oldname, osp.join(root_dir, 'plugins/ilastik/%s.fcl'%para['name'])) + self.LoadModel() + + def on_remove(self, event): + idx = self.lst_model.GetSelection() + if idx==-1: return self.app.alert('no model selected!') + os.remove(osp.join(root_dir, 'plugins/ilastik/%s'%self.lst_model.GetStringSelection())) + self.LoadModel() + + def on_run(self, event): + idx = self.lst_model.GetSelection() + if idx==-1: return self.app.alert('no model selected!') + FCL(path=self.lst_model.GetStringSelection()).start(self.app) + + def __del__( self ): + pass + \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/imgs/01.gif b/imagepy/menus/Process/Classify/imgs/01.gif new file mode 100644 index 00000000..f4f91159 Binary files /dev/null and b/imagepy/menus/Process/Classify/imgs/01.gif differ diff --git a/imagepy/menus/Process/Classify/imgs/03.gif b/imagepy/menus/Process/Classify/imgs/03.gif new file mode 100644 index 00000000..477da971 Binary files /dev/null and b/imagepy/menus/Process/Classify/imgs/03.gif differ diff --git a/imagepy/menus/Process/Classify/imgs/05.gif b/imagepy/menus/Process/Classify/imgs/05.gif new file mode 100644 index 00000000..cdb2a70a Binary files /dev/null and b/imagepy/menus/Process/Classify/imgs/05.gif differ diff --git a/imagepy/menus/Process/Classify/imgs/10.gif b/imagepy/menus/Process/Classify/imgs/10.gif new file mode 100644 index 00000000..316dc2d0 Binary files /dev/null and b/imagepy/menus/Process/Classify/imgs/10.gif differ diff --git a/imagepy/menus/Process/Classify/imgs/__init__.py b/imagepy/menus/Process/Classify/imgs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Process/Classify/imgs/fill.gif b/imagepy/menus/Process/Classify/imgs/fill.gif new file mode 100644 index 00000000..344279e6 Binary files /dev/null and b/imagepy/menus/Process/Classify/imgs/fill.gif differ diff --git a/imagepy/menus/Process/Classify/io_plgs.py b/imagepy/menus/Process/Classify/io_plgs.py new file mode 100644 index 00000000..f1ea3346 --- /dev/null +++ b/imagepy/menus/Process/Classify/io_plgs.py @@ -0,0 +1,30 @@ +from sciapp.action import Filter, Simple +from sciapp.object import Image +from imagepy.app import ColorManager +# from imagepy.core import ImagePlus +import numpy as np + +class BuildMark(Simple): + """Closing: derived from sciapp.action.Filter """ + title = 'Build Mark Image' + note = ['all'] + para = {'mode':'Mask', 'cm':'16_Colors', 'n':2, 'slice':True} + view = [(int, 'n', (0, 15), 0, 'n', 'colors'), + ('cmap', 'cm', 'colormap'), + (list, 'mode', ['None', 'Max', 'Min', 'Mask', '2-8mix', \ + '4-6mix', '5-5mix', '6-4mix', '8-2mix'], str, 'mode', 'channel'), + (bool, 'slice', 'slice')] + + def run(self, ips, imgs, para = None): + shp = ips.img.shape[:2] + imgs = [np.zeros(shp, dtype=np.uint8) for i in range([1, len(imgs)][para['slice']])] + newips = Image(imgs, ips.title+'-mark') + newips.back = ips + idx = ['None', 'Max', 'Min', 'Mask', '2-8mix', '4-6mix', '5-5mix', '6-4mix', '8-2mix'] + modes = ['set', 'max', 'min', 'msk', 0.2, 0.4, 0.5, 0.6, 0.8] + newips.lut = ColorManager.get(para['cm']) + newips.mode = modes[idx.index(para['mode'])] + #newips.range = (0, para['n']) + self.app.show_img(newips) + +plgs = [BuildMark] \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/label_wgt.py b/imagepy/menus/Process/Classify/label_wgt.py new file mode 100644 index 00000000..c87955df --- /dev/null +++ b/imagepy/menus/Process/Classify/label_wgt.py @@ -0,0 +1,151 @@ +from sciwx.widgets import CMapSelCtrl +from imagepy.app import ColorManager +from sciapp.action import Macros +import numpy as np +import wx, os.path as osp + +def make_bitmap(bmp): + img = bmp.ConvertToImage() + img.Resize((20, 20), (2, 2)) + return img.ConvertToBitmap() + +# apply, paint, fill, width, slic +class Plugin ( wx.Panel ): + title = 'Label Tool' + def __init__( self, parent, app=None): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(-1,-1), style = wx.TAB_TRAVERSAL ) + outsizer = wx.BoxSizer(wx.HORIZONTAL) + sizer = wx.BoxSizer( wx.VERTICAL ) + sizer_color = wx.BoxSizer( wx.HORIZONTAL ) + self.app = app + self.btns = [] + self.btn_make = wx.Button( self, wx.ID_ANY, 'New Mark', wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_color.Add(self.btn_make, 0, wx.ALL, 2) + for i in range(11): + btn = wx.Button( self, wx.ID_ANY, str(i), wx.DefaultPosition, wx.DefaultSize, 0 ) + btn.SetMaxSize( wx.Size( 30,-1 ) ) + self.btns.append(btn) + sizer_color.Add( btn, 0, wx.ALL, 2 ) + self.spn_num = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 2, 15, 0 ) + self.spn_num.SetMaxSize(wx.Size(45, -1)) + sizer_color.Add( self.spn_num, 0, wx.ALL|wx.EXPAND, 2 ) + + sizer.Add(sizer_color, 0, wx.ALL|wx.EXPAND, 0) + + sizer_other = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_update = wx.Button( self, wx.ID_ANY, 'Update', wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_other.Add( self.btn_update, 0, wx.ALL, 2) + + self.cmapsel = CMapSelCtrl(self) + self.cmapsel.SetItems(ColorManager.gets(tag='base')) + sizer_other.Add(self.cmapsel, 0, wx.ALL|wx.EXPAND, 2 ) + + com_backChoices = [ u"No Background" ] + self.com_back = wx.ComboBox( self, wx.ID_ANY, u"No Background", wx.DefaultPosition, wx.DefaultSize, com_backChoices, wx.CB_READONLY) + self.com_back.SetSelection( 0 ) + sizer_other.Add( self.com_back, 1, wx.ALL|wx.EXPAND, 2 ) + + com_modeChoices = [ u"None", u"Max", u"Min", u"Mask", u"2-8mix", u"4-6mix", u"5-5mix", u"6-4mix", u"8-2mix" ] + self.com_mode = wx.ComboBox( self, wx.ID_ANY, u"Min", wx.DefaultPosition, wx.DefaultSize, com_modeChoices, wx.CB_READONLY ) + self.com_mode.SetSelection( 0 ) + sizer_other.Add( self.com_mode, 0, wx.ALL|wx.EXPAND, 2 ) + + self.chk_hide = wx.CheckBox( self, wx.ID_ANY, u"Hide", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_other.Add( self.chk_hide, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + sizer.Add(sizer_other, 0, wx.ALL|wx.EXPAND, 0) + + #sizer_tol = wx.GridSizer( 0, 3, 0, 0 ) + self.pens = [] + name = ['01.gif','03.gif','05.gif','10.gif','fill.gif'] + path = osp.abspath(osp.dirname(__file__)) + for i in (0,1,2,3,4): + pen = wx.BitmapButton(self, wx.ID_ANY, make_bitmap(wx.Bitmap(osp.join(path, 'imgs', name[i]))),#make_bitmap(wx.Bitmap(data[1])), + wx.DefaultPosition, (30, 30), wx.BU_AUTODRAW|wx.RAISED_BORDER ) + #wx.Button( self, wx.ID_ANY, str(i), wx.DefaultPosition, wx.DefaultSize, 0 ) + pen.SetMaxSize( wx.Size( 30,-1 ) ) + self.pens.append( pen ) + sizer_color.Add( pen, 0, wx.ALL, 2 ) + + outsizer.AddStretchSpacer(prop=1) + #outsizer.Add(sizer_tol, 0, wx.ALL, 0) + outsizer.Add(sizer, 0, wx.ALL, 0) + outsizer.AddStretchSpacer(prop=1) + + self.SetSizer( outsizer ) + self.Layout() + + # Connect Events + self.spn_num.Bind( wx.EVT_SPINCTRL, self.on_cmapsel ) + self.com_back.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.on_items ) + self.cmapsel.Bind(wx.EVT_COMBOBOX, self.on_cmapsel) + self.com_back.Bind( wx.EVT_COMBOBOX, self.on_setback) + self.com_mode.Bind( wx.EVT_COMBOBOX, self.on_mode) + self.chk_hide.Bind( wx.EVT_CHECKBOX, self.on_mode) + self.pens[-1].Bind( wx.EVT_BUTTON, self.on_fill) + for i in self.btns: i.Bind(wx.EVT_BUTTON, self.on_color) + for i in range(4): self.pens[i].Bind(wx.EVT_BUTTON, \ + lambda e, x=(1,3,5,10)[i]: self.on_pen(x)) + self.btn_make.Bind( wx.EVT_BUTTON, self.on_make) + + def on_make(self, event): + Macros(None, ['Build Mark Image>None']).start(self.app) + + def on_fill(self, event): + tol = self.app.get_plugin('Flood Fill')() + tol.para['tor'] = 0 + tol.start(self.app) + + def on_pen(self, width): + tol = self.app.get_plugin('Pencil')() + tol.para['width'] = width + tol.start(self.app) + + def on_color(self, event): + self.app.manager('color').add('front', self.btns.index(event.GetEventObject())) + + def on_items(self, event): + items = ['No Background Image']+self.app.img_names() + self.com_back.SetItems(items) + if self.com_back.GetValue() in items: + self.com_back.Select(items.index(self.com_back.GetValue())) + else: self.com_back.Select(0) + + def on_cmapsel(self, event): + key = self.cmapsel.GetValue() + lut = ColorManager.get(key) + n = self.spn_num.GetValue()+1 + idx = np.linspace(0, 255, n).astype(int) + self.cs = list(lut[idx]) + [(128,128,128)]*(16-n) + for btn, c in zip(self.btns, self.cs): + btn.SetBackgroundColour(c) + + ips = self.app.get_img() + if ips is None: return + newlut = lut*0 + newlut[:n] = lut[idx] + ips.lut = newlut + ips.update() + + def on_setback(self, event): + name = self.com_back.GetValue() + if name is None: return + self.app.get_img().back = self.app.get_img(name) + #curwin = WindowsManager.get() + #curwin.set_back(ImageManager.get(name)) + self.app.get_img().update() + + def on_mode(self, event): + ips = self.app.get_img() + if ips is None: return + if self.chk_hide.GetValue(): + ips.mode = 0.0 + return ips.update() + modes = ['set', 'max', 'min', 'msk', 0.2, 0.4, 0.5, 0.6, 0.8] + ips.mode = modes[self.com_mode.GetSelection()] + ips.update() + + def __del__( self ): + pass + \ No newline at end of file diff --git a/imagepy/menus/Process/Classify/predict_plg.py b/imagepy/menus/Process/Classify/predict_plg.py new file mode 100644 index 00000000..9aab7a4d --- /dev/null +++ b/imagepy/menus/Process/Classify/predict_plg.py @@ -0,0 +1,41 @@ +from sciapp.action import Simple +from sciapp.object import Image +from glob import glob +import os.path as osp +import joblib +from imagepy.ipyalg import feature +from imagepy import root_dir + +class Plugin(Simple): + title = 'Feature Predictor' + note = ['all'] + + para = {'predictor':None, 'slice':False} + view = [(list, 'predictor', [''], str, 'predictor', ''), + (bool, 'slice', 'slice')] + + def __init__(self, ips=None, path=''): + Simple.__init__(self) + self.model = None + self.root = osp.join(root_dir, 'data/ilastik') + fs = glob(osp.join(self.root, '*.fcl')) + fs = [osp.split(i)[1] for i in fs] + if '/' in path: fs = [path] + if len(fs) == 0: return self.app.alert('No feature classfier found!') + self.para['predictor'] = fs[0] + self.view[0] = (list, 'predictor', fs, str, 'predictor', '') + + + def run(self, ips, imgs, para=None): + if '/' in para['predictor']: path = para['predictor'] + else: path = self.root+'/'+para['predictor'] + model, key = joblib.load(path) + if not para['slice']: imgs = [ips.img] + slir, slic = ips.rect + imgs = [i[slir, slic] for i in imgs] + rst = feature.get_predict(imgs, model, key, callback=self.progress) + if rst is None: + return self.app.alert('image channels dismatch this predictor!') + ips = Image(rst, ips.title+'-mark') + ips.range = ips.get_updown('all', 'one', step=512) + self.app.show_img(ips) \ No newline at end of file diff --git a/imagepy/menus/Process/FFT/__init__.py b/imagepy/menus/Process/FFT/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Process/FFT/fft_plgs.py b/imagepy/menus/Process/FFT/fft_plgs.py new file mode 100644 index 00000000..e4bab2ce --- /dev/null +++ b/imagepy/menus/Process/FFT/fft_plgs.py @@ -0,0 +1,75 @@ +import numpy as np +from sciapp.action import Simple, Filter +from numpy.fft import fft2, ifft2, fftshift, ifftshift +from sciapp.object import Image +#from imagepy.core import ImagePlus + +class FFT(Simple): + title = 'FFT' + note = ['8-bit', '16-bit', 'int', 'float'] + para = {'shift':True, 'slice':False} + view = [(bool, 'shift', 'zero center'), + (bool, 'slice', 'slices')] + + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + shift = fftshift if para['shift'] else lambda x:x + rst = [] + for i in range(len(imgs)): + rst.append(shift(fft2(imgs[i]))) + self.progress(i, len(imgs)) + ips = Image(rst, '%s-fft'%ips.title) + ips.log = True + self.app.show_img(ips) + +class IFFT(Simple): + title = 'Inverse FFT' + note = ['complex'] + para = {'shift':True, 'slice':False, 'type':'float'} + view = [(list, 'type', ['uint8', 'int', 'float'], str, 'type', ''), + (bool, 'shift', 'zero center'), + (bool, 'slice', 'slices')] + + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + shift = ifftshift if para['shift'] else lambda x:x + tp = {'uint8':np.uint8, 'int':np.int32, 'float':np.float32} + rst, tp = [], tp[para['type']] + for i in range(len(imgs)): + rst.append(ifft2(shift(ips.img)).astype(tp)) + self.progress(i, len(imgs)) + self.app.show_img(rst, '%s-ifft'%ips.title) + +class Shift(Filter): + title = 'Zero Center' + note = ['complex'] + + def run(self, ips, snap, img, para = None): + return fftshift(img) + +class IShift(Filter): + title = 'Zero Edge' + note = ['complex'] + + def run(self, ips, snap, img, para = None): + return ifftshift(img) + +class Split(Simple): + title = 'Split Real And Image' + note = ['complex'] + para = {'slice':False, 'copy':False} + view = [(bool, 'slice', 'slices'), + (bool, 'copy', 'memory copy')] + + def run(self, ips, imgs, para = None): + if not para['slice']: imgs = [ips.img] + copy = np.copy if para['copy'] else lambda x:x + imags, reals = [], [] + for i in range(len(imgs)): + reals.append(copy(imgs[i].real)) + imags.append(copy(imgs[i].imag)) + self.progress(i, len(imgs)) + self.app.show_img(reals, '%s-real'%ips.title) + self.app.show_img(imags, '%s-image'%ips.title) + +plgs = [FFT, IFFT, '-', Shift, IShift, '-', Split] \ No newline at end of file diff --git a/imagepy/menus/Process/Features/__init__.py b/imagepy/menus/Process/Features/__init__.py index e69de29b..6c24d1c3 100644 --- a/imagepy/menus/Process/Features/__init__.py +++ b/imagepy/menus/Process/Features/__init__.py @@ -0,0 +1 @@ +catlog = ['edge_plgs', '-', 'corner_plgs', '-', 'ridge_plgs', '-', 'blob_plgs'] \ No newline at end of file diff --git a/imagepy/menus/Process/Features/blob_plgs.py b/imagepy/menus/Process/Features/blob_plgs.py new file mode 100644 index 00000000..373de9de --- /dev/null +++ b/imagepy/menus/Process/Features/blob_plgs.py @@ -0,0 +1,169 @@ +import numpy as np +from sciapp.action import Simple +from skimage.feature import blob_dog, blob_doh, blob_log +from sciapp.object import mark2shp +import pandas as pd + +class Dog(Simple): + title = 'Blob Dog' + note = ['all', 'preview'] + para = {'min_sigma':1, 'max_sigma':50, 'sigma_ratio':1.6, 'threshold':0.1, + 'overlap':0.5, 'exclude_border':False, 'showid':True, 'slice':False} + + view = [(int, 'min_sigma', (1, 50), 0, 'min', 'sigma'), + (int, 'max_sigma', (1, 50), 0, 'max', 'sigma'), + (float, 'sigma_ratio', (1.3, 5), 1, 'ratio', '1.3~5'), + (float, 'threshold', (0.01, 10), 2, 'threshold', '0.01~10'), + (float, 'overlap', (0, 10), 1, 'overlap', ''), + (bool, 'exclude_border', 'exclude border'), + (bool, 'showid', 'show id on image'), + (bool, 'slice', 'slice')] + + def preview(self, ips, para): + grayimg = ips.img if ips.img.ndim==2 else ips.img.mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_dog(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + sigma_ratio=para['sigma_ratio'], threshold=para['threshold'], + overlap=para['overlap'], exclude_border=para['exclude_border']) + pts[:,2] *= np.sqrt(2) + ips.mark = mark2shp({'type':'circles', 'body':pts[:,[1,0,2]]}) + + def cancel(self, ips): ips.mark = None + + def run(self, ips, imgs, para = None): + if not para['slice']:imgs = [ips.img] + + data, sid, fid, mark = [], [], [], {'type':'layers', 'body':{}} + + for i in range(len(imgs)): + grayimg = imgs[i] if imgs[i].ndim==2 else imgs[i].mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_dog(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + sigma_ratio=para['sigma_ratio'], threshold=para['threshold'], + overlap=para['overlap'], exclude_border=para['exclude_border']) + pts[:,2] *= np.sqrt(2) + sid.extend([i]*len(pts)) + fid.extend(range(1, len(pts)+1)) + data.append(pts) + + layer = {'type':'layer', 'body':[{'type':'circles', 'body':pts[:,[1,0,2]]}]} + if para['showid']: + layer['body'].append({'type':'texts', 'body':[ + (x,y,'id=%d'%i) for (x,y),i in zip(pts[:,1::-1], fid)]}) + mark['body'][i] = layer + + ips.mark = mark2shp(mark) + df = pd.DataFrame(np.vstack(data)*ips.unit[0], columns = ['X', 'Y', 'R']) + df.insert(0, 'FID', fid) + if para['slice']: df.insert(o, 'SliceID', sid) + self.app.show_table(df, ips.title+'-dogblob') + +class Doh(Simple): + title = 'Blob Doh' + note = ['all', 'preview'] + para = {'min_sigma':1, 'max_sigma':30, 'num_sigma':10, 'threshold':0.01, + 'overlap':0.5, 'log_scale':False, 'showid':True, 'slice':False} + + view = [(int, 'min_sigma', (1, 50), 0, 'min', 'sigma'), + (int, 'max_sigma', (1, 50), 0, 'max', 'sigma'), + (int, 'num_sigma', (5, 30), 0, 'num', 'sigma'), + (float, 'threshold', (0.01, 1), 2, 'threshold', '0.01~10'), + (float, 'overlap', (0, 10), 1, 'overlap', ''), + (bool, 'log_scale', 'log scale'), + (bool, 'showid', 'show id on image'), + (bool, 'slice', 'slice')] + + def preview(self, ips, para): + grayimg = ips.img if ips.img.ndim==2 else ips.img.mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_doh(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + num_sigma=para['num_sigma'], threshold=para['threshold'], + overlap=para['overlap'], log_scale=para['log_scale']) + ips.mark = mark2shp({'type':'circles', 'body':pts[:,[1,0,2]]}) + + def cancel(self, ips): ips.mark = None + + def run(self, ips, imgs, para = None): + if not para['slice']:imgs = [ips.img] + + data, sid, fid, mark = [], [], [], {'type':'layers', 'body':{}} + + for i in range(len(imgs)): + grayimg = imgs[i] if imgs[i].ndim==2 else imgs[i].mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_doh(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + num_sigma=para['num_sigma'], threshold=para['threshold'], + overlap=para['overlap'], log_scale=para['log_scale']) + + sid.extend([i]*len(pts)) + fid.extend(range(1, len(pts)+1)) + data.append(pts) + + layer = {'type':'layer', 'body':[{'type':'circles', 'body':pts[:,[1,0,2]]}]} + if para['showid']: + layer['body'].append({'type':'texts', 'body':[ + (x,y,'id=%d'%i) for (x,y),i in zip(pts[:,1::-1], fid)]}) + mark['body'][i] = layer + + ips.mark = mark2shp(mark) + df = pd.DataFrame(np.vstack(data)*ips.unit[0], columns = ['X', 'Y', 'R']) + df.insert(0, 'FID', fid) + if para['slice']: df.insert(o, 'SliceID', sid) + self.app.show_table(df, ips.title+'-dohblob') + +class Log(Simple): + title = 'Blob Log' + note = ['all', 'preview'] + para = {'min_sigma':1, 'max_sigma':30, 'num_sigma':10, 'threshold':0.1, 'overlap':0.5, + 'log_scale':False, 'showid':True, 'exclude_border':False, 'slice':False} + + view = [(int, 'min_sigma', (1, 50), 0, 'min', 'sigma'), + (int, 'max_sigma', (1, 50), 0, 'max', 'sigma'), + (int, 'num_sigma', (5, 30), 0, 'num', 'sigma'), + (float, 'threshold', (0.02, 1), 2, 'threshold', '0.02~1'), + (float, 'overlap', (0, 10), 1, 'overlap', ''), + (bool, 'log_scale', 'log scale'), + (bool, 'exclude_border', 'exclude border'), + (bool, 'showid', 'show id on image'), + (bool, 'slice', 'slice')] + + def preview(self, ips, para): + grayimg = ips.img if ips.img.ndim==2 else ips.img.mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_log(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + num_sigma=para['num_sigma'], threshold=para['threshold'], + overlap=para['overlap'], log_scale=para['log_scale'], exclude_border=para['exclude_border']) + pts[:,2] *= np.sqrt(2) + ips.mark = mark2shp({'type':'circles', 'body':pts[:,[1,0,2]]}) + + def cancel(self, ips): ips.mark = None + + def run(self, ips, imgs, para = None): + if not para['slice']:imgs = [ips.img] + + data, sid, fid, mark = [], [], [], {'type':'layers', 'body':{}} + + for i in range(len(imgs)): + grayimg = imgs[i] if imgs[i].ndim==2 else imgs[i].mean(axis=-1) + grayimg /= grayimg.max() + pts = blob_log(grayimg, min_sigma=para['min_sigma'], max_sigma=para['max_sigma'], + num_sigma=para['num_sigma'], threshold=para['threshold'], + overlap=para['overlap'], log_scale=para['log_scale'], exclude_border=para['exclude_border']) + pts[:,2] *= np.sqrt(2) + sid.extend([i]*len(pts)) + fid.extend(range(1, len(pts)+1)) + data.append(pts) + + layer = {'type':'layer', 'body':[{'type':'circles', 'body':pts[:,[1,0,2]]}]} + if para['showid']: + layer['body'].append({'type':'texts', 'body':[ + (x,y,'id=%d'%i) for (x,y),i in zip(pts[:,1::-1], fid)]}) + mark['body'][i] = layer + + ips.mark = mark2shp(mark) + df = pd.DataFrame(np.vstack(data)*ips.unit[0], columns = ['X', 'Y', 'R']) + df.insert(0, 'FID', fid) + if para['slice']: df.insert(o, 'SliceID', sid) + self.app.show_table(df, ips.title+'-dohblob') + +plgs = [Dog, Doh, Log] \ No newline at end of file diff --git a/imagepy/menus/Process/Features/corner_plgs.py b/imagepy/menus/Process/Features/corner_plgs.py index 042480c1..cc9407ee 100644 --- a/imagepy/menus/Process/Features/corner_plgs.py +++ b/imagepy/menus/Process/Features/corner_plgs.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -* from skimage import feature -from imagepy.core.engine import Filter -from imagepy.core.roi import PointRoi +from sciapp.action import Filter +from sciapp.object import Points, ROI class Harris(Filter): title = 'Harris' @@ -13,7 +13,7 @@ class Harris(Filter): def run(self, ips, snap, img, para = None): cimg = feature.corner_harris(img, sigma=para['sigma'], k=para['k']) pts = feature.corner_peaks(cimg, min_distance=1) - self.ips.roi = PointRoi([tuple(i[::-1]) for i in pts]) + ips.roi = ROI([Points(pts[:,::-1])]) class Kitchen(Filter): title = 'Kitchen Rosenfeld' @@ -24,7 +24,7 @@ class Kitchen(Filter): def run(self, ips, snap, img, para = None): cimg = feature.corner_kitchen_rosenfeld(img, mode='constant', cval=para['cval']) pts = feature.corner_peaks(cimg, min_distance=1) - self.ips.roi = PointRoi([tuple(i[::-1]) for i in pts]) + ips.roi = ROI([Points(pts[:,::-1])]) class Moravec(Filter): title = 'Moravec' @@ -35,7 +35,7 @@ class Moravec(Filter): def run(self, ips, snap, img, para = None): cimg = feature.corner_moravec(img, window_size=para['size']) pts = feature.corner_peaks(cimg, min_distance=1) - self.ips.roi = PointRoi([tuple(i[::-1]) for i in pts]) + ips.roi = ROI([Points(pts[:,::-1])]) class Tomasi(Filter): title = 'Tomasi' @@ -46,6 +46,6 @@ class Tomasi(Filter): def run(self, ips, snap, img, para = None): cimg = feature.corner_shi_tomasi(img, sigma=para['sigma']) pts = feature.corner_peaks(cimg, min_distance=1) - self.ips.roi = PointRoi([tuple(i[::-1]) for i in pts]) + ips.roi = ROI([Points(pts[:,::-1])]) plgs = [Harris, Kitchen, Moravec, Tomasi] \ No newline at end of file diff --git a/imagepy/menus/Process/Features/edge_plgs.py b/imagepy/menus/Process/Features/edge_plgs.py index c352f078..bcf9134b 100644 --- a/imagepy/menus/Process/Features/edge_plgs.py +++ b/imagepy/menus/Process/Features/edge_plgs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -* from skimage import feature -from imagepy.core.engine import Filter +from sciapp.action import Filter class Canny(Filter): title = 'Canny' @@ -12,6 +12,6 @@ class Canny(Filter): def run(self, ips, snap, img, para = None): return feature.canny(snap, sigma=para['sigma'], low_threshold=para[ - 'low_threshold'], high_threshold=para['high_threshold'], mask=ips.get_msk())*255 + 'low_threshold'], high_threshold=para['high_threshold'], mask=ips.mask())*255 plgs = [Canny] \ No newline at end of file diff --git a/imagepy/menus/Process/Features/ridge_plgs.py b/imagepy/menus/Process/Features/ridge_plgs.py new file mode 100644 index 00000000..d6c3e9f1 --- /dev/null +++ b/imagepy/menus/Process/Features/ridge_plgs.py @@ -0,0 +1,93 @@ +from skimage.filters import frangi, sato, hessian ,meijering +from skimage.feature import structure_tensor, structure_tensor_eigenvalues +from sciapp.action import Filter, Simple +import numpy as np + +def scale(img, low, high): + img *= (high-low)/(max(img.ptp(), 1e-5)) + img += low - img.min() + return img + +class Frangi(Filter): + title = 'Frangi' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'start':1, 'end':10, 'step':2, 'alpha':0.5, 'beta':0.5, 'gamma':15, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (float, 'alpha', (0.1, 1), 1, 'alpha',''), + (float, 'beta', (0.1, 1), 1, 'beta',''), + (float, 'gamma', (1,30), 1, 'gamma',''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, snap, img, para = None): + rst = frangi(snap, range(para['start'], para['end'], para['step']), alpha=para['alpha'], + beta=para['beta'], gamma=para['gamma'], black_ridges=para['bridges']) + img[:] = scale(rst, ips.range[0], ips.range[1]) + +class Meijering(Filter): + title = 'Meijering' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'start':1, 'end':10, 'step':2, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, snap, img, para = None): + rst =meijering(snap, range(para['start'], para['end'], para['step']), black_ridges=para['bridges']) + print('hahaha') + img[:] = scale(rst, ips.range[0], ips.range[1]) + +class Sato(Filter): + title = 'Sato' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'start':1, 'end':10, 'step':2, 'bridges':False} + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, snap, img, para = None): + rst = sato(snap, range(para['start'], para['end'], para['step']), black_ridges=para['bridges']) + img[:] = scale(rst, ips.range[0], ips.range[1]) + +class Hessian(Filter): + title = 'Hessian' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'start':1, 'end':10, 'step':2, 'alpha':0.5, 'beta':0.5, 'gamma':15, 'bridges':False} + + view = [(int, 'start', (1,10), 0, 'sigma start', ''), + (int, 'end', (1,20), 0, 'sigma end', ''), + (int, 'step', (1,5), 0, 'sigma step', ''), + (float, 'alpha', (0.1, 1), 1, 'alpha',''), + (float, 'beta', (0.1, 1), 1, 'beta',''), + (float, 'gamma', (1,30), 1, 'gamma',''), + (bool, 'bridges', 'black ridges')] + + def run(self, ips, snap, img, para = None): + rst = hessian(snap, range(para['start'], para['end'], para['step']), alpha=para['alpha'], + beta=para['beta'], gamma=para['gamma'], black_ridges=para['bridges']) + img[:] = scale(rst, ips.range[0], ips.range[1]) + +class StructureTensor(Filter): + title = 'Structure Tensor' + note = ['all', 'auto_msk', 'auto_snap', 'preview', '2float'] + para = {'sigma':2, 'axis':'major', 'log':False} + + view = [(float, 'sigma', (0, 20), 1, 'sigma','pix'), + (list, 'axis', ['major', 'minor', 'both'], str, 'axis', ''), + (bool, 'log', 'log')] + + def run(self, ips, snap, img, para = None): + axx, axy, ayy = structure_tensor(snap, sigma=para['sigma']) + l1, l2 = structure_tensor_eigvals(axx, axy, ayy) + if para['axis']=='major': rst = l1 + elif para['axis']=='minor': rst = l2 + else: rst = (l1**2 + l2**2)**0.5 + if para['log']: rst += 1; np.log(rst, out=rst) + img[:] = scale(rst, ips.range[0], ips.range[1]) + +plgs = [Frangi, Meijering, Sato, Hessian, StructureTensor] diff --git a/imagepy/menus/Process/Filters/classic_plgs.py b/imagepy/menus/Process/Filters/classic_plgs.py index f843bd2d..7a5f34a1 100644 --- a/imagepy/menus/Process/Filters/classic_plgs.py +++ b/imagepy/menus/Process/Filters/classic_plgs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -* import scipy.ndimage as nimg -from imagepy.core.engine import Filter, Simple +from sciapp.action import Filter, Simple import numpy as np class Uniform(Filter): @@ -16,15 +16,12 @@ def run(self, ips, snap, img, para = None): nimg.uniform_filter(snap, para['size'], output=img) class Gaussian(Filter): - """Gaussian: derived from imagepy.core.engine.Filter """ title = 'Gaussian' note = ['all', 'auto_msk', 'auto_snap','preview'] para = {'sigma':2} view = [(float, 'sigma', (0,30), 1, 'sigma', 'pix')] def run(self, ips, snap, img, para = None): - #l = int(para['sigma']*3)*2+1 - #cv2.GaussianBlur(snap, (l, l), para['sigma'], dst=img) nimg.gaussian_filter(snap, para['sigma'], output=img) class GaussianLaplace(Filter): @@ -163,6 +160,19 @@ def run(self, ips, snap, img, para = None): np.multiply(img, -para['weight'], out=img, casting='unsafe') img += snap +class Variance(Filter): + title = 'Variance' + note = ['all', 'auto_msk', '2float', 'auto_snap','preview'] + + #parameter + para = {'size':2} + view = [(float, 'size', (0,30), 1, 'size', 'pix')] + + #process + def run(self, ips, snap, img, para = None): + nimg.uniform_filter(snap**2, para['size'], output=img) + img -= nimg.uniform_filter(snap, para['size'])**2 + class USM(Filter): title = 'Unsharp Mask' note = ['all', 'auto_msk', 'auto_snap', '2int', 'preview'] @@ -180,4 +190,12 @@ def run(self, ips, snap, img, para = None): img += snap plgs = [Uniform, Gaussian, '-', Maximum, Minimum, Median, Percent, '-', - Prewitt, Sobel, Laplace, GaussianLaplace, DOG, '-', LaplaceSharp, USM] \ No newline at end of file + Prewitt, Sobel, Laplace, GaussianLaplace, DOG, '-', Variance, LaplaceSharp, USM] + +if __name__ == '__main__': + from skimage.data import camera, astronaut + from sciwx.app import ImageApp + + ImageApp.start( + imgs = [('astronaut', astronaut())], + plgs=[('U', USM), ('G', Gaussian)]) \ No newline at end of file diff --git a/imagepy/menus/Process/Hydrology/hydrology_plgs.py b/imagepy/menus/Process/Hydrology/hydrology_plgs.py index a487d7a8..9d5c85b3 100644 --- a/imagepy/menus/Process/Hydrology/hydrology_plgs.py +++ b/imagepy/menus/Process/Hydrology/hydrology_plgs.py @@ -1,13 +1,12 @@ import scipy.ndimage as ndimg import numpy as np -from numba import jit -from imagepy.core.engine import Filter +from sciapp.action import Filter from imagepy.ipyalg import find_maximum, ridge, stair, isoline, watershed -from imagepy.core.roi import PointRoi +# from imagepy.core.roi import PointRoi +from sciapp.object import Points, ROI #from skimage.morphology import watershed, disk from skimage.filters import rank from skimage.filters import sobel -from imagepy import IPy class IsoLine(Filter): title = 'Find IsoLine' @@ -43,8 +42,8 @@ class FindMax(Filter): def run(self, ips, snap, img, para = None): pts = find_maximum(self.ips.img, para['tol']) - self.ips.roi = PointRoi([tuple(i) for i in pts[:,::-1]]) - self.ips.update = True + ips.roi = ROI([Points(pts[:,::-1])]) + ips.update() class FindMin(Filter): title = 'Find Minimum' @@ -55,11 +54,11 @@ class FindMin(Filter): def run(self, ips, snap, img, para = None): pts = find_maximum(self.ips.img, para['tol'], False) - self.ips.roi = PointRoi([tuple(i) for i in pts[:,::-1]]) - self.ips.update = True + ips.roi = ROI([Points(pts[:,::-1])]) + ips.update() class UPRidge(Filter): - title = 'Find Riedge' + title = 'Find Ridge' note = ['8-bit', 'not_slice', 'auto_snap', 'not_channel', 'preview'] para = {'sigma':1.0, 'thr':0, 'ud':True, 'type':'white line'} @@ -79,7 +78,7 @@ def preview(self, ips, para): ips.lut[:para['thr']] = [0,255,0] else: ips.lut[para['thr']:] = [255,0,0] - ips.update = 'pix' + ips.update() #process def run(self, ips, snap, img, para = None): @@ -99,11 +98,11 @@ def run(self, ips, snap, img, para = None): class ARidge(Filter): title = 'Active Ridge' - note = ['8-bit', 'not_slice', 'auto_snap', 'not_channel'] + note = ['8-bit', 'not_slice', 'auto_snap', 'not_channel', 'req_roi'] para = {'sigma':1.0, 'ud':True, 'type':'white line'} - view = [(float, (0,5), 1, 'sigma', 'sigma', 'pix'), - (list, 'type', ['white line', 'gray line', 'white line on ori'], str, 'output', ''), + view = [(float, 'sigma', (0,5), 1, 'sigma', 'pix'), + (list, 'type', ['white line', 'gray line', 'white line on ori'], str, 'output', ''), (bool, 'ud', 'ascend')] def run(self, ips, snap, img, para = None): @@ -123,7 +122,7 @@ class Watershed(Filter): note = ['8-bit', 'auto_snap', 'not_channel', 'preview'] para = {'sigma':1.0, 'thr':0, 'con':False, 'ud':True, 'type':'white line'} - view = [(float, (0,5), 1, 'sigma', 'sigma', 'pix'), + view = [(float, 'sigma', (0,5), 1, 'sigma', 'pix'), ('slide', 'thr', (0,255), 0, 'Low'), (bool, 'con', 'full connectivity'), (bool, 'ud', 'ascend'), @@ -140,7 +139,7 @@ def preview(self, ips, para): ips.lut[:para['thr']] = [0,255,0] else: ips.lut[para['thr']:] = [255,0,0] - ips.update = 'pix' + ips.update() #process def run(self, ips, snap, img, para = None): @@ -178,11 +177,11 @@ def preview(self, ips, para): ips.lut[:] = self.buflut ips.lut[:para['thr1']] = [0,255,0] ips.lut[para['thr2']:] = [255,0,0] - ips.update = 'pix' + ips.update() def cancel(self, ips): ips.lut = self.buflut - ips.update = 'pix' + ips.update() #process def run(self, ips, snap, img, para = None): @@ -214,11 +213,11 @@ def run(self, ips, snap, img, para = None): #gradient = rank.gradient(denoised, disk(para['gdt'])) ndimg.gaussian_filter(snap, para['sigma'], output=img) - markers, n = ndimg.label(ips.get_msk(), np.ones((3,3)), output=np.uint32) + markers, n = ndimg.label(ips.mask(), np.ones((3,3)), output=np.uint32) if not para['ud']:img[:] = 255-img mark = watershed(img, markers, line=True, conn=para['con']+1) mark = np.multiply((mark==0), 255, dtype=np.uint8) - + if para['type'] == 'white line': img[:] = mark if para['type'] == 'gray line': diff --git a/imagepy/menus/Process/Math/math_plgs.py b/imagepy/menus/Process/Math/math_plgs.py index 63f4049a..67752bd7 100644 --- a/imagepy/menus/Process/Math/math_plgs.py +++ b/imagepy/menus/Process/Math/math_plgs.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -* import numpy as np -from imagepy.core.engine import Filter +from sciapp.action import Filter class Add(Filter): - """Add_plg: derived from imagepy.core.engine.Filter """ + """Add_plg: derived from sciapp.action.Filter """ title = 'Add' note = ['all', 'auto_msk', 'auto_snap', 'preview', '2int'] para = {'num':0} @@ -11,9 +11,19 @@ class Add(Filter): def run(self, ips, snap, img, para = None): np.add(snap, para['num'], out=img, casting='unsafe') - + +class Subtract(Filter): + """Subtract_plg: derived from sciapp.action.Filter """ + title = 'Subtract' + note = ['all', 'auto_msk', 'auto_snap', 'preview', '2int'] + para = {'num':0} + view = [(float, 'num', (-255,255), 2, '-255', '+255')] + + def run(self, ips, snap, img, para = None): + np.subtract(snap, para['num'], out=img, casting='unsafe') + class Multiply(Filter): - """Multiply_plg: derived from imagepy.core.engine.Filter """ + """Multiply_plg: derived from sciapp.action.Filter """ title = 'Multiply' note = ['all', 'auto_msk', 'auto_snap', 'preview', '2int'] para = {'num':0} @@ -23,7 +33,7 @@ def run(self, ips, snap, img, para = None): np.multiply(snap, para['num'], out=img, casting='unsafe') class Max(Filter): - """Max_plg: derived from imagepy.core.engine.Filter """ + """Max_plg: derived from sciapp.action.Filter """ title = 'Max' note = ['all', 'auto_msk', 'auto_snap', 'preview'] para = {'num':0} @@ -34,7 +44,7 @@ def run(self, ips, snap, img, para = None): img[imgpara['num']] = para['num'] - + + class Sqrt(Filter): - """Sqrt_plg: derived from imagepy.core.engine.Filter """ - title = 'Squre Root' + """Sqrt_plg: derived from sciapp.action.Filter """ + title = 'Square Root' note = ['all', 'auto_msk', 'auto_snap', 'preview'] def run(self, ips, snap, img, para = None): - np.sqrt(snap, out=img) + np.sqrt(snap, out=img, casting='unsafe') -class Garmma(Filter): - """Garmma_plg: derived from imagepy.core.engine.Filter """ - title = 'Garmma' +class Gamma(Filter): + """Garmma_plg: derived from sciapp.action.Filter """ + title = 'Gamma' note = ['all', 'auto_msk', 'auto_snap', 'preview', '2float'] para = {'num':0} view = [(float, 'num', (-255,255), 2, '0.1', '10')] @@ -64,4 +75,4 @@ def run(self, ips, snap, img, para = None): img[:] = snap img[:] = (img/(x2-x1))**para['num']*(x2-x1) -plgs = [Add, Multiply, '-', Max, Min, '-', Sqrt, Garmma] \ No newline at end of file +plgs = [Add, Subtract, Multiply, '-', Max, Min, '-', Sqrt, Gamma] \ No newline at end of file diff --git a/imagepy/menus/Process/Segment/__init__.py b/imagepy/menus/Process/Segment/__init__.py new file mode 100644 index 00000000..47a61deb --- /dev/null +++ b/imagepy/menus/Process/Segment/__init__.py @@ -0,0 +1 @@ +catlog = ['shift_plgs', '-', 'graph_plgs', '-', 'active_plgs'] \ No newline at end of file diff --git a/imagepy/menus/Process/Segment/active_plgs.py b/imagepy/menus/Process/Segment/active_plgs.py new file mode 100644 index 00000000..f099fae5 --- /dev/null +++ b/imagepy/menus/Process/Segment/active_plgs.py @@ -0,0 +1,189 @@ +import numpy as np +from scipy.ndimage import binary_dilation +from sciapp.action import Filter +from skimage import img_as_float +from skimage.segmentation import chan_vese, morphological_chan_vese, \ + morphological_geodesic_active_contour, inverse_gaussian_gradient, \ + clear_border, random_walker + +class ChanVese(Filter): + title = 'Evolving Level Set' + note = ['all', 'not_slice', 'auto_snap', 'preview'] + + para = {'mu':0.25, 'lambda1':1.0, 'lambda2':1.0, 'tol':0.001, 'max_iter':500, + 'dt':0.5, 'init_level_set':'checkerboard', 'extended_output':False, 'out':'mask'} + + view = [(float, 'mu', (0,1), 2, 'mu', ''), + (float, 'lambda1', (0,10), 2, 'lambda1', ''), + (float, 'lambda2', (0,10), 2, 'lambda2', ''), + (float, 'tol', (0,1), 3, 'tol', ''), + (int, 'max_iter', (0,512), 0, 'max_iter', 'times'), + (float, 'dt', (0,10), 2, 'dt', ''), + (list, 'init_level_set', ['checkerboard', 'disk', 'small disk'], str, 'init', ''), + (list, 'out', ['mask', 'line on ori'], str, 'output', '')] + + def run(self, ips, snap, img, para = None): + msk = chan_vese(snap, mu=para['mu'], lambda1=para['lambda1'], lambda2=para['lambda2'], + tol=para['tol'], max_iter=para['max_iter'], dt=para['dt'], init_level_set=para['init_level_set']) + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + +class MorphChanVese(Filter): + title = 'Morphological Snake Fit' + note = ['all', 'not_slice', 'auto_snap', 'preview'] + + para = {'iter':10, 'init':'checkerboard', 'smooth':1, 'lambda1':1.0, + 'lambda2':1.0, 'out':'mask', 'sub':False} + + view = [(int, 'iter', (0,64), 0, 'iterations', 'time'), + (list, 'init', ['checkerboard', 'circle'], str, 'init set', ''), + (int, 'smooth', (1, 10), 0, 'smoothing', ''), + (float, 'lambda1', (0,10), 2, 'lambda1', ''), + (float, 'lambda2', (0,10), 2, 'lambda2', ''), + (list, 'out', ['mask', 'line on ori'], str, 'output', ''), + (bool, 'sub', 'show sub stack')] + + def preview(self, ips, para): + snap, img = ips.snap, ips.img + msk = morphological_chan_vese(snap, para['iter'], init_level_set=para['init'], + smoothing=para['smooth'], lambda1=para['lambda1'], lambda2=para['lambda2']) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + ips.update() + + def run(self, ips, snap, img, para = None): + stackimg = [] + callback = lambda x: stackimg.append((x*255).astype(np.uint8)) if para['sub'] else 0 + msk = morphological_chan_vese(snap, para['iter'], init_level_set=para['init'], + smoothing=para['smooth'], lambda1=para['lambda1'], lambda2=para['lambda2'], + iter_callback=callback) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + if para['sub']: self.app.show_img(stackimg, ips.title+'-sub') + +class MorphGeoChanVese(Filter): + title = 'Bound Snake Fit' + note = ['all', 'not_slice', 'auto_snap', 'preview'] + + para = {'iter':10, 'smooth':1, 'thr':128, 'auto':True, 'balloon':-1, 'out':'mask', 'sub':False} + + view = [(int, 'iter', (0, 1024), 0, 'iterations', 'time'), + (int, 'smooth', (1, 10), 0, 'smoothing', ''), + (float, 'thr', (0,1e5), 2, 'threshold', ''), + (bool, 'auto', 'auto threshold'), + (float, 'balloon', (-5, 5), 1, 'balloon', ''), + (list, 'out', ['mask', 'line on ori'], str, 'output', ''), + (bool, 'sub', 'show sub stack')] + + def preview(self, ips, para): + snap, img = ips.snap, ips.img + gimage = inverse_gaussian_gradient(img_as_float(snap)) + init = np.ones(img.shape, dtype='bool') + msk = morphological_geodesic_active_contour(gimage, para['iter'], + init_level_set=init, smoothing=para['smooth'], + threshold='auto' if para['auto'] else para['thr'], balloon=para['balloon']) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + ips.update() + + def run(self, ips, snap, img, para = None): + stackimg = [] + callback = lambda x: stackimg.append((x*255).astype(np.uint8)) if para['sub'] else 0 + gimage = inverse_gaussian_gradient(img_as_float(snap)) + init = np.ones(img.shape, dtype='bool') + msk = morphological_geodesic_active_contour(gimage, para['iter'], + init_level_set=init, smoothing=para['smooth'], + threshold='auto' if para['auto'] else para['thr'], + balloon=para['balloon'], iter_callback=callback) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + if para['sub']: self.app.show_img(stackimg, ips.title+'-sub') + +class MorphGeoRoi(Filter): + title = 'ROI Snake Fit' + note = ['all', 'not_slice', 'auto_snap', 'req_roi', 'preview'] + + para = {'iter':10, 'smooth':1, 'thr':128, 'auto':True, 'balloon':-1, 'out':'mask', 'sub':False} + + view = [(int, 'iter', (0, 1024), 0, 'iterations', 'time'), + (int, 'smooth', (1, 10), 0, 'smoothing', ''), + (float, 'thr', (0,1e5), 2, 'threshold', ''), + (bool, 'auto', 'auto threshold'), + (float, 'balloon', (-5, 5), 1, 'balloon', ''), + (list, 'out', ['mask', 'line on ori'], str, 'output', ''), + (bool, 'sub', 'show sub stack')] + + def preview(self, ips, para): + snap, img = ips.snap, ips.img + gimage = inverse_gaussian_gradient(img_as_float(snap)) + init = ips.mask('out') + msk = morphological_geodesic_active_contour(gimage, para['iter'], + init_level_set=init, smoothing=para['smooth'], + threshold='auto' if para['auto'] else para['thr'], balloon=para['balloon']) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + ips.update() + + def run(self, ips, snap, img, para = None): + stackimg = [] + callback = lambda x: stackimg.append((x*255).astype(np.uint8)) if para['sub'] else 0 + gimage = inverse_gaussian_gradient(img_as_float(snap)) + init = ips.mask('out') + msk = morphological_geodesic_active_contour(gimage, para['iter'], + init_level_set=init, smoothing=para['smooth'], + threshold='auto' if para['auto'] else para['thr'], + balloon=para['balloon'], iter_callback=callback) > 0 + (c1, c2), img[:] = ips.range, snap + if para['out'] == 'mask': img[~msk], img[msk] = c1, c2 + else: img[binary_dilation(msk) ^ msk] = c2 + if para['sub']: self.app.show_img(stackimg, ips.title+'-sub') + +class RandomWalker(Filter): + title = 'Random Walker' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + modal = False + + def load(self, ips): + minv, maxv = ips.range + self.para = {'beta':130, 'mode':'bf', 'tol':0.001, 'thr1':minv, 'thr2':maxv, 'out':'mask'} + self.view = [('slide', 'thr1', (minv, maxv), 4, 'Low'), + ('slide', 'thr2', (minv,maxv), 4, 'High'), + (int, 'beta', (10,256), 0, 'beta', ''), + (list, 'mode', ['cg_mg', 'cg', 'bf'], str, 'mode', ''), + (float, 'tol', (0,1), 3, 'tolerance', ''), + (list, 'out', ['mask', 'line on ori'], str, 'output', '')] + + self.buflut = ips.lut + ips.lut = ips.lut.copy() + return True + + def cancel(self, ips): + ips.lut = self.buflut + ips.update() + + def preview(self, ips, para): + ips.lut[:] = self.buflut + minv, maxv = ips.range + lim1 = (para['thr1']-minv)*255/(maxv-minv) + lim2 = (para['thr2']-minv)*255/(maxv-minv) + ips.lut[:int(lim1)] = [0,255,0] + ips.lut[int(lim2):] = [255,0,0] + ips.update() + + #process + def run(self, ips, snap, img, para = None): + msk = np.zeros_like(img) + msk[img>para['thr2']] = 1 + msk[imgpara['thr2'], np.ones((3,3)), output=np.uint16) - sta = ndimg.sum(snap>para['thr1'], lab, index=range(n+1)) > 0 - img[:] = (sta*255)[lab] diff --git a/imagepy/menus/Process/Threshold/threshold_plgs.py b/imagepy/menus/Process/Threshold/threshold_plgs.py new file mode 100644 index 00000000..063487ea --- /dev/null +++ b/imagepy/menus/Process/Threshold/threshold_plgs.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Nov 18 22:56:50 2016 +@author: yxl +""" +import numpy as np +from sciapp.action import Filter +import scipy.ndimage as ndimg +from skimage.filters import\ + threshold_otsu, threshold_yen,\ + threshold_isodata, threshold_li, threshold_local,\ + threshold_minimum, threshold_mean, threshold_niblack,\ + threshold_sauvola, threshold_triangle, apply_hysteresis_threshold +from ...Image.Adjust.threshold_plg import Plugin as ThresholdPlg + +class SimpleThreshold(ThresholdPlg): + title = 'Simple Threshold' + +class Hysteresis(Filter): + title = 'Hysteresis Threshold' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + modal = False + + def load(self, ips): + minv, maxv = ips.range + self.para = {'low':maxv, 'high':maxv} + self.view = [('slide', 'low', (minv, maxv), 4, 'Low'), + ('slide', 'high', (minv, maxv), 4, 'High')] + + self.buflut = ips.lut + ips.lut = ips.lut.copy() + return True + + def cancel(self, ips): + ips.lut = self.buflut + ips.update() + + def preview(self, ips, para): + ips.lut[:] = self.buflut + minv, maxv = ips.range + lim1 = (para['low']-minv)*255/(maxv-minv) + lim2 = (para['high']-minv)*255/(maxv-minv) + ips.lut[int(lim1):] = [0,255,0] + ips.lut[int(lim2):] = [255,0,0] + ips.update() + + def run(self, ips, snap, img, para = None): + ips.lut = self.buflut + return apply_hysteresis_threshold(snap, para['low'], para['high'])*ips.range[1] + +class Auto(Filter): + title = 'Auto Threshold' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'method':'Otsu'} + view = [(list, 'method', ['Otsu', 'Yen', 'Isodata', 'Li', + 'Mini', 'Mean', 'Triangle'], str, 'Method', '')] + + def run(self, ips, snap, img, para = None): + key = {'Otsu':threshold_otsu, 'Yen':threshold_yen, + 'Isodata':threshold_isodata, 'Li':threshold_li, + 'Mini':threshold_minimum, 'Mean':threshold_mean, + 'Triangle':threshold_triangle} + img[:] = (snap>key[para['method']](snap))*ips.range[1] + +class Local(Filter): + title = 'Adaptive Threshold' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'method':'Mean', 'size':9, 'offset':2} + view = [(list, 'method', ['Gaussian', 'Mean', 'Median'], str, 'method', ''), + (int, 'size', (3, 31), 0, 'blocksize', 'pix'), + (int, 'offset', (0, 50), 0, 'offset', '')] + + def run(self, ips, snap, img, para = None): + img[:] = (snap>threshold_local(snap, para['size'], para['method'].lower(), para['offset']))*ips.range[1] + +class Niblack(Filter): + title = 'Niblack Threshold' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'size':15, 'k':0.2} + view = [(int, 'size', (3, 30), 0, 'blocksize', 'pix'), + (float, 'k', (0, 1), 2, 'offset', '')] + + def run(self, ips, snap, img, para = None): + if para['size']%2==0: return self.app.alert('size must be Odd') + img[:] = (snap>threshold_niblack(snap, para['size'], para['k']))*ips.range[1] + +class Sauvola(Filter): + title = 'Sauvola Threshold' + note = ['all', 'auto_msk', 'auto_snap', 'preview'] + para = {'size':15, 'k':0.2} + view = [(int, 'size', (3, 30), 0, 'blocksize', 'pix'), + (float, 'k', (0, 1), 2, 'offset', '')] + + def run(self, ips, snap, img, para = None): + if para['size']%2==0: return self.app.alert('size must be Odd') + img[:] = (snap>threshold_sauvola(snap, para['size'], para['k']))*ips.range[1] + +plgs = [SimpleThreshold, Auto, '-', Local, Niblack, Sauvola, '-', Hysteresis] \ No newline at end of file diff --git a/imagepy/menus/Process/__init__.py b/imagepy/menus/Process/__init__.py index 77f5f30a..647836fe 100644 --- a/imagepy/menus/Process/__init__.py +++ b/imagepy/menus/Process/__init__.py @@ -1 +1 @@ -catlog = ['Math', 'Binary', 'Filters', '-', 'Threshold', 'Hydrology', 'Features', '-', 'calculator_plg'] \ No newline at end of file +catlog = ['Math', 'Binary', 'Filters', 'FFT', '-', 'Threshold', 'Hydrology', 'Features', 'Segment', 'Classify', 'repair_plg', '-', 'calculator_plg'] \ No newline at end of file diff --git a/imagepy/menus/Process/calculator_plg.py b/imagepy/menus/Process/calculator_plg.py index ca900063..737af45f 100644 --- a/imagepy/menus/Process/calculator_plg.py +++ b/imagepy/menus/Process/calculator_plg.py @@ -1,55 +1,54 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 1 01:22:19 2016 -@author: yxl -""" -from imagepy.core.manager import ImageManager -from imagepy import IPy - -from imagepy.core.engine import Simple -from imagepy.core.pixel import bliter +from sciapp.action import Simple +import numpy as np class Plugin(Simple): - """Calculator Plugin derived from imagepy.core.engine.Simple """ title = 'Image Calculator' note = ['all'] - para = {'img1':None,'op':'add','img2':None} + para = {'op':'add','img':None} - view = [('img', 'img1', 'image1', ''), - (list, 'op', ['max', 'min', 'diff', 'add', 'substract'], str, 'operator', ''), - ('img', 'img2', 'image2', '')] + view = [(list, 'op', ['max', 'min', 'diff', 'add', 'substract'], str, 'operator', ''), + ('img', 'img', 'image', '')] def run(self, ips, imgs, para = None): - ips1 = ImageManager.get(para['img1']) - ips2 = ImageManager.get(para['img2']) - ips1.snapshot() + down, up = ips.range + ips2 = self.app.get_img(para['img']) + ips.snapshot() + + sl1, sl2 = ips.slices, ips2.slices + cn1, cn2 = ips.channels, ips2.channels - sl1, sl2 = ips1.get_nslices(), ips2.get_nslices() - cn1, cn2 = ips1.get_nchannels(), ips2.get_nchannels() - if ips1.dtype != ips2.dtype: - IPy.alert('Two stack must be equal dtype!') - return + if ips.dtype != ips2.dtype: + return self.app.alert('Two stack must be equal dtype!') elif sl1>1 and sl2>1 and sl1!=sl2: - IPy.alert('Two stack must have equal slices!') - return + return self.app.alert('Two stack must have equal slices!') elif cn1>1 and cn2>1 and cn1!=cn2: - IPy.alert('Two stack must have equal channels!') - return - - w, h = ips1.size, ips2.size - w, h = min(w[0], h[0]), min(w[1], h[1]) - if sl1 == 1: - bliter.blit(ips1.get_subimg(), ips2.get_subimg(), mode=para['op']) - elif sl1>1 and sl2==1: - for i in range(sl1): - self.progress(i, sl1) - ss1, se1 = ips1.get_rect() - bliter.blit(ips1.imgs[i][ss1, se1], ips2.get_subimg(), mode=para['op']) - elif sl1>1 and sl2>1: - for i in range(sl1): - self.progress(i, sl1) - ss1, se1 = ips1.get_rect() - ss2, se2 = ips2.get_rect() - bliter.blit(ips1.imgs[i][ss1, se1], ips2.imgs[i][ss2, se2], mode=para['op']) - ips1.update = 'pix' - \ No newline at end of file + return self.app.alert('Two stack must have equal channels!') + + imgs1, imgs2 = ips.subimg(), ips2.subimg() + if len(imgs1)==1: imgs1 = imgs1 * len(imgs2) + if len(imgs2)==1: imgs2 = imgs2 * len(imgs1) + if imgs1[0].shape[:2] != imgs2[0].shape[:2]: + return self.app.alert('Two image must be in equal shape') + + for i in range(len(imgs1)): + im1, im2 = imgs1[i], imgs2[i] + if cn1==1 and cn2>1: + im2 = im2.mean(axis=-1, dtype=np.float32) + if cn2==1 and cn1>1: + im2 = im2[:,:,None] * np.ones(cn1) + + if para['op'] == 'max': + msk = im1 < im2 + im1[msk] = im2[msk] + if para['op'] == 'min': + msk = im1 > im2 + im1[msk] = im2[msk] + if para['op'] == 'diff': + dif = np.abs(im1.astype(np.float32) - im2) + np.clip(dif, down, up, out=im1) + if para['op'] == 'add': + dif = im1.astype(np.float32) + im2 + np.clip(dif, down, up, out=im1) + if para['op'] == 'substract': + dif = im1.astype(np.float32) - im2 + np.clip(dif, down, up, out=im1) \ No newline at end of file diff --git a/imagepy/menus/Process/repair_plg.py b/imagepy/menus/Process/repair_plg.py new file mode 100644 index 00000000..e696c3c2 --- /dev/null +++ b/imagepy/menus/Process/repair_plg.py @@ -0,0 +1,21 @@ +from sciapp.action import Filter +import numpy as np +import scipy.ndimage as ndimg +from imagepy.ipyalg import distance_transform_edt + +class Plugin(Filter): + title = 'Fragment Repair' + note = ['all', 'req_roi', 'auto_msk', 'auto_snap', 'preview'] + para = {'mode':'nearest'} + view = [(list, 'mode', ['nearest', 'mean'], str, 'replace by', 'pix')] + + def run(self, ips, snap, img, para = None): + msk = ips.mask() + if self.para['mode']=='nearest': + rr, cc = ndimg.distance_transform_edt(msk, return_distances=False, return_indices=True) + img[:] = snap[rr, cc] + else: + lab1, n = ndimg.label(msk) + lab2 = ndimg.maximum_filter(lab1, 3) + idx = ndimg.mean(img, lab2-lab1, np.arange(1,n+1)) + img[msk] = idx[lab1[msk]-1] \ No newline at end of file diff --git a/imagepy/menus/Selection/Relation/__init__.py b/imagepy/menus/Selection/Relation/__init__.py deleted file mode 100644 index 4287ca86..00000000 --- a/imagepy/menus/Selection/Relation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# \ No newline at end of file diff --git a/imagepy/menus/Selection/Relation/relation_plg.py b/imagepy/menus/Selection/Relation/relation_plg.py deleted file mode 100644 index e3be5210..00000000 --- a/imagepy/menus/Selection/Relation/relation_plg.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 22 20:25:52 2016 - -@author: yxl -""" - -from imagepy.core.engine import Simple -from imagepy.core.manager import RoiManager -from imagepy import IPy - -class Union(Simple): - """Union: derived from imagepy.core.engine.Simple """ - title = 'Union' - note = ['all', 'req_roi'] - para = {'name':''} - - def load(self, ips): - titles = list(RoiManager.rois.keys()) - if len(titles)==0: - IPy.alert('No roi in manager!') - return False - self.para['name'] = titles[0] - self.view = [(list, 'name', titles, str, 'Name', '')] - return True - - def run(self, ips, imgs, para = None): - ips.roi = ips.roi.union(RoiManager.get(para['name'])) - -class Diff(Simple): - """Diff: derived from imagepy.core.engine.Simple """ - title = 'Difference' - note = ['all', 'req_roi'] - para = {'name':''} - - def load(self, ips): - titles = list(RoiManager.rois.keys()) - if len(titles)==0: - IPy.alert('No roi in manager!') - return False - self.para['name'] = titles[0] - self.view = [(list, 'name', titles, str, 'Name', '')] - return True - - def run(self, ips, imgs, para = None): - ips.roi = ips.roi.diff(RoiManager.get(para['name'])) - -plgs = [Union, Diff] \ No newline at end of file diff --git a/imagepy/menus/Selection/__init__.py b/imagepy/menus/Selection/__init__.py index f1854c3f..f0845f10 100644 --- a/imagepy/menus/Selection/__init__.py +++ b/imagepy/menus/Selection/__init__.py @@ -1 +1 @@ -catlog = ['select_plg', 'Relation', '-', 'setting_plg'] \ No newline at end of file +catlog = ['select_plg', 'roiwindow_wgt'] \ No newline at end of file diff --git a/imagepy/menus/Selection/roiwindow_wgt.py b/imagepy/menus/Selection/roiwindow_wgt.py new file mode 100644 index 00000000..778242e6 --- /dev/null +++ b/imagepy/menus/Selection/roiwindow_wgt.py @@ -0,0 +1,322 @@ +import wx +#from imagepy.core.manager import RoiManager, ImageManager +from sciapp.action import Macros +from sciapp.object import ROI, mark2shp +#from imagepy import IPy + +class VirtualListCtrl(wx.ListCtrl): + def __init__(self, parent, title, data=[]): + wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL|wx.LC_EDIT_LABELS) + self.title, self.data = title, data + #self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems) + for col, text in enumerate(title): + self.InsertColumn(col, text) + self.SetValue(data) + + def OnGetItemText(self, row, col): + return self.data[row][col] + + def OnGetItemAttr(self, item): return None + + def OnGetItemImage(self, item): return -1 + + def SetValue(self, data): + self.data = data + self.SetItemCount(len(data)) + + def Refresh(self): + self.SetItemCount(len(self.data)) + wx.ListCtrl.Refresh(self) + +class Plugin(wx.Panel): + title = 'ROI Ctrl Panel' + single = None + + def __init__( self, parent, app=None): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(-1,-1), style = wx.TAB_TRAVERSAL ) + self.app = app + sizer = wx.BoxSizer( wx.HORIZONTAL ) + + self.note_book = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_LEFT|wx.NB_TOP ) + #self.note_book = wx.Choicebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.CHB_DEFAULT ) + self.pan_manage = wx.Panel( self.note_book, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizer_manage = wx.BoxSizer( wx.VERTICAL ) + + self.btn_add = wx.Button( self.pan_manage, wx.ID_ANY, u"Add", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_add, 0, wx.LEFT|wx.RIGHT|wx.TOP, 5 ) + + self.btn_load = wx.Button( self.pan_manage, wx.ID_ANY, u"Load", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_load, 0, wx.ALL, 5 ) + + self.btn_update = wx.Button( self.pan_manage, wx.ID_ANY, u"Update", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_update, 0, wx.ALL, 5 ) + + self.btn_remove = wx.Button( self.pan_manage, wx.ID_ANY, u"Remove", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_remove, 0, wx.ALL, 5 ) + + self.btn_open = wx.Button( self.pan_manage, wx.ID_ANY, u"Open", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_open, 0, wx.ALL, 5 ) + + self.btn_save = wx.Button( self.pan_manage, wx.ID_ANY, u"Save", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_manage.Add( self.btn_save, 0, wx.ALL, 5 ) + + + self.pan_manage.SetSizer( sizer_manage ) + self.pan_manage.Layout() + sizer_manage.Fit( self.pan_manage ) + self.note_book.AddPage( self.pan_manage, u"Manage", True ) + self.pan_operate = wx.Panel( self.note_book, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizer_operate = wx.BoxSizer( wx.VERTICAL ) + + self.btn_inflate = wx.Button( self.pan_operate, wx.ID_ANY, u"Inflate", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_inflate, 0, wx.ALL, 5 ) + + self.btn_shrink = wx.Button( self.pan_operate, wx.ID_ANY, u"Shrink", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_shrink, 0, wx.ALL, 5 ) + + self.btn_convex = wx.Button( self.pan_operate, wx.ID_ANY, u"Convex", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_convex, 0, wx.ALL, 5 ) + + self.btn_bound = wx.Button( self.pan_operate, wx.ID_ANY, u"Bound", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_bound, 0, wx.ALL, 5 ) + + self.btn_clip = wx.Button( self.pan_operate, wx.ID_ANY, u"Clip", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_clip, 0, wx.ALL, 5 ) + + self.btn_invert = wx.Button( self.pan_operate, wx.ID_ANY, u"Invert", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_operate.Add( self.btn_invert, 0, wx.ALL, 5 ) + + + self.pan_operate.SetSizer( sizer_operate ) + self.pan_operate.Layout() + sizer_operate.Fit( self.pan_operate ) + self.note_book.AddPage( self.pan_operate, u"Operate", False ) + self.pan_relation = wx.Panel( self.note_book, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizer_relation = wx.BoxSizer( wx.VERTICAL ) + + self.btn_intersect = wx.Button( self.pan_relation, wx.ID_ANY, u"Intersect", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_relation.Add( self.btn_intersect, 0, wx.ALL, 5 ) + + self.btn_union = wx.Button( self.pan_relation, wx.ID_ANY, u"Union", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_relation.Add( self.btn_union, 0, wx.ALL, 5 ) + + self.btn_difference = wx.Button( self.pan_relation, wx.ID_ANY, u"Difference", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_relation.Add( self.btn_difference, 0, wx.ALL, 5 ) + + self.btn_symdiff = wx.Button( self.pan_relation, wx.ID_ANY, u"Sym Diff", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_relation.Add( self.btn_symdiff, 0, wx.ALL, 5 ) + + + self.pan_relation.SetSizer( sizer_relation ) + self.pan_relation.Layout() + sizer_relation.Fit( self.pan_relation ) + self.note_book.AddPage( self.pan_relation, u"Relationship", False ) + self.pan_draw = wx.Panel( self.note_book, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizer_draw = wx.BoxSizer( wx.VERTICAL ) + + self.btn_sketch = wx.Button( self.pan_draw, wx.ID_ANY, u"Sketch", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_draw.Add( self.btn_sketch, 0, wx.ALL, 5 ) + + self.btn_clear = wx.Button( self.pan_draw, wx.ID_ANY, u"Clear", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_draw.Add( self.btn_clear, 0, wx.ALL, 5 ) + + self.btn_clearout = wx.Button( self.pan_draw, wx.ID_ANY, u"Clear Out", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_draw.Add( self.btn_clearout, 0, wx.ALL, 5 ) + + sizer_draw.AddStretchSpacer(1) + self.btn_setting = wx.Button( self.pan_draw, wx.ID_ANY, u"Setting", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizer_draw.Add( self.btn_setting, 0, wx.ALL, 5 ) + + self.pan_draw.SetSizer( sizer_draw ) + self.pan_draw.Layout() + sizer_draw.Fit( self.pan_draw ) + self.note_book.AddPage( self.pan_draw, u"Draw", False ) + + sizer.Add( self.note_book, 0, wx.EXPAND |wx.ALL, 5 ) + + sizer_lst = wx.BoxSizer(wx.VERTICAL) + + self.lst_rois = VirtualListCtrl(self, ['name', 'type'], []) + self.lst_rois.SetColumnWidth(0, 100) + sizer_lst.Add( self.lst_rois, 1, wx.ALL|wx.EXPAND, 5 ) + self.UpdateData() + + self.info = wx.StaticText( self, wx.ID_ANY, 'Information', wx.DefaultPosition, wx.DefaultSize ) + sizer_lst.Add( self.info, 0, wx.ALL|wx.EXPAND, 5 ) + sizer.Add(sizer_lst, 1, wx.ALL|wx.EXPAND, 5 ) + self.SetSizer( sizer ) + self.Fit() + self.Layout() + self.AddEvent() + + def AddEvent(self): + self.btn_add.Bind(wx.EVT_BUTTON, self.on_add) + self.btn_add.Bind(wx.EVT_RIGHT_DOWN, self.on_add_nameless) + self.btn_add.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('right click to add nameless') ) + self.btn_load.Bind(wx.EVT_BUTTON, self.on_load) + self.btn_load.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('load selected roi to image') ) + self.btn_update.Bind(wx.EVT_BUTTON, self.on_update) + self.btn_update.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('update and refresh the list') ) + self.btn_remove.Bind(wx.EVT_BUTTON, self.on_remove) + self.btn_remove.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('remove selected roi from list') ) + self.btn_open.Bind(wx.EVT_BUTTON, self.on_open) + self.btn_open.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('open a roi file and load it') ) + self.btn_save.Bind(wx.EVT_BUTTON, self.on_save) + self.btn_save.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('save current image roi to file') ) + self.btn_inflate.Bind(wx.EVT_BUTTON, self.on_inflate) + self.btn_inflate.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('inflate the current roi') ) + self.btn_shrink.Bind(wx.EVT_BUTTON, self.on_shrink) + self.btn_shrink.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('shrink the current roi') ) + self.btn_convex.Bind(wx.EVT_BUTTON, self.on_convex) + self.btn_convex.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('make convex hull of current roi') ) + self.btn_bound.Bind(wx.EVT_BUTTON, self.on_box) + self.btn_bound.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('make bounding of current roi') ) + self.btn_clip.Bind(wx.EVT_BUTTON, self.on_clip) + self.btn_clip.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('clip the region out of image') ) + self.btn_invert.Bind(wx.EVT_BUTTON, self.on_invert) + self.btn_invert.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('select invert region on image') ) + self.btn_intersect.Bind(wx.EVT_BUTTON, self.on_intersect) + self.btn_intersect.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('intersect selected roi with current') ) + self.btn_union.Bind(wx.EVT_BUTTON, self.on_union) + self.btn_union.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('union selected roi with current') ) + self.btn_difference.Bind(wx.EVT_BUTTON, self.on_difference) + self.btn_difference.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('clip selected roi with current') ) + self.btn_symdiff.Bind(wx.EVT_BUTTON, self.on_symdiff) + self.btn_symdiff.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('symdiff of selected roi and current') ) + self.btn_sketch.Bind(wx.EVT_BUTTON, self.on_sketch) + self.btn_sketch.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('sketch current roi') ) + self.btn_clear.Bind(wx.EVT_BUTTON, self.on_clear) + self.btn_clear.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('clear pixels in current roi') ) + self.btn_clearout.Bind(wx.EVT_BUTTON, self.on_clearout) + self.btn_clearout.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('clear pixels out current roi') ) + self.btn_setting.Bind(wx.EVT_BUTTON, self.on_setting) + self.btn_setting.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('set roi color and line width') ) + self.lst_rois.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.on_load) + self.lst_rois.Bind( wx.EVT_ENTER_WINDOW, + lambda e: self.info.SetLabel('double click to load roi') ) + + self.lst_rois.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.on_begin_edit) + self.lst_rois.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.on_end_edit) + + def on_begin_edit(self, event): + self.begin = event.GetText() + + def on_end_edit(self, event): + end = event.GetText() + if end == self.begin or end == '': return + RoiManager.add(end, RoiManager.get(self.begin)) + RoiManager.remove(self.begin) + self.UpdateData() + + def on_add(self, event): + Macros('', ['ROI Add>None']).start(self.app, callafter=self.UpdateData) + + def on_add_nameless(self, event): + ips = ImageManager.get() + if ips is None: return self.app.alert('No image opened!') + if ips.roi is None: return self.app.alert('No Roi found!') + Macros('', ['ROI Add>{"name":"%s-roi"}'%ips.title]).start(self.app, callafter=self.UpdateData) + + def on_load(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Load>{"name":"%s"}'%name]).start(self.app) + + def on_remove(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Remove>{"name":"%s"}'%name]).start(self.app, callafter=self.UpdateData) + + def on_open(self, event): + Macros('', ['ROI Open>None']).start(self.app) + + def on_save(self, event): + Macros('', ['ROI Save>None']).start(self.app) + + def on_inflate(self, event): + Macros('', ['ROI Inflate>None']).start(self.app) + + def on_shrink(self, event): + Macros('', ['ROI Shrink>None']).start(self.app) + + def on_convex(self, event): + Macros('', ['ROI Convex Hull>None']).start(self.app) + + def on_box(self, event): + Macros('', ['ROI Bound Box>None']).start(self.app) + + def on_clip(self, event): + Macros('', ['ROI Clip>None']).start(self.app) + + def on_invert(self, event): + Macros('', ['ROI Invert>None']).start(self.app) + + def on_intersect(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Intersect>{"name":"%s"}'%name]).start(self.app) + + def on_union(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Union>{"name":"%s"}'%name]).start(self.app) + + def on_difference(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Difference>{"name":"%s"}'%name]).start(self.app) + + def on_symdiff(self, event): + idx = self.lst_rois.GetFirstSelected() + if idx==-1: return self.app.alert('No ROI Selected!') + name = self.lst_rois.OnGetItemText(idx, 0) + Macros('', ['ROI Symmetric Diff>{"name":"%s"}'%name]).start(self.app) + + def on_clear(self, event): + Macros('', ['Clear>None']).start(self.app) + + def on_clearout(self, event): + Macros('', ['Clear Out>None']).start(self.app) + + def on_sketch(self, event): + Macros('', ['Sketch>None']).start(self.app) + + def on_update(self, event): + self.UpdateData() + + def on_setting(self, event): + Macros('', ['ROI Setting>None']).start(self.app) + + def UpdateData(self): + names = self.app.manager('roi').gets('name') + objs = self.app.manager('roi').gets('obj') + types = [ROI(mark2shp(i)).roitype for i in objs] + self.lst_rois.SetValue(list(zip(names, types))) + + def __del__( self ): + pass \ No newline at end of file diff --git a/imagepy/menus/Selection/select_plg.py b/imagepy/menus/Selection/select_plg.py index 24d71268..453befcb 100644 --- a/imagepy/menus/Selection/select_plg.py +++ b/imagepy/menus/Selection/select_plg.py @@ -1,144 +1,239 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Dec 18 22:31:12 2016 - -@author: yxl -""" -from imagepy.core.manager import RoiManager -from imagepy.core.engine import Simple -from imagepy.core.roi import RectangleRoi -from imagepy.core.roi import roiio -from imagepy import IPy +from sciapp.action import Simple, Free +from sciapp.object import ROI, Rectangle +from sciapp.util.shputil import geom2shp, geom_flatten, geom_union, mark2shp +from imagepy.app import ConfigManager +import json, time class SelectAll(Simple): - """SelectAll: derived from imagepy.core.engine.Simple """ title = 'Select All' note = ['all'] def run(self, ips, imgs, para = None): - ips.roi = RectangleRoi(0,0,ips.size[1],ips.size[0]) + ips.roi = ROI(Rectangle([0, 0, ips.shape[1], ips.shape[0]])) class SelectNone(Simple): - """SelectNone: derived from imagepy.core.engine.Simple """ title = 'Select None' note = ['all'] def run(self, ips, imgs, para = None): ips.roi = None + time.sleep(0.1) class Add2Manager(Simple): - """Add2Manager: derived from imagepy.core.engine.Simple """ - title = 'Add To Manager' + title = 'ROI Add' note = ['all', 'req_roi'] para = {'name':''} view = [(str, 'name', 'Name', '')] def run(self, ips, imgs, para = None): - RoiManager.add(para['name'], ips.roi) + self.app.manager('roi').add(obj=ips.roi.to_mark(), name=para['name']) +class RemoveFManager(Simple): + title = 'ROI Remove' + note = ['all'] + para = {'name':''} + + def load(self, ips): + titles = self.app.manager('roi').names() + if len(titles)==0: + self.app.alert('No roi in manager!') + return False + self.para['name'] = titles[0] + RemoveFManager.view = [(list, 'name', titles, str, 'Name', '')] + return True + + def run(self, ips, imgs, para = None): + self.app.manager('roi').remove(name=para['name']) + class LoadRoi(Simple): - """LoadRoi: derived from imagepy.core.engine.Simple """ - title = 'Load Roi' + title = 'ROI Load' note = ['all'] para = {'name':''} def load(self, ips): - titles = list(RoiManager.rois.keys()) + titles = self.app.manager('roi').names() if len(titles)==0: - IPy.alert('No roi in manager!') + self.app.alert('No roi in manager!') return False self.para['name'] = titles[0] LoadRoi.view = [(list, 'name', titles, str, 'Name', '')] return True def run(self, ips, imgs, para = None): - ips.roi = RoiManager.get(para['name']) + ips.roi = mark2shp(self.app.manager('roi').get(name=para['name'])) class Inflate(Simple): - """Inflate: derived from imagepy.core.engine.Simple """ - title = 'Inflate' + title = 'ROI Inflate' note = ['all', 'req_roi'] para = {'r':5} view = [(int, 'r', (1,100),0, 'radius', 'pix')] def run(self, ips, imgs, para = None): - ips.roi = ips.roi.buffer(para['r']) + geom = ips.roi.to_geom().buffer(para['r']) + ips.roi = ROI(geom2shp(geom_flatten(geom))) class Shrink(Simple): - """Shrink: derived from imagepy.core.engine.Simple """ - title = 'Shrink' + title = 'ROI Shrink' note = ['all', 'req_roi'] para = {'r':5} view = [(int, 'r', (1,100),0, 'radius', 'pix')] def run(self, ips, imgs, para = None): - ips.roi = ips.roi.buffer(-para['r']) + geom = ips.roi.to_geom().buffer(-para['r']) + ips.roi = ROI(geom2shp(geom_flatten(geom))) class Convex(Simple): - """Convex: derived from imagepy.core.engine.Simple """ - title = 'Convex Hull' + title = 'ROI Convex Hull' note = ['all', 'req_roi'] def run(self, ips, imgs, para = None): - ips.roi = ips.roi.convex() + geom = ips.roi.to_geom().convex_hull + ips.roi = ROI(geom2shp(geom_flatten(geom))) class Box(Simple): - """Box: derived from imagepy.core.engine.Simple """ - title = 'Bound Box' + title = 'ROI Bound Box' note = ['all', 'req_roi'] def run(self, ips, imgs, para = None): - ips.roi = ips.roi.bounds() + a,b,c,d = ips.roi.to_geom().bounds + ips.roi = ROI(Rectangle([a,b,c-a,d-b])) class Clip(Simple): - """Clip: derived from imagepy.core.engine.Simple """ - title = 'Clip Roi' + title = 'ROI Clip' note = ['all', 'req_roi'] def run(self, ips, imgs, para = None): - rect = RectangleRoi(0,0,ips.size[1],ips.size[0]) - ips.roi = ips.roi.clip(rect) + rect = Rectangle([0, 0, ips.shape[1], ips.shape[0]]) + geom = rect.to_geom().intersection(geom_flatten(ips.roi.to_geom())) + ips.roi = ROI(geom2shp(geom_flatten(geom))) class Invert(Simple): - """Invert: derived from imagepy.core.engine.Simple """ - title = 'Invert Roi' + title = 'ROI Invert' note = ['all', 'req_roi'] def run(self, ips, imgs, para = None): - rect = RectangleRoi(0,0,ips.size[1],ips.size[0]) - ips.roi = ips.roi.invert(rect) + rect = Rectangle([0, 0, ips.shape[1], ips.shape[0]]) + geom = rect.to_geom().difference(geom_flatten(ips.roi.to_geom())) + ips.roi = ROI(geom2shp(geom_flatten(geom))) class Save(Simple): - """Save: save roi as a wkt file """ - title = 'Save ROI' + title = 'ROI Save' note = ['all', 'req_roi'] para={'path':''} def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in ['roi', 'wkt']]) - return IPy.getpath('Save..', filt, 'save', self.para) + self.para['path'] = self.app.get_path('ROI Save', ['roi'], 'save') + return not self.para['path'] is None def run(self, ips, imgs, para = None): - file = para['path'] - if file[-3:] == 'wkt':roiio.savewkt(ips.roi, file) - if file[-3:] == 'roi':roiio.saveroi(ips.roi, file) + with open(para['path'], 'w') as f: + f.write(json.dumps(ips.roi.to_mark())) class Open(Simple): - """Save: save roi as a wkt file """ - title = 'Open ROI' + title = 'ROI Open' note = ['all'] para={'path':''} def show(self): - filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in ['roi', 'wkt']]) - return IPy.getpath('Save..', filt, 'open', self.para) + self.para['path'] = self.app.get_path('ROI Save', ['roi'], 'open') + return not self.para['path'] is None def run(self, ips, imgs, para = None): - file = para['path'] + with open(para['path']) as f: + ips.roi = ROI(mark2shp(json.loads(f.read()))) + +class Intersect(Simple): + title = 'ROI Intersect' + note = ['all', 'req_roi'] + para = {'name':''} + + def load(self, ips): + titles = self.app.manager('roi').names() + if len(titles)==0: + self.app.alert('No roi in manager!') + return False + self.para['name'] = titles[0] + self.view = [(list, 'name', titles, str, 'Name', '')] + return True + + def run(self, ips, imgs, para = None): + obj = mark2shp(self.app.manager('roi').get(name=para['name'])).to_geom() + roi = geom_flatten(ips.roi.to_geom()) + ips.roi = ROI(geom2shp(geom_flatten(roi.intersection(obj)))) - if file[-3:] == 'wkt':ips.roi = roiio.readwkt(file) - if file[-3:] == 'roi':ips.roi = roiio.readroi(file) +class Union(Simple): + title = 'ROI Union' + note = ['all', 'req_roi'] + para = {'name':''} + + def load(self, ips): + titles = self.app.manager('roi').names() + if len(titles)==0: + self.app.alert('No roi in manager!') + return False + self.para['name'] = titles[0] + self.view = [(list, 'name', titles, str, 'Name', '')] + return True + + def run(self, ips, imgs, para = None): + obj = mark2shp(self.app.manager('roi').get(name=para['name'])).to_geom() + roi = geom_flatten(ips.roi.to_geom()) + ips.roi = ROI(geom2shp(geom_flatten(roi.union(obj)))) + +class Diff(Simple): + title = 'ROI Difference' + note = ['all', 'req_roi'] + para = {'name':''} + + def load(self, ips): + titles = self.app.manager('roi').names() + if len(titles)==0: + self.app.alert('No roi in manager!') + return False + self.para['name'] = titles[0] + self.view = [(list, 'name', titles, str, 'Name', '')] + return True + + def run(self, ips, imgs, para = None): + print(self.app.manager('roi').get(name=para['name'])) + obj = mark2shp(self.app.manager('roi').get(name=para['name'])).to_geom() + roi = geom_flatten(ips.roi.to_geom()) + ips.roi = ROI(geom2shp(geom_flatten(roi.difference(obj)))) + +class SymDiff(Simple): + title = 'ROI Symmetric Diff' + note = ['all', 'req_roi'] + para = {'name':''} + + def load(self, ips): + titles = self.app.manager('roi').names() + if len(titles)==0: + self.app.alert('No roi in manager!') + return False + self.para['name'] = titles[0] + self.view = [(list, 'name', titles, str, 'Name', '')] + return True + + def run(self, ips, imgs, para = None): + obj = mark2shp(self.app.manager('roi').get(name=para['name'])).to_geom() + roi = geom_flatten(ips.roi.to_geom()) + ips.roi = ROI(geom2shp(geom_flatten(roi.symmetric_difference(obj)))) + +class Setting(Free): + title = 'ROI Setting' + para = ROI.default.copy() + view = [('color', 'color', 'line', 'color'), + ('color', 'fcolor', 'face', 'color'), + ('color', 'tcolor', 'text', 'color'), + (int, 'lw', (1,10), 0, 'width', 'pix'), + (int, 'size', (1,30), 0, 'text', 'size'), + (bool, 'fill', 'solid fill')] + + def run(self, para=None): + for i in para: ROI.default[i] = para[i] + ConfigManager.set('roi_style', para) plgs = [SelectAll, SelectNone, '-', Inflate, Shrink, Convex, Box, Clip, Invert, - '-', Open, Save, Add2Manager, LoadRoi] \ No newline at end of file + '-', Open, Save, Add2Manager, LoadRoi, RemoveFManager, + '-', Intersect, Union, Diff, SymDiff, '-', Setting] \ No newline at end of file diff --git a/imagepy/menus/Selection/setting_plg.py b/imagepy/menus/Selection/setting_plg.py deleted file mode 100644 index 03449689..00000000 --- a/imagepy/menus/Selection/setting_plg.py +++ /dev/null @@ -1,16 +0,0 @@ -from imagepy.core.manager import RoiManager -from imagepy.core.engine import Free - -class Plugin(Free): - title = 'Roi Setting' - - def load(self): - Plugin.para = {'color':RoiManager.get_color(), - 'lw':RoiManager.get_lw()} - Plugin.view = [('color', 'color', 'roi', 'color'), - (int, 'lw', (1,5), 0, 'line width', 'pix')] - return True - - def run(self, para=None): - RoiManager.set_color(para['color']) - RoiManager.set_lw(para['lw']) \ No newline at end of file diff --git a/imagepy/menus/Table/Basic Operator/basic_plgs.py b/imagepy/menus/Table/Basic Operator/basic_plgs.py index 60d89cea..d6588c84 100644 --- a/imagepy/menus/Table/Basic Operator/basic_plgs.py +++ b/imagepy/menus/Table/Basic Operator/basic_plgs.py @@ -1,17 +1,16 @@ -from imagepy.core.engine import Table -from imagepy import IPy +from sciapp.action import Table class Transpose(Table): title = 'Table Transpose' - def run(self, tps, data, snap, para = None): - tps.set_data(data.T) + def run(self, tps, snap, data, para = None): + tps.data = data.T -class Corp(Table): - title = 'Table Corp' +class Crop(Table): + title = 'Table Crop' note = ['req_sel'] - def run(self, tps, data, snap, para): - tps.set_data(data.loc[tps.rowmsk, tps.colmsk]) + def run(self, tps, snap, data, para): + tps.data = tps.subtab() class Duplicate(Table): title = 'Table Duplicate' @@ -22,22 +21,21 @@ def load(self, tps): self.view = [(str, 'name', 'Name', '')] return True - def run(self, tps, data, snap, para = None): - newdata = data.loc[tps.rowmsk, tps.colmsk] - IPy.show_table(para['name'], newdata) + def run(self, tps, snap, data, para = None): + self.app.show_table(tps.subtab(), para['name']) class DeleteRow(Table): title = 'Delete Rows' note = ['row_sel'] - def run(self, tps, data, snap, para = None): + def run(self, tps, snap, data, para = None): data.drop(tps.rowmsk, inplace=True) class DeleteCol(Table): title = 'Delete Columns' note = ['col_sel'] - def run(self, tps, data, snap, para = None): + def run(self, tps, snap, data, para = None): data.drop(tps.colmsk, axis=1, inplace=True) class AppendRow(Table): @@ -46,10 +44,9 @@ class AppendRow(Table): view = [(int, 'count', (1,100), 0, 'count', ''), (bool, 'fill', 'fill by last row')] - def run(self, tps, data, snap, para = None): - newdata = data.reindex(index=range(data.shape[0]+para['count']), \ + def run(self, tps, snap, data, para = None): + tps.data = data.reindex(index=range(data.shape[0]+para['count']), \ method=[None,'pad'][para['fill']]) - tps.set_data(newdata) class AddCol(Table): title = 'Add Column' @@ -57,9 +54,7 @@ class AddCol(Table): view = [(str, 'name', 'name', ''), ('any', 'value', 'value')] - def run(self, tps, data, snap, para = None): - ctype = data.columns.dtype.type - data[ctype(para['name'])] = para['value'] - print(data.info()) + def run(self, tps, snap, data, para = None): + data[para['name']] = para['value'] -plgs = [Transpose, Duplicate, Corp, '-', DeleteRow, DeleteCol, AppendRow, AddCol] \ No newline at end of file +plgs = [Transpose, Duplicate, Crop, '-', DeleteRow, DeleteCol, AppendRow, AddCol] \ No newline at end of file diff --git a/imagepy/menus/Table/Chart/__init__.py b/imagepy/menus/Table/Chart/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/menus/Table/Chart/plot_plgs.py b/imagepy/menus/Table/Chart/plot_plgs.py new file mode 100644 index 00000000..d6d72066 --- /dev/null +++ b/imagepy/menus/Table/Chart/plot_plgs.py @@ -0,0 +1,135 @@ +from sciapp.action import Table +import matplotlib.pyplot as plt +#from imagepy.core.manager import ColorManager +from matplotlib import colors +from imagepy.app import ColorManager + +class Plot(Table): + title = 'Plot Chart' + para = {'cn':[], 'lw':1, 'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields'), + (int, 'lw', (1,5), 0, 'line width', ''), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + data[para['cn']].plot(lw=para['lw'], grid=para['grid'], + title=para['title'], ax=plt.add_subplot()) + plt.Show() + +class Bar(Table): + title = 'Bar Chart' + para = {'cn':[], 'dir':False, 'stack':False,'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields'), + (bool, 'dir', 'horizon'), + (bool, 'stack', 'stacked'), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + if para['dir']: + data[para['cn']].plot.barh(stacked=para['stack'], grid=para['grid'], + title=para['title'], ax=plt.add_subplot()) + else: data[para['cn']].plot.bar(stacked=para['stack'], grid=para['grid'], + title=para['title'], ax=plt.add_subplot()) + plt.Show() + +class Hist(Table): + title = 'Hist Chart' + para = {'cn':[], 'dir':'vertical', 'stack':False, 'bins':10, 'alpha':1.0, + 'overlay':False, 'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields'), + (int, 'bins', (3,1000), 0, 'bins', ''), + (float, 'alpha', (0,1), 1, 'alpha', '0~1'), + (list, 'dir', ['horizontal', 'vertical'], str, 'orientation', ''), + (bool, 'stack', 'stacked'), + (bool, 'overlay', 'draw every columns in one'), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + if para['overlay']: + data[para['cn']].plot.hist(stacked=para['stack'], bins=para['bins'], alpha=para['alpha'], + orientation=para['dir'], grid=para['grid'], title=para['title'], ax=plt.add_subplot()) + else: + data[para['cn']].hist(stacked=para['stack'], bins=para['bins'], alpha=para['alpha'], + orientation=para['dir'], grid=para['grid'], ax=plt.add_subplot()) + plt.Show() + +class Box(Table): + title = 'Box Chart' + para = {'cn':[], 'hor':False, 'by':None, 'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields'), + ('field', 'by', 'by', 'group'), + (bool, 'hor', 'horizontal'), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + data[para['cn']].plot.box(by=None, vert=~para['hor'], grid=para['grid'], + title=para['title'], ax=plt.add_subplot()) + plt.Show() + +class Area(Table): + title = 'Area Chart' + para = {'cn':[], 'dir':'vertical', 'stack':False, 'bins':10, 'alpha':1.0, 'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields'), + (float, 'alpha', (0,1), 1, 'alpha', '0~1'), + (bool, 'stack', 'stacked'), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + data[para['cn']].plot.area(stacked=para['stack'], alpha=para['alpha'], + grid=para['grid'], title=para['title'], ax=plt.add_subplot()) + plt.Show() + +class Scatter(Table): + title = 'Scatter Chart' + para = {'x':None, 'y':None, 's':1, 'alpha':1.0, 'rs':None, 'c':(0,0,255), + 'cs':None, 'cm':None, 'grid':False, 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('field', 'x', 'x data', ''), + ('field', 'y', 'y data', ''), + (int, 's', (0, 1024), 0, 'size', 'pix'), + ('field', 'rs', 'radius column', 'optional'), + (float, 'alpha', (0,1), 1, 'alpha', '0~1'), + ('color', 'c', 'color', ''), + ('field', 'cs', 'color column', 'optional'), + ('cmap', 'cm', 'color map'), + (bool, 'grid', 'grid')] + + def run(self, tps, snap, data, para = None): + rs = data[para['rs']] * para['s'] if para['rs'] != 'None' else para['s'] + cs = data[para['cs']] if para['cs'] != 'None' else '#%.2x%.2x%.2x'%para['c'] + cm = ColorManager.get(para['cm'])/255.0 + cm = None if para['cs'] == 'None' else colors.ListedColormap(cm, N=256) + plt = self.app.show_plot(para['title']) + data.plot.scatter(x=para['x'], y=para['y'], s=rs, c=cs, alpha=para['alpha'], + cmap=cm, grid=para['grid'], title=para['title'], ax=plt.add_subplot()) + plt.Show() + +class Pie(Table): + title = 'Pie Chart' + para = {'cn':[], 'title':''} + asyn = False + view = [(str, 'title', 'title', ''), + ('fields', 'cn', 'select fields')] + + def run(self, tps, snap, data, para = None): + plt = self.app.show_plot(para['title']) + data[para['cn']].plot.pie(subplots=True, title=para['title'], ax=plt.add_subplot()) + plt.Show() + +plgs = [Plot, Area, Bar, Box, Hist, Pie, Scatter] \ No newline at end of file diff --git a/imagepy/menus/Table/Selection/select_plgs.py b/imagepy/menus/Table/Selection/select_plgs.py index d9899dde..5cd6c1a8 100644 --- a/imagepy/menus/Table/Selection/select_plgs.py +++ b/imagepy/menus/Table/Selection/select_plgs.py @@ -1,4 +1,4 @@ -from imagepy.core.engine import Table +from sciapp.action import Table import pandas as pd import numpy as np @@ -12,7 +12,7 @@ class Select(Table): ('any', 'r2', 'end rows'), (bool, 'rall', 'all rows')] - def run(self, tps, data, snap, para=None): + def run(self, tps, snap, data, para=None): if not para['call']: cmsk = para['cn'] else: cmsk=[] diff --git a/imagepy/menus/Table/Signal/signal_plgs.py b/imagepy/menus/Table/Signal/signal_plgs.py index 52b4a58c..344a97ef 100644 --- a/imagepy/menus/Table/Signal/signal_plgs.py +++ b/imagepy/menus/Table/Signal/signal_plgs.py @@ -1,4 +1,4 @@ -from imagepy.core.engine import Table +from sciapp.action import Table import pandas as pd import numpy as np from scipy import signal @@ -6,13 +6,13 @@ class Statistic(Table): title = 'Signal Uniform Filter' - note = ['snap', 'only_num', 'col_msk', 'preview'] + note = ['auto_snap', 'only_num', 'auto_msk', 'preview'] para = {'size':2} view = [(int, 'size', (0,30), 0, 'size', '')] - def run(self, tps, data, snap, para=None): + def run(self, tps, snap, data, para=None): for s in snap.columns: data[s] = nimg.uniform_filter(snap[s], para['size']) diff --git a/imagepy/menus/Table/Statistic/__init__.py b/imagepy/menus/Table/Statistic/__init__.py index 24715d13..a58cdeb2 100644 --- a/imagepy/menus/Table/Statistic/__init__.py +++ b/imagepy/menus/Table/Statistic/__init__.py @@ -1,17 +1 @@ -from imagepy.core.engine import Table -from imagepy import IPy - -class Transpose(Table): - title = 'Table Statistic' - para = {'sum':True, 'mean':True, 'max':False, 'min':False, 'std':False, 'var':False, 'only':True} - para = [(bool, 'only', 'only number columns'), - ('lab', None, '========= indecate ========='), - (bool, 'sum', 'sum'), - (bool, 'mean', 'mean'), - (bool, 'max', 'max'), - (bool, 'min', 'min'), - (bool, 'std', 'std'), - (bool, 'var', 'var')] - - def run(self, tps, data, para = None): - pass \ No newline at end of file +catlog = ['statistic_plgs', '-', 'frequency_plgs', '-', 'sort_plg'] \ No newline at end of file diff --git a/imagepy/menus/Table/Statistic/frequency_plgs.py b/imagepy/menus/Table/Statistic/frequency_plgs.py new file mode 100644 index 00000000..ff7615e8 --- /dev/null +++ b/imagepy/menus/Table/Statistic/frequency_plgs.py @@ -0,0 +1,41 @@ +from sciapp.action import Table +import pandas as pd +import numpy as np + +class Count(Table): + title = 'Values Frequency' + para = {'cn':[]} + view = [('fields', 'cn', 'fields to count')] + + def run(self, tps, snap, data, para=None): + for i in para['cn']: + df = pd.DataFrame(data[i].value_counts()) + df.columns = ['%s-vf'%i] + self.app.show_table(df, '%s-values-frequency'%i) + +class Frequency(Table): + title = 'Table Bins Frequency' + + para = {'bins':10, 'max':1, 'min':0, 'auto':True, 'count':True, 'fre':False, 'weight':False, 'cn':[]} + + view = [(int, 'bins', (3,1024), 0, 'bins', 'n'), + (bool, 'auto', 'auto scale'), + (int, 'min', (-1e8, 1e8), 5, 'min range', ''), + (int, 'max', (-1e8, 1e8), 5, 'max range', ''), + ('fields', 'cn', 'fields to count'), + (bool, 'count', 'count times'), + (bool, 'fre', 'count frequency'), + (bool, 'weight', 'count weight')] + + def run(self, tps, snap, data, para=None): + rg = None if para['auto'] else (para['min'], para['max']) + for i in para['cn']: + hist, bins = np.histogram(data[i], para['bins'], rg) + vs = {'bins':bins[:-1]} + if para['count']:vs['count'] = hist + if para['fre']:vs['frequency'] = hist/hist.sum() + if para['weight']:vs['weight'] = np.histogram(data[i], bins, rg, weights=data[i])[0] + df = pd.DataFrame(vs, columns = [i for i in ['bins', 'count', 'frequency', 'weight'] if i in vs]) + self.app.show_table(df, '%s-frequency'%i) + +plgs = [Count, Frequency] \ No newline at end of file diff --git a/imagepy/menus/Table/Statistic/sort_plg.py b/imagepy/menus/Table/Statistic/sort_plg.py new file mode 100644 index 00000000..b1fa7c36 --- /dev/null +++ b/imagepy/menus/Table/Statistic/sort_plg.py @@ -0,0 +1,16 @@ +from sciapp.action import Table +import pandas as pd + +class Plugin(Table): + title = 'Table Sort By Key' + + para = {'major':None, 'minor':None, 'descend':False} + + view = [('field', 'major', 'major', 'key'), + ('field', 'minor', 'minor', 'key'), + (bool, 'descend', 'descend')] + + def run(self, tps, snap, data, para=None): + by = [para['major'], para['minor']] + tps.data.sort_values(by=[i for i in by if i != 'None'], + axis=0, ascending=not para['descend'], inplace=True) \ No newline at end of file diff --git a/imagepy/menus/Table/Statistic/statistic_plgs.py b/imagepy/menus/Table/Statistic/statistic_plgs.py index 7d0a2d8e..b0168570 100644 --- a/imagepy/menus/Table/Statistic/statistic_plgs.py +++ b/imagepy/menus/Table/Statistic/statistic_plgs.py @@ -1,10 +1,9 @@ -from imagepy.core.engine import Table +from sciapp.action import Table import pandas as pd -from imagepy import IPy class Statistic(Table): title = 'Table Statistic' - note = ['snap', 'only_num', 'row_msk', 'col_msk'] + note = ['auto_snap', 'num_only', 'auto_msk'] para = {'axis':'Column', 'sum':True, 'mean':True,'max':False, 'min':False,'var':False,'std':False,'skew':False,'kurt':False} @@ -19,8 +18,9 @@ class Statistic(Table): (bool, 'skew', 'skew'), (bool, 'kurt', 'kurt')] - def run(self, tps, data, snap, para=None): + def run(self, tps, snap, data, para=None): rst, axis = {}, (0,1)[para['axis']=='Row'] + print("snap = ", snap) if para['sum']:rst['sum'] = snap.sum(axis=axis) if para['mean']:rst['mean'] = snap.mean(axis=axis) if para['max']:rst['max'] = snap.max(axis=axis) @@ -29,6 +29,45 @@ def run(self, tps, data, snap, para=None): if para['std']:rst['std'] = snap.std(axis=axis) if para['skew']:rst['skew'] = snap.skew(axis=axis) if para['kurt']:rst['kurt'] = snap.kurt(axis=axis) - IPy.show_table(pd.DataFrame(rst), tps.title+'-statistic') + cols = ['sum', 'mean', 'min', 'max', 'var', 'std', 'skew', 'kurt'] + cols = [i for i in cols if i in rst] + self.app.show_table(pd.DataFrame(rst, columns=cols).T, tps.title+'-statistic') -plgs = [Statistic] \ No newline at end of file +class GroupStatistic(Table): + title = 'Group Statistic' + + para = {'major':None, 'minor':None, 'sum':True, 'mean':True,'max':False, + 'min':False,'var':False,'std':False,'skew':False,'kurt':False, 'cn':[]} + + view = [('fields', 'cn', 'field to statistic'), + ('field', 'major', 'group by', 'major'), + ('field', 'minor', 'group by', 'minor'), + + (bool, 'sum', 'sum'), + (bool, 'mean', 'mean'), + (bool, 'max', 'max'), + (bool, 'min', 'min'), + (bool, 'var', 'var'), + (bool, 'std', 'std'), + (bool, 'skew', 'skew')] + + def run(self, tps, snap, data, para=None): + by = [i for i in [para['major'], para['minor']] if i!='None'] + gp = data.groupby(by)[para['cn']] + + rst = [] + def post(a, fix): + a.columns = ['%s-%s'%(i,fix) for i in a.columns] + return a + + if para['sum']:rst.append(post(gp.sum(), 'sum')) + if para['mean']:rst.append(post(gp.mean(), 'mean')) + if para['max']:rst.append(post(gp.max(), 'max')) + if para['min']:rst.append(post(gp.min(), 'min')) + if para['var']:rst.append(post(gp.var(), 'var')) + if para['std']:rst.append(post(gp.std(), 'std')) + if para['skew']:rst.append(post(gp.skew(), 'skew')) + + self.app.show_table(pd.concat(rst, axis=1), tps.title+'-statistic') + +plgs = [Statistic, GroupStatistic] \ No newline at end of file diff --git a/imagepy/menus/Table/Table IO/tableio_plgs.py b/imagepy/menus/Table/Table IO/tableio_plgs.py index 71759b10..5267b714 100644 --- a/imagepy/menus/Table/Table IO/tableio_plgs.py +++ b/imagepy/menus/Table/Table IO/tableio_plgs.py @@ -1,34 +1,39 @@ -from imagepy.core.util import tableio +from sciapp.action import dataio from pandas import read_csv, read_excel, read_hdf -from imagepy import IPy -from imagepy.core.manager import ReaderManager, WriterManager, ViewerManager def show(data, title): IPy.show_table(data, title) -ViewerManager.add('tab', show) + +# ViewerManager.add('tab', show) save_csv = lambda path, data:data.to_csv(path) -ReaderManager.add('csv', read_csv, tag='tab') -WriterManager.add('csv', save_csv, tag='tab') +dataio.ReaderManager.add('csv', read_csv, 'tab') +dataio.WriterManager.add('csv', save_csv, 'tab') -class OpenCSV(tableio.Reader): +class OpenCSV(dataio.Reader): title = 'CSV Open' + tag = 'tab' filt = ['csv'] -class SaveCSV(tableio.Writer): +class SaveCSV(dataio.TableWriter): title = 'CSV Save' + tag = 'tab' filt = ['csv'] save_excel = lambda path, data:data.to_excel(path) -ReaderManager.add(['xls','xlsx'], read_excel, tag='tab') -WriterManager.add(['xls','xlsx'], save_excel, tag='tab') +dataio.ReaderManager.add('xls', read_excel, 'tab') +dataio.WriterManager.add('xls', save_excel, 'tab') +dataio.ReaderManager.add('xlsx', read_excel, 'tab') +dataio.WriterManager.add('xlsx', save_excel, 'tab') -class OpenExcel(tableio.Reader): +class OpenExcel(dataio.Reader): title = 'Excel Open' + tag = 'tab' filt = ['xls','xlsx'] -class SaveExcel(tableio.Writer): +class SaveExcel(dataio.TableWriter): title = 'Excel Save' + tag = 'tab' filt = ['xls', 'xlsx'] plgs = [OpenCSV, SaveCSV, '-', OpenExcel, SaveExcel] \ No newline at end of file diff --git a/imagepy/menus/Table/Universal Generator/gnerator_plgs.py b/imagepy/menus/Table/Universal Generator/gnerator_plgs.py index 32832aae..af6807b3 100644 --- a/imagepy/menus/Table/Universal Generator/gnerator_plgs.py +++ b/imagepy/menus/Table/Universal Generator/gnerator_plgs.py @@ -1,38 +1,7 @@ -from imagepy.core.engine import Free +from sciapp.action import Free import numpy as np import pandas as pd -from imagepy import IPy -''' -生成: - 随机数 - 正太随机 - 日历 - 单位矩阵 -基础: - 添加字段 - 删除字段 - 字段运算 - 赋值 - 截取 - 转置 -统计: - 平均数,最大值,最小值,方差 - 频率统计 - 按id汇总 -筛选: - : -图表: - 折线图 - 条形图 - 饼状图 -信号: - 高斯 - 差分 -关联: - 聚合 - 分组 -''' class One(Free): title = 'Unit Matrix' para = {'size':3} @@ -41,7 +10,7 @@ class One(Free): def run(self, para=None): data = np.eye(para['size']) dataframe = pd.DataFrame(data) - IPy.show_table(dataframe, 'Eye[%s,%s]'%data.shape) + self.app.show_table(dataframe, 'Eye[%s,%s]'%data.shape) class Random01(Free): title = 'Uniform Random' @@ -56,7 +25,7 @@ def run(self, para=None): data *= para['high']-para['low'] data -= para['low'] dataframe = pd.DataFrame(data) - IPy.show_table(dataframe, 'Random01[%s,%s]'%data.shape) + self.app.show_table(dataframe, 'Random01[%s,%s]'%data.shape) class RandomN(Free): title = 'Gaussian Random' @@ -71,7 +40,7 @@ def run(self, para=None): data *= para['std'] data += para['mean'] dataframe = pd.DataFrame(data) - IPy.show_table(dataframe, 'RandomN[%s,%s]'%data.shape) + self.app.show_table(dataframe, 'RandomN[%s,%s]'%data.shape) class Calendar(Free): title = 'Calendar' @@ -89,6 +58,6 @@ def run(self, para=None): a = i.replace(' ', ' None ').strip() table.append(a.replace(' ', ' ').split(' ')) dataframe = pd.DataFrame(table, columns=titles) - IPy.show_table(dataframe, ls[0].strip()) + self.app.show_table(dataframe, ls[0].strip()) plgs = [One, Random01, RandomN, Calendar] \ No newline at end of file diff --git a/imagepy/menus/Window/Windows Style/style_plgs.py b/imagepy/menus/Window/Windows Style/style_plgs.py index 335b58b3..e1dd9005 100644 --- a/imagepy/menus/Window/Windows Style/style_plgs.py +++ b/imagepy/menus/Window/Windows Style/style_plgs.py @@ -1,19 +1,18 @@ -from imagepy.core.engine import Free -from imagepy.core.manager import ConfigManager -from imagepy import IPy +from sciapp.action import Free +from imagepy.app import ConfigManager class ImageJStyle(Free): title = 'Pay Tribute To ImageJ' asyn = False def run(self, para = None): - ConfigManager.set('uistyle', 'ij') - IPy.alert('Shown in ImageJ style when next setup!') + ConfigManager.add('uistyle', 'imagej') + self.app.alert('Shown in ImageJ style when next setup!') class ImagePyStyle(Free): title = 'Elegant ImagePy Style' asyn = False def run(self, para = None): - ConfigManager.set('uistyle', 'ipy') - IPy.alert('Shown in ImagePy style when next setup!') + ConfigManager.add('uistyle', 'imagepy') + self.app.alert('Shown in ImagePy style when next setup!') plgs = [ImageJStyle, ImagePyStyle] \ No newline at end of file diff --git a/imagepy/menus/Window/develop_wgts.py b/imagepy/menus/Window/develop_wgts.py index c3957043..6a45358b 100644 --- a/imagepy/menus/Window/develop_wgts.py +++ b/imagepy/menus/Window/develop_wgts.py @@ -1,5 +1,4 @@ -import wx -from imagepy.core.manager import WidgetsManager +import wx, wx.lib.agw.aui as aui from imagepy.menus.Plugins.Macros.recorder_wgt import Plugin as recorder from imagepy.menus.Plugins.Manager.console_wgt import Plugin as console from imagepy.menus.Plugins.Manager.plglist_wgt import Plugin as plglist @@ -9,19 +8,20 @@ class DevelopToolSute ( wx.Panel ): title = 'Develop Tool Sute' single = True - def __init__( self, parent ): + def __init__( self, parent, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 300,200), style = wx.TAB_TRAVERSAL ) sizer = wx.BoxSizer( wx.VERTICAL ) mrecorder = recorder(self) - self.notebook = wx.aui.AuiNotebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.aui.AUI_NB_DEFAULT_STYLE ) + self.notebook = aui.AuiNotebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, aui.AUI_NB_DEFAULT_STYLE ) self.notebook.AddPage( mrecorder, mrecorder.title, True, wx.NullBitmap ) - self.notebook.AddPage( console(self), console.title, False, wx.NullBitmap ) - self.notebook.AddPage( plglist(self), plglist.title, False, wx.NullBitmap ) - self.notebook.AddPage( plgtree(self), plgtree.title, False, wx.NullBitmap ) - self.notebook.AddPage( toltree(self), toltree.title, False, wx.NullBitmap ) - WidgetsManager.addref(mrecorder) + self.notebook.AddPage( console(self, app), console.title, False, wx.NullBitmap ) + self.notebook.AddPage( plglist(self, app), plglist.title, False, wx.NullBitmap ) + self.notebook.AddPage( plgtree(self, app), plgtree.title, False, wx.NullBitmap ) + self.notebook.AddPage( toltree(self, app), toltree.title, False, wx.NullBitmap ) + for i in range(5): self.notebook.GetPage(i).app = parent + app.manager('widget').add('Macros Recorder', mrecorder) sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) self.SetSizer( sizer ) self.Fit() diff --git a/imagepy/menus/Window/widgets_plgs.py b/imagepy/menus/Window/widgets_plgs.py index c535b083..4a05938a 100644 --- a/imagepy/menus/Window/widgets_plgs.py +++ b/imagepy/menus/Window/widgets_plgs.py @@ -1,42 +1,27 @@ -from imagepy.core.engine import Free -from imagepy import IPy -import wx +from sciapp.action import Free class Widgets(Free): - """ImageKiller: derived from imagepy.core.engine.Free""" + """ImageKiller: derived from sciapp.action.Free""" title = 'Widgets' asyn = False - #process def run(self, para = None): - app = IPy.curapp - info = app.auimgr.GetPane(app.widgets) - info.Show(not info.IsShown()) - app.auimgr.Update() + self.app.switch_widget() class ToolBar(Free): - """ImageKiller: derived from imagepy.core.engine.Free""" title = 'Toolbar' asyn = False - #process def run(self, para = None): - app = IPy.curapp - info = app.auimgr.GetPane(app.toolbar) - info.Show(not info.IsShown()) - app.auimgr.Update() + self.app.switch_toolbar() class TableWindow(Free): - """ImageKiller: derived from imagepy.core.engine.Free""" + """ImageKiller: derived from sciapp.action.Free""" title = 'Tables Window' asyn = False #process def run(self, para = None): - if IPy.uimode() != 'ipy': return - app = IPy.curapp - info = app.auimgr.GetPane(app.tablenb) - info.Show(not info.IsShown()) - app.auimgr.Update() + self.app.switch_table() plgs = [Widgets, ToolBar, TableWindow] \ No newline at end of file diff --git a/imagepy/menus/Window/windowskiller_plg.py b/imagepy/menus/Window/windowskiller_plg.py index 9764afa7..7b8824fc 100644 --- a/imagepy/menus/Window/windowskiller_plg.py +++ b/imagepy/menus/Window/windowskiller_plg.py @@ -4,60 +4,29 @@ @author: yxl """ -from imagepy.core.engine import Free -from imagepy.core.manager import ImageManager, TextLogManager, \ - TableManager, WindowsManager, WTableManager +from sciapp.action import Free +#from imagepy.core.manager import ImageManager, TextLogManager, \ +# TableManager, WindowsManager, WTableManager class ImageKiller(Free): - """ImageKiller: derived from imagepy.core.engine.Free""" title = 'Kill Image' - - def load(self): - ImageKiller.para = {'name':'All'} - titles =['All'] + ImageManager.get_titles() - ImageKiller.view = [(list, 'name', titles, str, 'Name', 'selected')] - return True - - #process - def run(self, para = None): - if para['name'] == 'All': - for i in ImageManager.get_titles(): - WindowsManager.get(i).close() - else: WindowsManager.get(para['name']).close() - -class TextKiller(Free): - """TextKiller: derived from imagepy.core.engine.Free""" - title = 'Kill TextLog' - - def load(self): - TextKiller.para = {'name':'All'} - titles =['All'] + TextLogManager.get_titles() - TextKiller.view = [(list, 'name', titles, str, 'Name', 'selected')] - return True + asyn = False + para = {'img':None, 'all':False} + view = [('img', 'img', 'name', ''), + (bool, 'all', 'close all images')] - #process def run(self, para = None): - if para['name'] == 'All': - for i in TextLogManager.get_titles(): - TextLogManager.close(i) - else: TextLogManager.close(para['name']) + self.app.close_img(None if para['all'] else para['img']) class TableKiller(Free): - """TableKiller: derived from imagepy.core.engine.Free""" - title = 'Kill TableLog' - - def load(self): - self.para = {'name':'All'} - titles = ['All'] + TableManager.get_titles() - self.view = [(list, 'name', titles, str, 'Name', 'selected')] - return True + title = 'Kill Table' + asyn = False + para = {'tab':None, 'all':False} + view = [('tab', 'tab', 'name', ''), + (bool, 'all', 'close all tables')] - #process def run(self, para = None): - if para['name'] == 'All': - for i in TableManager.get_titles(): - WTableManager.get(i).close() - else: WTableManager.get(para['name']).close() + self.app.close_table(None if para['all'] else para['tab']) #!TODO: plugins ?! -plgs = [ImageKiller, TextKiller, TableKiller] \ No newline at end of file +plgs = [ImageKiller, TableKiller] \ No newline at end of file diff --git a/imagepy/tools/Draw/Route.gif b/imagepy/tools/Draw/Route.gif new file mode 100644 index 00000000..e0f0ca2f Binary files /dev/null and b/imagepy/tools/Draw/Route.gif differ diff --git a/imagepy/tools/Draw/Route_tol.py b/imagepy/tools/Draw/Route_tol.py new file mode 100644 index 00000000..f2a06b19 --- /dev/null +++ b/imagepy/tools/Draw/Route_tol.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 19 17:35:09 2016 + +@author: yxl +""" +from sciapp.action import Filter +from sciapp.action import ImageTool +import numpy as np +from sciapp.util import mark2shp +from sciapp.action import ImageTool +#from imagepy.core.manager import ColorManager +#from imagepy.core.roi import lineroi +#from imagepy.core.mark import GeometryMark +from skimage.graph import route_through_array +import scipy.ndimage as ndimg + +def route_through(ips, snap, poins,para): + para['max']=True + para['lcost']=2 + img = snap.astype('float32') + if para['max']: img *= -1 + np.add(img, para['lcost']-img.min(), casting='unsafe', out=img) + minv, maxv = ips.range + routes = [] + for line in poins: + pts = np.array(list(zip(line[:-1], line[1:]))) + for p0, p1 in pts[:,:,::-1].astype(int): + indices, weight = route_through_array(img, p0, p1) + routes.append(indices) + return routes + +class Plugin(ImageTool): + title = 'Route Toolk' + note = ['auto_snap','8-bit', '16-bit','int', 'float','2int', 'preview'] + para = {'fully connected':True, 'lcost':0, 'max':False, 'geometric':True, 'type':'ROI'} + view = [(float, 'lcost', (0, 1e5), 3, 'step', 'cost'), + (bool, 'max', 'max cost'), + (bool, 'fully connected', 'fully connected'), + (bool, 'geometric', 'geometric'), + (list, 'type', ['white line', 'ROI'], str, 'output', '')] + + def __init__(self): + self.curobj = None + self.doing = 'Nothing' + #初始化线条缓存 + # self.helper = AnchorLine() + self.odx,self.ody = 0, 0 + self.cursor = 'cross' + self.buf=[] + + def mouse_down(self, ips, x, y, btn, **key): + # print(key['canvas'].scale) + lim = 5.0/key['canvas'].scale + if btn==2: + if key['ctrl']: + if len(self.buf)>1: + lines=route_through(ips, ips.img,[self.buf],self.para) + rs, cs = np.vstack(lines).T + minv, maxv = ips.range + if self.para['type']=='white line': + # ips.img[:,:] = minv + ips.img[rs,cs] = maxv + elif self.para['type']=='ROI': + ips.img[:,:] = minv + ips.img[rs,cs] = maxv + ndimg.binary_fill_holes(ips.img.copy(), output=ips.img) + ips.img[:,:] *= 255 + self.buf=[] + self.draw(ips) + return + else: + self.oldp = key['canvas'].to_panel_coor(x,y) + self.do_old=self.doing + self.doing = 'all_move' + return + #左键按下 + elif btn==1: + if self.doing=='Nothing': + if len(self.buf) == 0: + self.doing ='draw' + elif self.doing=='draw' and self.cursor=='hand': + a=self.in_points(x,y,lim) + if a!=(-1,-1): + # print('doing to move') + self.doing='move' + #记录第几个点被改变 + self.p_index=self.buf.index(a) + elif self.doing=='down': + #判断是否在点内,如果在,说明在移动 + a=self.in_points(x,y,lim) + if a!=(-1,-1): + self.doing='move' + self.p_index=self.buf.index(a) + return + self.buf=[] + self.doing='Nothing' + self.draw(ips) + return + if self.doing=='draw' and self.cursor=='cross': + self.addpoint((x,y)) + self.draw(ips) + elif btn == 3: + + if (self.doing=='draw' or self.doing=='down')and key['ctrl']: + a=self.in_points(x,y,lim) + if a!=(-1,-1):self.delete(self.buf.index(a)) + self.draw(ips) + elif self.doing=='draw': + self.addpoint((x,y)) + self.addpoint(self.buf[0]) + self.draw(ips) + self.doing='down' + + def mouse_up(self, ips, x, y, btn, **key): + if self.doing == 'move' and btn == 1: + self.doing = 'draw' + elif self.doing == 'all_move' and btn ==2: + self.doing = self.do_old + + def mouse_move(self, ips, x, y, btn, **key): + if len(self.buf)==0:return + lim = 5.0/key['canvas'].scale + #如果鼠标移动的同时没有按下 + if btn==1: + #如果在鼠标是手的时候左键按下,而且在移动,说明在修改点的位置 + if self.doing=='move': + self.change(self.p_index,(x,y)) + self.draw(ips) + elif btn==None: + #鼠标变成一个十字架 + self.cursor = 'cross' + a=self.in_points(x,y,lim) + if a!=(-1,-1): + self.cursor = 'hand' + if self.doing == 'all_move': + x,y = key['canvas'].to_panel_coor(x,y) + key['canvas'].move(x-self.oldp[0], y-self.oldp[1]) + self.oldp = x, y + ips.update() + # print('move') + self.odx, self.ody = x, y + + def in_points(self,x,y,range): + for i in self.buf: + if ((i[0]>x-range) and (i[0]y-range) and (i[1]1: + lines=route_through(ips, ips.img,[self.buf],self.para) + lst=[] + for line in lines:lst.append([(j,i) for i,j in line]) + layer['body'].append({'type':'lines', 'body':lst}) + mark['body'][0] = layer + ips.mark = mark2shp(mark) + ips.update() + + def mouse_wheel(self, ips, x, y, d, **key): + if d>0:key['canvas'].zoomout(x, y, 'data') + if d<0:key['canvas'].zoomin(x, y, 'data') + ips.update() \ No newline at end of file diff --git a/imagepy/tools/Draw/__init__.py b/imagepy/tools/Draw/__init__.py index e69de29b..9a27abf7 100644 --- a/imagepy/tools/Draw/__init__.py +++ b/imagepy/tools/Draw/__init__.py @@ -0,0 +1 @@ +catlog = ['floodfill_tol', 'flood3d_tol', 'aibrush_tol','Route_tol'] diff --git a/imagepy/tools/Draw/aibrush.gif b/imagepy/tools/Draw/aibrush.gif new file mode 100644 index 00000000..761025c1 Binary files /dev/null and b/imagepy/tools/Draw/aibrush.gif differ diff --git a/imagepy/tools/Draw/aibrush_tol.py b/imagepy/tools/Draw/aibrush_tol.py new file mode 100644 index 00000000..cb1b8da0 --- /dev/null +++ b/imagepy/tools/Draw/aibrush_tol.py @@ -0,0 +1,283 @@ +from sciapp.action import ImageTool +import numpy as np +from time import time +from skimage.morphology import flood_fill, flood +from skimage.draw import line, disk +from skimage.segmentation import felzenszwalb +from sciapp.util import mark2shp +from scipy.ndimage import binary_fill_holes, binary_dilation, binary_erosion + +def fill_normal(img, r, c, color, con, tor): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (r, c), connectivity=con, tolerance=tor) + img[msk] = color + +def local_brush(img, back, r, c, color, sigma, msize): + lab = felzenszwalb(back, 1, sigma, msize) + msk = flood(lab, (r, c), connectivity=2) + img[msk] = color + +def local_pen(img, r, c, R, color): + img = img.reshape((img.shape+(1,))[:3]) + rs, cs = disk((r, c), R/2+1e-6, shape=img.shape) + img[rs, cs] = color + +def local_in_fill(img, r, c, R, color, bcolor): + img = img.reshape((img.shape+(1,))[:3]) + msk = (img == color).min(axis=2) + filled = binary_fill_holes(msk) + filled ^= msk + rs, cs = disk((r, c), R/2+1e-6, shape=img.shape) + msk[:] = 0 + msk[rs, cs] = 1 + msk &= filled + img[msk] = bcolor + +def local_out_fill(img, r, c, R, color, bcolor): + img = img.reshape((img.shape+(1,))[:3]) + msk = (img != color).max(axis=2) + rs, cs = disk((r, c), R/2+1e-6, shape=img.shape) + buf = np.zeros_like(msk) + buf[rs, cs] = 1 + msk &= buf + img[msk] = bcolor + +def local_sketch(img, r, c, R, color, bcolor): + img = img.reshape((img.shape+(1,))[:3]) + msk = (img == color).min(axis=2) + dilation = binary_dilation(msk, np.ones((3,3))) + dilation ^= msk + rs, cs = disk((r, c), R/2+1e-6, shape=img.shape) + msk[:] = 0 + msk[rs, cs] = 1 + msk &= dilation + img[msk] = bcolor + +def global_both_line(img, r, c, color): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (r, c), connectivity=2) + dilation = binary_dilation(msk, np.ones((3,3))) + dilation ^= msk + img[dilation] = color + +def global_out_line(img, r, c, color): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (r, c), connectivity=2) + msk = binary_fill_holes(msk) + dilation = binary_dilation(msk, np.ones((3,3))) + dilation ^= msk + img[dilation] = color + +def global_in_line(img, r, c, color): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (r, c), connectivity=2) + inarea = binary_fill_holes(msk) + inarea ^= msk + inarea ^= binary_erosion(inarea, np.ones((3,3))) + img[inarea] = color + +def global_in_fill(img, r, c, color): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (r, c), connectivity=2) + filled = binary_fill_holes(msk) + filled ^= msk + img[filled] = color + +def global_out_fill(img, r, c, color): + img = img.reshape((img.shape+(1,))[:3]) + ori = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + ori &= flood(img[:,:,i], (r, c), connectivity=2) + filled = binary_fill_holes(ori) + dilation = binary_dilation(ori) + dilation ^= filled + rs, cs = np.where(dilation) + if len(rs)==0: return + msk = ((img == img[r,c]).min(axis=2)).astype(np.uint8) + flood_fill(msk, (rs[0], cs[0]), 2, connectivity=2, inplace=True) + img[msk==2] = color + +''' +general: size + +>>> local =================================== +block: minsize | left +in fill: r | left shift +pen: r | left ctrl +sketch: r | left alt +out fill: r | left ctrl + alt + +>>> global ================================== +flood: | right +in fill: | right shift +out line: | right ctrl +in line: | right alt +out fill: | right ctrl + alt + +scale and move: | wheel +''' + +class Plugin(ImageTool): + title = 'AI Brush' + + para = {'win':48, 'tor':10, 'con':'8-connect', 'ms':30, 'r':2, 'color':(255,0,128)} + view = [(int, 'win', (28, 64), 0, 'window', 'size'), + ('color', 'color', 'color', 'mark'), + ('lab', None, '======= Brush ======='), + (float, 'ms', (10, 50), 0, 'stickiness', 'pix'), + ('lab', None, '======= Pen ======='), + (int, 'r', (1, 30), 0, 'radius', 'pix'), + ('lab', None, '======= Flood ======='), + (int, 'tor', (0,1000), 0, 'tolerance', 'value'), + (list, 'con', ['4-connect', '8-connect'], str, 'connect', 'pix')] + + def __init__(self): + self.status = None + self.pickp = (0,0) + self.oldp = (0,0) + + def mouse_down(self, ips, x, y, btn, **key): + if btn==2: + self.oldp = key['canvas'].to_panel_coor(x,y) + self.status = 'move' + return + + self.oldp = self.pickp = (y, x) + color = self.app.manager('color').get('front') + x = int(round(min(max(x,0), ips.img.shape[1]))) + y = int(round(min(max(y,0), ips.img.shape[0]))) + color = (np.mean(color), color)[ips.img.ndim==3] + self.pickcolor = ips.img[y, x] + ips.snapshot() + + if btn==1 and key['ctrl'] and key['alt']: + self.status = 'local_out' + elif btn==1 and key['ctrl']: + self.status = 'local_pen' + elif btn==1 and key['alt']: + self.status = 'local_sketch' + elif btn==1 and key['shift']: + self.status = 'local_in' + elif btn==1: + self.status = 'local_brush' + elif btn==3 and key['ctrl'] and key['alt']: + self.status = 'global_out_fill' + global_out_fill(ips.img, y, x, color) + ips.update() + elif btn==3 and key['ctrl']: + self.status = 'global_out_line' + global_out_line(ips.img, y, x, color) + ips.update() + elif btn==3 and key['alt']: + self.status = 'global_in_line' + global_in_line(ips.img, y, x, color) + ips.update() + elif btn==3 and key['shift']: + self.status = 'global_in_fill' + global_in_fill(ips.img, y, x, color) + ips.update() + elif btn==3: + if (ips.img[y, x] - color).sum()==0: return + conn = {'4-connect':1, '8-connect':2} + conn = conn[self.para['con']] + tor = self.para['tor'] + fill_normal(ips.img, y, x, color, conn, tor) + ips.update() + + def mouse_up(self, ips, x, y, btn, **key): + if btn==1 and (y,x)==self.pickp and key['ctrl']: + x = int(round(min(max(x,0), ips.img.shape[1]))) + y = int(round(min(max(y,0), ips.img.shape[0]))) + self.app.manager('color').add('front', ips.img[y, x]) + self.status = None + ips.mark = None + ips.update() + + def make_mark(self, x, y): + wins = self.para['win'] + rect = {'type':'rectangle', 'body':(x-wins, y-wins, wins*2, wins*2), 'color':self.para['color']} + mark = {'type':'layer', 'body':[rect]} + r = 2 if self.status=='local_brush' else self.para['r']/2 + mark['body'].append({'type':'circle', 'body':(x, y, r), 'color':self.para['color']}) + + mark['body'].append({'type':'text', 'body':(x-wins, y-wins, + 'S:%s W:%s'%(self.para['ms'], self.para['win'])), 'pt':False, 'color':self.para['color']}) + return mark2shp(mark) + + def mouse_move(self, ips, x, y, btn, **key): + if self.status == None and ips.mark != None: + ips.mark = None + ips.update() + if not self.status in ['local_pen','local_brush', + 'local_sketch','local_in','local_out','move']: return + img, color = ips.img, self.app.manager('color').get('front') + x = int(round(min(max(x,0), img.shape[1]))) + y = int(round(min(max(y,0), img.shape[0]))) + + if self.status == 'move': + x,y = key['canvas'].to_panel_coor(x,y) + key['canvas'].move(x-self.oldp[0], y-self.oldp[1]) + self.oldp = x, y + ips.update() + return + + rs, cs = line(*[int(round(i)) for i in self.oldp + (y, x)]) + np.clip(rs, 0, img.shape[0]-1, out=rs) + np.clip(cs, 0, img.shape[1]-1, out=cs) + + color = (np.mean(color), color)[img.ndim==3] + + for r,c in zip(rs, cs): + start = time() + w = self.para['win'] + sr = (max(0,r-w), min(img.shape[0], r+w)) + sc = (max(0,c-w), min(img.shape[1], c+w)) + r, c = min(r, w), min(c, w) + backclip = imgclip = img[slice(*sr), slice(*sc)] + if not ips.back is None: + backclip = ips.back.img[slice(*sr), slice(*sc)] + + if self.status == 'local_pen': + local_pen(imgclip, r, c, self.para['r'], color) + if self.status == 'local_brush': + if (imgclip[r,c] - color).sum()==0: continue + local_brush(imgclip, backclip, r, c, color, 0, self.para['ms']) + if self.status == 'local_in': + local_in_fill(imgclip, r, c, self.para['r'], self.pickcolor, color) + if self.status == 'local_sketch': + local_sketch(imgclip, r, c, self.para['r'], self.pickcolor, color) + if self.status=='local_out': + local_out_fill(imgclip, r, c, self.para['r'], self.pickcolor, color) + + + ips.mark = self.make_mark(x, y) + self.oldp = (y, x) + ips.update() + + def mouse_wheel(self, ips, x, y, d, **key): + if key['shift']: + if d>0: self.para['ms'] = min(50, self.para['ms']+1) + if d<0: self.para['ms'] = max(10, self.para['ms']-1) + ips.mark = self.make_mark(x, y) + elif key['ctrl'] and key['alt']: + if d>0: self.para['win'] = min(64, self.para['win']+1) + if d<0: self.para['win'] = max(28, self.para['win']-1) + ips.mark = self.make_mark(x, y) + elif key['ctrl']: + if d>0: self.para['r'] = min(30, self.para['r']+1) + if d<0: self.para['r'] = max(2, self.para['r']-1) + ips.mark = self.make_mark(x, y) + elif self.status == None: + if d>0:key['canvas'].zoomout(x, y, 'data') + if d<0:key['canvas'].zoomin(x, y, 'data') + ips.update() \ No newline at end of file diff --git a/imagepy/tools/Draw/flood3d.gif b/imagepy/tools/Draw/flood3d.gif new file mode 100644 index 00000000..d1bb00dc Binary files /dev/null and b/imagepy/tools/Draw/flood3d.gif differ diff --git a/imagepy/tools/Draw/flood3d_tol.py b/imagepy/tools/Draw/flood3d_tol.py new file mode 100644 index 00000000..2a602d8b --- /dev/null +++ b/imagepy/tools/Draw/flood3d_tol.py @@ -0,0 +1,32 @@ +from sciapp.action import ImageTool +import numpy as np +from skimage.morphology import flood_fill, flood +from sciapp.action import Filter, Simple + +class FloodFill3D(Simple): + title = 'Flood Fill 3D' + note = ['all', 'stack3d'] + + def run(self, ips, imgs, para = None): + flood_fill(imgs, para['seed'], para['color'], connectivity=para['conn'], tolerance=para['tor'], inplace=True) + +class Plugin(ImageTool): + title = 'Flood Fill 3D' + para = {'tor':10, 'con':'8-connect'} + view = [(int, 'tor', (0,1000), 0, 'torlorance', 'value'), + (list, 'con', ['4-connect', '8-connect'], str, 'fill', 'pix')] + + def mouse_down(self, ips, x, y, btn, **key): + FloodFill3D().start(self.app, {'seed':(ips.cur, int(y), int(x)), + 'color':np.mean(self.app.manager('color').get('front')), + 'conn':(self.para['con']=='8-connect')+1, 'tor':self.para['tor']}) + + def mouse_up(self, ips, x, y, btn, **key): + pass + + def mouse_move(self, ips, x, y, btn, **key): + pass + + def mouse_wheel(self, ips, x, y, d, **key): + pass + diff --git a/imagepy/tools/Draw/floodfill_tol.py b/imagepy/tools/Draw/floodfill_tol.py index c6cc85b6..3a493bef 100644 --- a/imagepy/tools/Draw/floodfill_tol.py +++ b/imagepy/tools/Draw/floodfill_tol.py @@ -5,26 +5,32 @@ @author: yxl """ -from imagepy.core.engine import Tool +from sciapp.action import ImageTool import numpy as np -from imagepy.core.manager import ColorManager -from imagepy.core.draw.fill import floodfill +# from imagepy.core.draw.fill import floodfill +from skimage.morphology import flood_fill, flood -class Plugin(Tool): +class Plugin(ImageTool): title = 'Flood Fill' para = {'tor':10, 'con':'8-connect'} view = [(int, 'tor', (0,1000), 0, 'torlorance', 'value'), (list, 'con', ['4-connect', '8-connect'], str, 'fill', 'pix')] def mouse_down(self, ips, x, y, btn, **key): + + img, color = ips.img, self.app.manager('color').get('front') + if int(y)<0 or int(x)<0: return + if int(y)>=img.shape[0] or int(x)>=img.shape[1]: return + ips.snapshot() - msk = floodfill(ips.img, x, y, self.para['tor'], self.para['con']=='8-connect') - #plt.imshow(msk) - #plt.show() - color = ColorManager.get_front() - if ips.get_nchannels()==1:color = np.mean(color) - ips.img[msk] = color - ips.update = 'pix' + connectivity=(self.para['con']=='8-connect')+1 + img = ips.img.reshape((ips.img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (int(y),int(x)), + connectivity=connectivity, tolerance=self.para['tor']) + img[msk] = np.mean(color) if img.shape[2]==1 else color + ips.update() def mouse_up(self, ips, x, y, btn, **key): pass diff --git a/imagepy/tools/Draw/magic.gif b/imagepy/tools/Draw/magic.gif deleted file mode 100644 index d4cbd24b..00000000 Binary files a/imagepy/tools/Draw/magic.gif and /dev/null differ diff --git a/imagepy/tools/Draw/magic_tol.py b/imagepy/tools/Draw/magic_tol.py deleted file mode 100644 index 27da6d1e..00000000 --- a/imagepy/tools/Draw/magic_tol.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -import wx -from imagepy.core.engine import Tool -import numpy as np -from imagepy.core.manager import ColorManager -from imagepy.core.draw.fill import floodfill -from skimage.measure import find_contours -from imagepy.core.roi.convert import shape2roi, roi2shape -from shapely.geometry import Polygon, Point -from shapely.ops import cascaded_union -import matplotlib.pyplot as plt - -def polygonize(conts, withholes = False): - for i in conts:i[:,[0,1]] = i[:,[1,0]] - polygon = Polygon(conts[0]).buffer(0) - if not withholes:return polygon - holes = [] - for i in conts[1:]: - if len(i)<4:continue - holes.append(Polygon(i).buffer(0)) - hole = cascaded_union(holes) - return polygon.difference(hole) - - -class Plugin(Tool): - title = 'Magic Stick' - para = {'tor':10, 'con':'8-connect'} - view = [(int, 'tor', (0,1000), 0, 'torlorance', 'value'), - (list, 'con', ['4-connect', '8-connect'], str, 'fill', 'pix')] - - def __init__(self): - self.curobj = None - self.oper = '' - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - if btn==1 or btn==3: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - if not self.curobj in (None,True):return - if ips.roi == None: - msk = floodfill(ips.img, x, y, - self.para['tor'], self.para['con']=='8-connect') - conts = find_contours(msk, 0, 'high') - ips.roi = shape2roi(polygonize(conts, btn==3)) - elif hasattr(ips.roi, 'topolygon'): - shp = roi2shape(ips.roi.topolygon()) - oper = '' - if key['shift']: oper = '+' - elif key['ctrl']: oper = '-' - elif self.curobj: return - else: ips.roi=None - - msk = floodfill(ips.img, x, y, - self.para['tor'], self.para['con']=='8-connect') - conts = find_contours(msk, 0, 'high') - cur = polygonize(conts, btn==3) - if oper == '+': - ips.roi = shape2roi(shp.union(cur)) - elif oper == '-': - ips.roi = shape2roi(shp.difference(cur)) - else: ips.roi = shape2roi(cur) - - else: ips.roi = None - - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file diff --git a/imagepy/tools/Measure/angle2_tol.py b/imagepy/tools/Measure/angle2_tol.py index d548570a..8448ea97 100644 --- a/imagepy/tools/Measure/angle2_tol.py +++ b/imagepy/tools/Measure/angle2_tol.py @@ -1,131 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 3 22:21:32 2017 - -@author: yxl -""" - -import wx -from imagepy.core.engine import Tool -import numpy as np -import pandas as pd -from numpy.linalg import norm -from .setting import Setting -from imagepy import IPy - -class Angle: - """Define the class with line drawing fucntions """ - dtype = 'angle' - def __init__(self, body=None): - self.body = body if body!=None else [] - self.buf = [] - - def addline(self): - line = self.buf - if len(line)!=2 or line[0] !=line[-1]: - self.body.append(line) - self.buf = [] - - def snap(self, x, y, lim): - minl, idx = 1000, None - for i in self.body: - for j in i: - d = (j[0]-x)**2+(j[1]-y)**2 - if d < minl:minl,idx = d,(i, i.index(j)) - return idx if minl**0.52: - self.body.append(line) - self.buf = [] - - def snap(self, x, y, lim): - minl, idx = 1000, None - for i in self.body: - for j in i: - d = (j[0]-x)**2+(j[1]-y)**2 - if d < minl:minl,idx = d,(i, i.index(j)) - return idx if minl**0.5lim:return None - return cur, cur.index(cid) - - def pick(self, x, y, lim): - rst = self.snap(x, y, lim) - if rst!=None:return rst - for i in self.body: - if Polygon(i).contains(Point(x,y)): - return True, i - return None - - def draged(self, ox, oy, nx, ny, i): - self.update, self.infoupdate = True, True - if i[0]==True: - for j in range(len(i[1])): - i[1][j] = (i[1][j][0]+(nx-ox), i[1][j][1]+(ny-oy)) - else: - i[0][i[1]] = (nx, ny) - #print('drag,',i,self.body[0][0][i]hasattr(ips.roi, 'topolygon')) - if i[1]==0:i[0][-1] = (nx,ny) - if i[1]==len(i[0])-1: - i[0][0] = (nx, ny) - - def report(self, title): - rst = [] - titles = ['Area', 'OX', 'OY'] - for pg in self.body: - plg = Polygon(pg) - area, xy = plg.area, plg.centroid - unit = 1 if self.unit==None else self.unit[0] - axy = [area*unit**2, xy.x*unit, xy.y*unit] - rst.append([round(i,1) for i in axy]) - IPy.show_table(pd.DataFrame(rst, columns=titles), title) - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen(Setting['color'], width=1, style=wx.SOLID)) - dc.SetTextForeground(Setting['tcolor']) - font = wx.Font(10, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - - dc.SetFont(font) - dc.DrawLines([f(*i) for i in self.buf]) - for i in self.buf:dc.DrawCircle(f(*i),2) - - for pg in self.body: - plg = Polygon(pg) - dc.DrawLines([f(*i) for i in pg]) - for i in pg: dc.DrawCircle(f(*i),2) - area, xy = plg.area, plg.centroid - if self.unit!=None: - area *= self.unit[0]**2 - dc.DrawText('%.1f'%area, f(xy.x, xy.y)) - -class Plugin(Tool): - """Define the area class plugin with some events callback fucntions """ - title = 'Area' - def __init__(self): - self.curobj = None - self.doing = False - - def mouse_down(self, ips, x, y, btn, **key): - if key['ctrl'] and key['alt']: - if isinstance(ips.mark, Area): - ips.mark.report(ips.title) - return - - lim = 5.0/key['canvas'].get_scale() - if btn==1: - if not self.doing: - if isinstance(ips.mark, Area): - self.curobj = ips.mark.pick(x, y, lim) - if not self.curobj in (None,True):return - if not isinstance(ips.mark, Area): - ips.mark = Area(unit=ips.unit) - self.doing = True - elif isinstance(ips.mark, Area): - if key['shift']: self.oper,self.doing = '+',True - elif self.curobj: return - else: ips.mark=None - if self.doing: - ips.mark.addpoint((x,y)) - self.curobj = (ips.mark.buf, -1) - self.odx, self.ody = x, y - - elif btn==3: - if self.doing: - ips.mark.addpoint((x,y)) - self.doing = False - ips.mark.commit() - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if not isinstance(ips.mark, Area):return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.mark.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - ips.mark.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_up(self, ips, x, y, btn, **key): - self.curobj = None - - def on_switch(self): - print('AreaTool_Plugin') \ No newline at end of file +from sciapp.action import AreaTool as Plugin \ No newline at end of file diff --git a/imagepy/tools/Measure/coordinate_tol.py b/imagepy/tools/Measure/coordinate_tol.py index 494d6801..f48bb672 100644 --- a/imagepy/tools/Measure/coordinate_tol.py +++ b/imagepy/tools/Measure/coordinate_tol.py @@ -1,99 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 2 23:04:46 2017 - -@author: yxl -""" - -import wx -from imagepy.core.engine import Tool -from .setting import Setting -from imagepy import IPy -import pandas as pd - -class Coordinate: - """Define the coordinate class""" - dtype = 'coordinate' - def __init__(self, body=None, unit=None): - self.body = body if body!=None else [] - self.unit = unit - - def add(self, p): - self.body.append(p) - - def snap(self, x, y, lim): - cur, minl = None, 1000 - for i in self.body: - d = (i[0]-x)**2+(i[1]-y)**2 - if d < minl:cur,minl = i,d - if minl**0.5>lim:return None - return self.body.index(cur) - - def pick(self, x, y, lim): - return self.snap(x, y, lim) - - def draged(self, ox, oy, nx, ny, i): - self.body[i] = (nx, ny) - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen(Setting['color'], width=1, style=wx.SOLID)) - dc.SetTextForeground(Setting['tcolor']) - font = wx.Font(10, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - - dc.SetFont(font) - for i in self.body: - x,y = f(*i) - unit = 1 if self.unit is None else self.unit[0] - dc.DrawCircle(x, y, 2) - dc.DrawText('(%.1f,%.1f)'%(i[0]*unit, i[1]*unit), x, y) - - def report(self, title): - unit = 1 if self.unit is None else self.unit[0] - rst = [(x*unit, y*unit) for x,y in self.body] - titles = ['OX', 'OY'] - IPy.show_table(pd.DataFrame(rst, columns=titles), title) - -class Plugin(Tool): - """Define the coordinate class plugin with the event callback functions""" - title = 'Coordinate' - def __init__(self): - self.curobj = None - self.odx, self.ody = 0, 0 - - def mouse_down(self, ips, x, y, btn, **key): - if key['ctrl'] and key['alt']: - if isinstance(ips.mark, Coordinate): - ips.mark.report(ips.title) - return - lim = 5.0/key['canvas'].get_scale() - if btn==1: - if isinstance(ips.mark, Coordinate): - self.curobj = ips.mark.pick(x, y, lim) - if self.curobj!=None:return - if not isinstance(ips.mark, Coordinate): - ips.mark = Coordinate(unit=ips.unit) - elif not key['shift']: - ips.mark = Coordinate(unit=ips.unit) - ips.mark.add((x,y)) - self.curobj = ips.mark.pick(x,y, lim) - ips.update = True - self.odx, self.ody = x, y - - def mouse_up(self, ips, x, y, btn, **key): - self.curobj = None - - def mouse_move(self, ips, x, y, btn, **key): - if not isinstance(ips.mark, Coordinate):return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.mark.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - ips.mark.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import CoordinateTool as Plugin \ No newline at end of file diff --git a/imagepy/tools/Measure/distance_tol.py b/imagepy/tools/Measure/distance_tol.py index fc2d48ef..6e646ede 100644 --- a/imagepy/tools/Measure/distance_tol.py +++ b/imagepy/tools/Measure/distance_tol.py @@ -1,130 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 3 22:21:32 2017 - -@author: yxl -""" - -import wx -from imagepy.core.engine import Tool -import numpy as np -import pandas as pd -from numpy.linalg import norm -from .setting import Setting -from imagepy import IPy - -class Distance: - """Define the distance class""" - dtype = 'distance' - def __init__(self, body=None, unit=None): - self.body = body if body!=None else [] - self.buf, self.unit = [], unit - - def addline(self): - line = self.buf - if len(line)!=2 or line[0] !=line[-1]: - self.body.append(line) - self.buf = [] - - def snap(self, x, y, lim): - minl, idx = 1000, None - for i in self.body: - for j in i: - d = (j[0]-x)**2+(j[1]-y)**2 - if d < minl:minl,idx = d,(i, i.index(j)) - return idx if minl**0.5=img.shape[1]:continue - if cy<0 or cy>=img.shape[0]:continue - if img[cy, cx]!=color:continue - img[cy, cx] = 0 - buf[s,0] = cx; buf[s,1] = cy - s+=1 - if s==len(buf): - buf[:len(buf)-cur] = buf[cur:] - s -= cur; cur=0 - cur += 1 - if cur==s:break def draw(img, lines): if len(lines)<2:return @@ -50,47 +19,42 @@ def draw(img, lines): xys.append((y,x)) ox, oy = i cur = 0 + neibs = [(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1)] for y,x in xys: - for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]: + for dx, dy in neibs: if img[y+dy, x+dx]>0: mark.append(cur) - continue + img[y+dy, x+dx] = 255 cur += 1 if len(mark)<4:return - for y,x in xys[mark[0]+1:mark[-1]-1]: - img[y,x] = 128 - -class Mark(): - def __init__(self, line): - self.line = line - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((255,0,0), width=2, style=wx.SOLID)) - dc.DrawLines([f(*i) for i in self.line]) + for y,x in xys[mark[0]+1:mark[-1]-1]: img[y,x] = 128 + for cur in mark: img[tuple(xys[cur])] = 255 -class Plugin(Tool): +class Plugin(ImageTool): title = 'Graph Cut' def __init__(self): self.status = 0 + self.line = {'type':'line', 'body':[]} def mouse_down(self, ips, x, y, btn, **key): if btn==1: ips.snapshot() self.status = 1 - self.cur = [(x, y)] - ips.mark = Mark(self.cur) - ips.update = True + self.line['body'] = [(x, y)] + ips.mark = mark2shp(self.line) + ips.update() def mouse_up(self, ips, x, y, btn, **key): ips.mark = None self.status = 0 - draw(ips.img, self.cur) - ips.update = 'pix' + draw(ips.img, self.line['body']) + ips.update() def mouse_move(self, ips, x, y, btn, **key): if self.status==1: - self.cur.append((x, y)) - ips.update = True + self.line['body'].append((x, y)) + ips.mark = mark2shp(self.line) + ips.update() def mouse_wheel(self, ips, x, y, d, **key): pass \ No newline at end of file diff --git a/imagepy/tools/Standard/colorpicker_tol.py b/imagepy/tools/Standard/colorpicker_tol.py index b13bcd27..4b3c8028 100644 --- a/imagepy/tools/Standard/colorpicker_tol.py +++ b/imagepy/tools/Standard/colorpicker_tol.py @@ -1,13 +1,6 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 +from sciapp.action import ImageTool -@author: yxl -""" -from imagepy.core.engine import Tool -from imagepy.core.manager import ColorManager - -class Plugin(Tool): +class Plugin(ImageTool): """ColorPicker class plugin with events callbacks""" title = 'Color Picker' para = {'front':(255,255,255), 'back':(0,0,0)} @@ -15,14 +8,13 @@ class Plugin(Tool): ('color', 'back', 'back', 'color')] def config(self): - ColorManager.set_front(self.para['front']) - ColorManager.set_back(self.para['back']) + self.app.manager('color').add('front', self.para['front']) + self.app.manager('color').add('back', self.para['back']) def mouse_down(self, ips, x, y, btn, **key): - if btn == 1:ColorManager.set_front(ips.img[int(y), int(x)]) - if btn == 3:ColorManager.set_back(ips.img[int(y), int(x)]) - print(ips.img[int(y), int(x)]) - print(ColorManager.get_front()) + manager = self.app.manager('color') + if btn == 1: manager.add('front', ips.img[int(y), int(x)]) + if btn == 3: manager.add('back', ips.img[int(y), int(x)]) def mouse_up(self, ips, x, y, btn, **key): pass diff --git a/imagepy/tools/Standard/freearea_tol.py b/imagepy/tools/Standard/freearea_tol.py index fcb6c50b..3f95cb4e 100644 --- a/imagepy/tools/Standard/freearea_tol.py +++ b/imagepy/tools/Standard/freearea_tol.py @@ -1,72 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import polygonroi -import wx -from .polygon_tol import Polygonbuf -from imagepy.core.engine import Tool - -class Plugin(Tool): - """FreeArea class plugin with events callbacks""" - title = 'Free Area' - def __init__(self): - self.curobj = None - self.doing = False - self.oper = '' - self.helper = Polygonbuf() - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if not self.curobj in (None,True):return - #self.oper = '+' - if ips.roi == None: - ips.roi = polygonroi.PolygonRoi() - self.doing = True - elif hasattr(ips.roi, 'topolygon'): - if key['shift']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing = '+',True - elif key['ctrl']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing = '-',True - elif self.curobj: return - else: ips.roi=None - else: ips.roi = None - if self.doing: - self.helper.addpoint((x,y)) - self.odx, self.ody = x, y - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - if self.doing: - self.helper.addpoint((x,y)) - self.doing = False - self.curobj = None - ips.roi.commit(self.helper.pop(), self.oper) - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if self.doing: - self.helper.addpoint((x,y)) - elif self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import FreePolygonROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/freeline_tol.py b/imagepy/tools/Standard/freeline_tol.py index d46237d7..f8888bc5 100644 --- a/imagepy/tools/Standard/freeline_tol.py +++ b/imagepy/tools/Standard/freeline_tol.py @@ -1,81 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import lineroi -import wx -from imagepy.core.engine import Tool - -class Linebuf: - """FreeLinebuf class""" - title = 'Free Line' - def __init__(self): - self.buf = [] - - def addpoint(self, p): - self.buf.append(p) - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((0,255,255), width=1, style=wx.SOLID)) - if len(self.buf)>1: - dc.DrawLines([f(*i) for i in self.buf]) - for i in self.buf:dc.DrawCircle(f(*i),2) - - def pop(self): - a = self.buf - self.buf = [] - return a - -class Plugin(Tool): - """FreeLinebuf class plugin with events callbacks""" - def __init__(self): - self.curobj = None - self.doing = False - self.helper = Linebuf() - self.odx,self.ody = 0, 0 - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if self.curobj!=None:return - - if ips.roi == None: - ips.roi = lineroi.LineRoi() - self.doing = True - elif ips.roi.dtype=='line' and key['shift']: - self.doing = True - else: ips.roi = None - if self.doing: - self.helper.addpoint((x,y)) - self.odx, self.ody = x,y - - def mouse_up(self, ips, x, y, btn, **key): - if self.doing: - self.doing = False - self.curobj = None - ips.roi.addline(self.helper.pop()) - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if self.doing: - self.helper.addpoint((x,y)) - elif self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import FreeLineROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/line_tol.py b/imagepy/tools/Standard/line_tol.py index 7de48a6e..935a40b6 100644 --- a/imagepy/tools/Standard/line_tol.py +++ b/imagepy/tools/Standard/line_tol.py @@ -1,87 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import lineroi -import wx -from imagepy.core.engine import Tool - -class Linebuf: - """Linebuf class""" - def __init__(self): - self.buf = [] - - def addpoint(self, p): - self.buf.append(p) - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((0,255,255), width=1, style=wx.SOLID)) - if len(self.buf)>1: - dc.DrawLines([f(*i) for i in self.buf]) - for i in self.buf:dc.DrawCircle(f(*i),2) - - def pop(self): - a = self.buf - self.buf = [] - return a - -class Plugin(Tool): - """FreeLinebuf class plugin with events callbacks""" - title = 'Line' - def __init__(self): - self.curobj = None - self.doing = False - self.helper = Linebuf() - self.odx,self.ody = 0, 0 - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - print(ips.roi) - print(self.curobj) - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if self.curobj!=None:return - - if ips.roi == None: - print(1) - ips.roi = lineroi.LineRoi() - self.doing = True - elif ips.roi.dtype=='line' and key['shift']: - print(2) - self.doing = True - else: ips.roi = None - if self.doing: - self.helper.addpoint((x,y)) - self.curobj = (self.helper.buf, -1) - self.odx, self.ody = x,y - - elif btn==3: - if self.doing: - self.helper.addpoint((x,y)) - self.doing = False - ips.roi.addline(self.helper.pop()) - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - self.curobj = None - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import LineROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/magic_tol.py b/imagepy/tools/Standard/magic_tol.py index b4355226..7a99b63e 100644 --- a/imagepy/tools/Standard/magic_tol.py +++ b/imagepy/tools/Standard/magic_tol.py @@ -5,17 +5,17 @@ @author: yxl """ import wx -from imagepy.core.engine import Tool +from sciapp.action import ImageTool, BaseROI, BaseEditor +from sciapp.util import geom_union, geom_flatten, geom2shp +from sciapp.object import ROI import numpy as np -from imagepy.core.manager import ColorManager -from imagepy.core.draw.fill import floodfill +from skimage.morphology import flood_fill, flood from skimage.measure import find_contours -from imagepy.core.roi.convert import shape2roi, roi2shape from shapely.geometry import Polygon, Point from shapely.ops import cascaded_union -import matplotlib.pyplot as plt -def polygonize(conts, withholes = False): + +def polygonize(conts, withholes = True): for i in conts:i[:,[0,1]] = i[:,[1,0]] polygon = Polygon(conts[0]).buffer(0) if not withholes:return polygon @@ -26,65 +26,87 @@ def polygonize(conts, withholes = False): hole = cascaded_union(holes) return polygon.difference(hole) - -class Plugin(Tool): +def magic_cont(img, x, y, conn, tor): + img = img.reshape((img.shape+(1,))[:3]) + msk = np.ones(img.shape[:2], dtype='bool') + for i in range(img.shape[2]): + msk &= flood(img[:,:,i], (int(y),int(x)), + connectivity=conn, tolerance=tor) + return find_contours(msk, 0, 'high') + +def inbase(key, btn): + status = key['ctrl'], key['alt'], key['shift'] + return status == (1,1,0) or btn in {2,3} + +class Plugin(BaseROI): title = 'Magic Stick' para = {'tor':10, 'con':'8-connect'} view = [(int, 'tor', (0,1000), 0, 'torlorance', 'value'), (list, 'con', ['4-connect', '8-connect'], str, 'fill', 'pix')] - def __init__(self): - self.curobj = None - self.oper = '' - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - if btn==1 or btn==3: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if not self.curobj in (None,True):return - if ips.roi == None: - msk = floodfill(ips.img, x, y, - self.para['tor'], self.para['con']=='8-connect') - conts = find_contours(msk, 0, 'high') - ips.roi = shape2roi(polygonize(conts, btn==3)) - elif hasattr(ips.roi, 'topolygon'): - shp = roi2shape(ips.roi.topolygon()) - oper = '' - if key['shift']: oper = '+' - elif key['ctrl']: oper = '-' - elif self.curobj: return - else: ips.roi=None + + def __init__(self): + BaseROI.__init__(self, BaseEditor) + + def mouse_down(self, ips, x, y, btn, **key): + if ips.roi is None: ips.roi = ROI() + else: ips.roi.msk = None + if inbase(key, btn): + BaseEditor.mouse_down(self, ips.roi, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + conts = magic_cont(ips.img, x, y, + (self.para['con']=='8-connect')+1, self.para['tor']) + ips.roi.body.append(geom2shp(polygonize(conts))) + ips.roi.dirty = True - msk = floodfill(ips.img, x, y, - self.para['tor'], self.para['con']=='8-connect') - conts = find_contours(msk, 0, 'high') - cur = polygonize(conts, btn==3) - if oper == '+': - ips.roi = shape2roi(shp.union(cur)) - elif oper == '-': - ips.roi = shape2roi(shp.difference(cur)) - else: ips.roi = shape2roi(cur) + if key['alt'] or key['shift']: + obj = ips.roi.body.pop(-1) + rst = geom_union(ips.roi.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + ips.roi.body = layer.body + ips.roi.dirty = True - else: ips.roi = None + ''' - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn) and self.obj is None: + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + if self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + else: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + if btn==3 and not self.obj is None : + body = np.vstack((self.obj.body, [(x,y)])) + shp.body[-1] = Polygon(body) + if key['alt'] or key['shift']: + obj = shp.body.pop(-1) + rst = geom_union(shp.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + shp.body = layer.body + self.obj, shp.dirty = None, True + del key['canvas'].marks['buffer'] + shp.dirty = True + + ''' \ No newline at end of file diff --git a/imagepy/tools/Standard/move_tol.py b/imagepy/tools/Standard/move_tol.py index 2cc8b963..c1f6c7ca 100644 --- a/imagepy/tools/Standard/move_tol.py +++ b/imagepy/tools/Standard/move_tol.py @@ -1,30 +1,24 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Nov 16 12:12:43 2016 +from sciapp.action import ImageTool -@author: yxl -""" -import wx -from imagepy.core.engine import Tool - -class Plugin(Tool): - title = 'Move' - def __init__(self): - self.ox, self.oy = 0, 0 - self.cursor = wx.CURSOR_HAND - - def mouse_down(self, ips, x, y, btn, **key): - self.ox, self.oy = key['canvas'].to_panel_coor(x,y) +class Plugin(ImageTool): + title = 'Move And Scale' + def __init__(self): + self.oldxy = None + + def mouse_down(self, obj, x, y, btn, **key): + if btn==1: self.oldxy = key['px'], key['py'] + if btn==3: key['canvas'].fit() + + def mouse_up(self, obj, x, y, btn, **key): + self.oldxy = None - def mouse_up(self, ips, x, y, btn, **key): - pass + def mouse_move(self, obj, x, y, btn, **key): + if self.oldxy is None: return + ox, oy = self.oldxy + up = (1,-1)[key['canvas'].up] + key['canvas'].move(key['px']-ox, (key['py']-oy)*up) + self.oldxy = key['px'], key['py'] - def mouse_move(self, ips, x, y, btn, **key): - if btn==None:return - x,y = key['canvas'].to_panel_coor(x,y) - key['canvas'].move(x-self.ox, y-self.oy) - self.ox, self.oy = x,y - - def mouse_wheel(self, ips, x, y, d, **key): - if d>0:key['canvas'].zoomout(x,y) - if d<0:key['canvas'].zoomin(x,y) \ No newline at end of file + def mouse_wheel(self, obj, x, y, d, **key): + if d>0: key['canvas'].zoomout(x, y, coord='data') + if d<0: key['canvas'].zoomin(x, y, coord='data') \ No newline at end of file diff --git a/imagepy/tools/Standard/oval_tol.py b/imagepy/tools/Standard/oval_tol.py index 8d297b0d..07a9a360 100644 --- a/imagepy/tools/Standard/oval_tol.py +++ b/imagepy/tools/Standard/oval_tol.py @@ -1,88 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import ovalroi -import numpy as np -import wx -from .polygon_tol import Polygonbuf -from imagepy.core.engine import Tool - -class Plugin(Tool): - title = 'Ellipse' - def __init__(self): - self.curobj = None - self.doing = False - self.oper = '' - self.helper = Polygonbuf() - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if not self.curobj in (None,True):return - self.oper = '+' - if ips.roi==None or not hasattr(ips.roi, 'topolygon'): - ips.roi = ovalroi.OvalRoi() - self.doing = True - ips.roi.lt, ips.roi.tp = x, y - ips.roi.rt, ips.roi.bm = x, y - self.curobj = 'rb' - self.odx, self.ody = x,y - elif hasattr(ips.roi, 'topolygon'): - self.odx, self.ody = x, y - self.ox, self.oy = x, y - if key['shift']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing,self.curobj = '+',True,None - elif key['ctrl']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing,self.curobj = '-',True,None - elif self.curobj: return - else: - ips.roi = ovalroi.OvalRoi() - self.doing = True - ips.roi.lt, ips.roi.tp = x, y - ips.roi.rt, ips.roi.bm = x, y - self.curobj = 'rb' - self.odx, self.ody = x,y - else: ips.roi = None - - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - if self.doing: - self.doing = False - self.curobj = None - if ips.roi.dtype == 'rect': - if not ips.roi.commit():ips.roi = None - elif ips.roi.dtype == 'polygon': - ips.roi.commit(self.helper.pop(), self.oper) - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if ips.roi.dtype == 'polygon' and self.doing: - l,b,r,t = self.ox, self.oy, x, y - ar = np.linspace(0, np.pi*2,29) - xs = np.cos(ar)*abs(r-l)/2+(r+l)/2 - ys = np.sin(ar)*abs(t-b)/2+(t+b)/2 - self.helper.buf = [[(x,y) for x,y in zip(xs,ys)],[]] - if self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import EllipseROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/painter_tol.py b/imagepy/tools/Standard/painter_tol.py index 6d172a3c..6a2dcf5a 100644 --- a/imagepy/tools/Standard/painter_tol.py +++ b/imagepy/tools/Standard/painter_tol.py @@ -1,34 +1,38 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 +from sciapp.action import ImageTool +from skimage.draw import line, disk -@author: yxl -""" -from imagepy.core.draw import paint -from imagepy.core.engine import Tool -import wx +def drawline(img, oldp, newp, w, value): + if img.ndim == 2 and hasattr(value, '__iter__'): value = sum(value)/3 + oy, ox = line(*[int(round(i)) for i in oldp+newp]) + cy, cx = disk((0, 0), w/2+1e-6) + ys = (oy.reshape((-1,1))+cy).clip(0, img.shape[0]-1) + xs = (ox.reshape((-1,1))+cx).clip(0, img.shape[1]-1) + img[ys.ravel(), xs.ravel()] = value -class Plugin(Tool): +class Plugin(ImageTool): title = 'Pencil' - view = [(int, 'width', (0,30), 0, 'width', 'pix')] + para = {'width':1} + view = [(int, 'width', (0,30), 0, 'width', 'pix')] def __init__(self): - self.sta = 0 - self.paint = paint.Paint() - self.cursor = wx.CURSOR_CROSS + self.status = False + self.oldp = (0,0) def mouse_down(self, ips, x, y, btn, **key): - self.sta = 1 - self.paint.set_curpt(x,y) + self.status = True + self.oldp = (y, x) ips.snapshot() def mouse_up(self, ips, x, y, btn, **key): - self.sta = 0 + self.status = False def mouse_move(self, ips, x, y, btn, **key): - if self.sta==0:return - self.paint.lineto(ips.img,x,y, self.para['width']) - ips.update = 'pix' + if not self.status:return + w = self.para['width'] + value = self.app.manager('color').get('front') + drawline(ips.img, self.oldp, (y, x), w, value) + self.oldp = (y, x) + ips.update() def mouse_wheel(self, ips, x, y, d, **key):pass \ No newline at end of file diff --git a/imagepy/tools/Standard/point_tol.py b/imagepy/tools/Standard/point_tol.py index 048683a6..3d2a288d 100644 --- a/imagepy/tools/Standard/point_tol.py +++ b/imagepy/tools/Standard/point_tol.py @@ -1,48 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import pointroi -import wx -from imagepy.core.engine import Tool - -class Plugin(Tool): - title = 'Point' - def __init__(self): - self.curobj = None - self.odx, self.ody = 0, 0 - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - if btn==1: - if ips.roi!=None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if self.curobj!=None:return - if not isinstance(ips.roi, pointroi.PointRoi): - ips.roi = pointroi.PointRoi() - if not key['shift']:del ips.roi.body[:] - ips.roi.add((x,y)) - self.curobj = ips.roi.pick(x,y, lim) - ips.update = True - self.odx, self.ody = x, y - - def mouse_up(self, ips, x, y, btn, **key): - self.curobj = None - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import PointROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/polygon_tol.py b/imagepy/tools/Standard/polygon_tol.py index 8f0fb977..f29674f9 100644 --- a/imagepy/tools/Standard/polygon_tol.py +++ b/imagepy/tools/Standard/polygon_tol.py @@ -1,86 +1,9 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 +from sciapp.action import PolygonROI as Plugin -@author: yxl -""" -from imagepy.core.roi import polygonroi -import wx +if __name__ == '__main__': + from skimage.data import camera, astronaut + from sciwx.app import ImageApp -class Polygonbuf: - def __init__(self): - self.buf = [[],[]] - - def addpoint(self, p): - self.buf[0].append(p) - - def draw(self, dc, f, **key): - dc.SetPen(wx.Pen((0,255,0), width=1, style=wx.SOLID)) - if len(self.buf[0])>1: - dc.DrawLines([f(*i) for i in self.buf[0]]) - for i in self.buf[0]: dc.DrawCircle(f(*i),2) - - def pop(self): - a = self.buf - self.buf = [[],[]] - return a - -from imagepy.core.engine import Tool - -class Plugin(Tool): - title = 'Polygon' - def __init__(self): - self.curobj = None - self.doing = False - self.oper = '' - self.helper = Polygonbuf() - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if not self.curobj in (None,True):return - self.oper = '+' - if ips.roi == None: - ips.roi = polygonroi.PolygonRoi() - self.doing = True - elif hasattr(ips.roi, 'topolygon'): - if key['shift']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing = '+',True - elif key['ctrl']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing = '-',True - elif self.curobj: return - else: ips.roi=None - else: ips.roi = None - if self.doing: - self.helper.addpoint((x,y)) - self.curobj = (self.helper.buf[0], -1) - self.odx, self.ody = x, y - - elif btn==3: - if self.doing: - self.helper.addpoint((x,y)) - self.doing = False - ips.roi.commit(self.helper.pop(), self.oper) - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def on_switch(self): - print('hahaha') \ No newline at end of file + ImageApp.start( + imgs = [('astronaut', astronaut())], + plgs=[('P', Plugin)]) \ No newline at end of file diff --git a/imagepy/tools/Standard/rectangle_tol.py b/imagepy/tools/Standard/rectangle_tol.py index 7bc7caf2..c73ba4d5 100644 --- a/imagepy/tools/Standard/rectangle_tol.py +++ b/imagepy/tools/Standard/rectangle_tol.py @@ -1,84 +1 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 - -@author: yxl -""" -from imagepy.core.roi import rectangleroi, polygonroi -import wx -from .polygon_tol import Polygonbuf -from imagepy.core.engine import Tool - -class Plugin(Tool): - title = 'Rectangle' - def __init__(self): - self.curobj = None - self.doing = False - self.oper = '' - self.helper = Polygonbuf() - - def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() - ips.mark = self.helper - if btn==1: - if not self.doing: - if ips.roi!= None: - self.curobj = ips.roi.pick(x, y, lim) - ips.roi.info(ips, self.curobj) - if not self.curobj in (None,True):return - self.oper = '+' - if ips.roi==None or not hasattr(ips.roi, 'topolygon'): - ips.roi = rectangleroi.RectangleRoi() - self.doing = True - ips.roi.lt, ips.roi.tp = x, y - ips.roi.rt, ips.roi.bm = x, y - self.curobj = 'rb' - self.odx, self.ody = x,y - elif hasattr(ips.roi, 'topolygon'): - self.odx, self.ody = x, y - self.ox, self.oy = x, y - if key['shift']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing,self.curobj = '+',True,None - elif key['ctrl']: - ips.roi = ips.roi.topolygon() - self.oper,self.doing,self.curobj = '-',True,None - elif self.curobj: return - else: - ips.roi = rectangleroi.RectangleRoi() - self.doing = True - ips.roi.lt, ips.roi.tp = x, y - ips.roi.rt, ips.roi.bm = x, y - self.curobj = 'rb' - self.odx, self.ody = x,y - else: ips.roi = None - - ips.update = True - - def mouse_up(self, ips, x, y, btn, **key): - if self.doing: - self.doing = False - self.curobj = None - if ips.roi.dtype == 'rect': - if not ips.roi.commit():ips.roi = None - elif ips.roi.dtype == 'polygon': - ips.roi.commit(self.helper.pop(), self.oper) - ips.update = True - - def mouse_move(self, ips, x, y, btn, **key): - if ips.roi==None:return - lim = 5.0/key['canvas'].get_scale() - if btn==None: - self.cursor = wx.CURSOR_CROSS - if ips.roi.snap(x, y, lim)!=None: - self.cursor = wx.CURSOR_HAND - elif btn==1: - if ips.roi.dtype == 'polygon' and self.doing: - l,b,r,t = self.ox, self.oy, x, y - self.helper.buf = [[(l,b),(r,b),(r,t),(l,t),(l,b)],[]] - if self.curobj: ips.roi.draged(self.odx, self.ody, x, y, self.curobj) - ips.update = True - self.odx, self.ody = x, y - - def mouse_wheel(self, ips, x, y, d, **key): - pass \ No newline at end of file +from sciapp.action import RectangleROI as Plugin \ No newline at end of file diff --git a/imagepy/tools/Standard/scale_tol.py b/imagepy/tools/Standard/scale_tol.py index 8bcc6f79..7a7ef570 100644 --- a/imagepy/tools/Standard/scale_tol.py +++ b/imagepy/tools/Standard/scale_tol.py @@ -1,12 +1,6 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 19 17:35:09 2016 +from sciapp.action import ImageTool -@author: yxl -""" -from imagepy.core.engine import Tool - -class Plugin(Tool): +class Plugin(ImageTool): title = 'Scope' def __init__(self): self.ox, self.oy = 0, 0 @@ -16,8 +10,9 @@ def mouse_down(self, ips, x, y, btn, **key): self.ox, self.oy = key['canvas'].to_panel_coor(x,y) print(self.ox, self.oy) #print 'down', self.ox, self.oy - if btn==1: key['canvas'].zoomout(x,y) - if btn==3: key['canvas'].zoomin(x,y) + if btn==1: key['canvas'].zoomout(x, y, 'data') + if btn==3: key['canvas'].zoomin(x, y, 'data') + ips.update() def mouse_up(self, ips, x, y, btn, **key): pass @@ -28,8 +23,10 @@ def mouse_move(self, ips, x, y, btn, **key): #print 'x,y',x,y #print 'dx,dy:', x-self.ox, y-self.oy key['canvas'].move(x-self.ox, y-self.oy) + ips.update() self.ox, self.oy = x,y def mouse_wheel(self, ips, x, y, d, **key): - if d>0:key['canvas'].zoomout(x,y) - if d<0:key['canvas'].zoomin(x,y) \ No newline at end of file + if d>0:key['canvas'].zoomout(x, y, 'data') + if d<0:key['canvas'].zoomin(x, y, 'data') + ips.update() \ No newline at end of file diff --git a/imagepy/tools/Toolkit3D/cursor3d_tol.py b/imagepy/tools/Toolkit3D/cursor3d_tol.py index 4d4c1760..366ff90e 100644 --- a/imagepy/tools/Toolkit3D/cursor3d_tol.py +++ b/imagepy/tools/Toolkit3D/cursor3d_tol.py @@ -1,10 +1,10 @@ import wx -from imagepy.core.engine import Tool -from imagepy.core import myvi -from imagepy import IPy +from sciapp.action import ImageTool +#from imagepy.core import myvi +#from imagepy import IPy import numpy as np -class Plugin(Tool): +class Plugin(ImageTool): title = 'Cursor 3D' para = {'r':1, 'color':(255,0,0)} view = [(int, 'r', (0,100), 0, 'radius', 'pix'), @@ -56,6 +56,6 @@ def mouse_wheel(self, ips, x, y, d, **key): ips.cur+=1 if d<0: if ips.cur>0:ips.cur-=1 - ips.update = 'pix' + ips.update() if not self.pressed: return self.set_cursor(y, ips.cur, x) \ No newline at end of file diff --git a/imagepy/tools/Toolkit3D/flood3d.gif b/imagepy/tools/Toolkit3D/flood3d.gif new file mode 100644 index 00000000..d1bb00dc Binary files /dev/null and b/imagepy/tools/Toolkit3D/flood3d.gif differ diff --git a/imagepy/tools/Toolkit3D/flood3d_tol.py b/imagepy/tools/Toolkit3D/flood3d_tol.py new file mode 100644 index 00000000..2a602d8b --- /dev/null +++ b/imagepy/tools/Toolkit3D/flood3d_tol.py @@ -0,0 +1,32 @@ +from sciapp.action import ImageTool +import numpy as np +from skimage.morphology import flood_fill, flood +from sciapp.action import Filter, Simple + +class FloodFill3D(Simple): + title = 'Flood Fill 3D' + note = ['all', 'stack3d'] + + def run(self, ips, imgs, para = None): + flood_fill(imgs, para['seed'], para['color'], connectivity=para['conn'], tolerance=para['tor'], inplace=True) + +class Plugin(ImageTool): + title = 'Flood Fill 3D' + para = {'tor':10, 'con':'8-connect'} + view = [(int, 'tor', (0,1000), 0, 'torlorance', 'value'), + (list, 'con', ['4-connect', '8-connect'], str, 'fill', 'pix')] + + def mouse_down(self, ips, x, y, btn, **key): + FloodFill3D().start(self.app, {'seed':(ips.cur, int(y), int(x)), + 'color':np.mean(self.app.manager('color').get('front')), + 'conn':(self.para['con']=='8-connect')+1, 'tor':self.para['tor']}) + + def mouse_up(self, ips, x, y, btn, **key): + pass + + def mouse_move(self, ips, x, y, btn, **key): + pass + + def mouse_wheel(self, ips, x, y, d, **key): + pass + diff --git a/imagepy/tools/Transform/rotate_tol.py b/imagepy/tools/Transform/rotate_tol.py index 2e501f20..6c6b3780 100644 --- a/imagepy/tools/Transform/rotate_tol.py +++ b/imagepy/tools/Transform/rotate_tol.py @@ -1,17 +1,21 @@ import wx import numpy as np -from imagepy.core.engine import Tool, Filter +from sciapp.action import ImageTool +from sciapp.util import mark2shp, geom2shp +from sciapp.object import ROI +from sciapp.action import Filter +from shapely.affinity import affine_transform import scipy.ndimage as nimg -class RotateTool(Tool): - """RotateTool class derived from imagepy.core.engine.Tool""" +class RotateTool(ImageTool): + """RotateTool class derived from sciapp.action.Tool""" def __init__(self, plg): self.plg = plg self.para = plg.para self.moving = False def mouse_down(self, ips, x, y, btn, **key): - lim = 5.0/key['canvas'].get_scale() + lim = 5.0/key['canvas'].scale if abs(x-self.para['ox'])r1[0]:r2[0]=r1[0] - elif r2[0]+r2[2]r1[1]:r2[1]=r1[1] - elif r2[1]+r2[3]=0 and xx=0 and yy1 else '',ips.size[0], ips.size[1], - ips.imgtype, round(ips.get_nbytes()/1024.0/1024.0, 2)) - self.txt_info.SetLabel(label) - - - - if ips.get_nslices() != self.opage: - self.opage = ips.get_nslices() - if ips.get_nslices()==1 and self.page.Shown: - self.page.Hide() - resize = True - if ips.get_nslices()>1 and not self.page.Shown: - self.page.Show() - resize = True - self.page.SetScrollbar(0, 0, ips.get_nslices()-1, 0, refresh=True) - - if resize: - if IPy.uimode()!='ipy': self.Fit() - else: - #self.SetSizer(self.GetSizer()) - self.Layout() - #self.GetSizer().Layout() - if not self.handle is None: self.handle(ips, resize) - - #print('CanvasFrame:set_info') - #self.page.Show() - - def set_ips(self, ips): - self.ips = ips - self.canvas.set_ips(ips) - - def on_scroll(self, event): - self.ips.cur = self.page.GetThumbPosition() - self.ips.update = 'pix' - self.canvas.on_idle(None) - - def __del__(self):pass - -class CanvasFrame(wx.Frame): - """CanvasFrame: derived from the wx.core.Frame""" - ## TODO: Main frame ??? - def __init__(self, parent=None): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, - title = wx.EmptyString, - pos = wx.DefaultPosition, - size = wx.Size( -1,-1 ), - style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) - self.canvaspanel = CanvasPanel(self) - logopath = os.path.join(root_dir, 'data/logo.ico') - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - self.Bind(wx.EVT_ACTIVATE, self.on_valid) - self.SetAcceleratorTable(IPy.curapp.shortcut) - self.Bind(wx.EVT_CLOSE, self.on_close) - self.canvaspanel.set_handler(self.set_title) - - def set_ips(self, ips): - self.canvaspanel.set_ips(ips) - - def set_title(self, ips, resized): - title = ips.title + '' if ips.tool==None else ' [%s]'%ips.tool.title - self.SetTitle(ips.title) - if resized: self.Fit() - - def on_valid(self, event): - if event.GetActive(): - ImageManager.add(self.canvaspanel.ips) - - def on_close(self, event): - self.canvaspanel.set_handler() - self.canvaspanel.canvas.set_handler() - WindowsManager.remove(self.canvaspanel) - event.Skip() - -class CanvasNoteBook(wx.aui.AuiNotebook): - def __init__(self, parent): - wx.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, - wx.DefaultPosition, wx.DefaultSize, wx.aui.AUI_NB_DEFAULT_STYLE ) - self.Bind( wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_pagevalid) - self.Bind( wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) - - def add_page(self, panel, ips): - self.AddPage(panel, ips.title, True, wx.NullBitmap ) - panel.set_handler(lambda ips, res, pan=panel: self.set_title(ips, pan)) - - def set_title(self, ips, panel): - title = ips.title + '' if ips.tool==None else ' [%s]'%ips.tool.title - self.SetPageText(self.GetPageIndex(panel), title) - - def on_pagevalid(self, event): - ImageManager.add(event.GetEventObject().GetPage(event.GetSelection()).ips) - - def on_close(self, event): - event.GetEventObject().GetPage(event.GetSelection()).set_handler() - event.GetEventObject().GetPage(event.GetSelection()).canvas.set_handler() - WindowsManager.remove(event.GetEventObject().GetPage(event.GetSelection())) - -class VirturlCanvas: - instance = [] - class Canvas: - def __init__(self, ips): - self.ips = ips - def __del__(self): - print('virturl canvas deleted!') - - def __init__(self, ips): - self.ips = ips - self.canvas = VirturlCanvas.Canvas(ips) - VirturlCanvas.instance.append(self) - ImageManager.add(self) - - def close(self): VirturlCanvas.instance.remove(self) - - -if __name__=='__main__': - app = wx.PySimpleApp() - CanvasFrame().Show(True) - app.MainLoop() diff --git a/imagepy/ui/logwindow.py b/imagepy/ui/logwindow.py deleted file mode 100644 index 2455f8a5..00000000 --- a/imagepy/ui/logwindow.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -import wx, os -from .. import IPy, root_dir -from ..core.manager import TextLogManager - -class TextLog(wx.Frame): - """TexLog:derived from wx.core.Frame""" - @classmethod - def write(cls, cont, title='ImagePy TexLog'): - if title not in TextLogManager.windows: - win = cls(title) - win.Show() - TextLogManager.windows[title].append(cont) - - def __init__(self, title='ImagePy TexLog'): - wx.Frame.__init__(self, IPy.curapp,title=title,size=(500,300)) - logopath = os.path.join(root_dir, 'data/logo.ico') - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_3DLIGHT ) ) - self.title = title - TextLogManager.add(title, self) - self.file='' - - ### Create menus (name:event) k-v pairs - menus = [ - ## File - ('File(&F)',[('Open', self.OnOpen), - ('Save', self.OnSave), - ('Save as', self.OnSaveAs), - ('-'), - ('Exit', self.OnClose) - ]), - ## Edit - ('Edit(&E)', [ ('Undo', self.OnUndo), - ('Redo', self.OnRedo), - ('-'), - ('Cut', self.OnCut), - ('Copy', self.OnCopy), - ('Paste', self.OnPaste), - ('-'), - ('All', self.OnSelectAll) - ]), - ## Help - ('Help(&H)', [('About', self.OnAbout)]) - ] - - ### Bind menus with the corresponding events - self.menuBar=wx.MenuBar() - for menu in menus: - m = wx.Menu() - for item in menu[1]: - if item[0]=='-': - m.AppendSeparator() - else: - i = m.Append(-1, item[0]) - self.Bind(wx.EVT_MENU,item[1], i) - self.menuBar.Append(m,menu[0]) - self.SetMenuBar(self.menuBar) - self.Bind(wx.EVT_CLOSE, self.OnClosing) - - sizer = wx.BoxSizer( wx.VERTICAL ) - self.text= wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, - wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) - sizer.Add( self.text, 1, wx.ALL|wx.EXPAND, 1 ) - self.SetSizer( sizer ) - - self.Bind(wx.EVT_RIGHT_DOWN,self.OnRClick) - - def OnOpen(self,event): - dialog=wx.FileDialog(None,'wxpython Notebook(o)',style=wx.FD_OPEN) - if dialog.ShowModal()==wx.ID_OK: - self.file=dialog.GetPath() - file=open(self.file) - self.text.write(file.read()) - file.close() - dialog.Destroy() - - def OnSave(self,event): - if self.file=='': - dialog=wx.FileDialog(None,'wxpython Notebook(s)',style=wx.FD_SAVE) - if dialog.ShowModal()==wx.ID_OK: - self.file=dialog.GetPath() - self.text.SaveFile(self.file) - dialog.Destroy() - else: - self.text.SaveFile(self.file) - - def OnSaveAs(self,event): - dialog=wx.FileDialog(None,'wxpython notebook',style=wx.FD_SAVE) - if dialog.ShowModal()==wx.ID_OK: - self.file=dialog.GetPath() - self.text.SaveFile(self.file) - dialog.Destroy() - - def OnClose(self,event): - self.Destroy() - - def OnClosing(self, event): - TextLogManager.close(self.title) - event.Skip() - - def OnAbout(self,event): - wx.MessageBox('Text Log Window!','ImagePy',wx.OK) - - def OnRClick(self,event): - pos=(event.GetX(),event.GetY()) - self.panel.PopupMenu(self.menu.edit,pos) - - def OnUndo(self,event): self.text.Undo() - - def OnRedo(self,event): self.text.Redo() - - def OnCut(self,event): self.text.Cut() - - def OnCopy(self,event): self.text.Copy() - - def OnPaste(self,event): self.text.Paste() - - def OnSelectAll(self,event): self.text.SelectAll() - - def append(self, cont): - self.text.AppendText(cont+'\r\n') - -if __name__ == '__main__': - app=wx.App(False) - win = TextLog() - win.Show() - win.append('abc') - app.MainLoop() \ No newline at end of file diff --git a/imagepy/ui/mainframe.py b/imagepy/ui/mainframe.py deleted file mode 100644 index 1a23ad0d..00000000 --- a/imagepy/ui/mainframe.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Jan 14 23:23:30 2017 - -@author: yxl -""" -import wx, os, sys -import time, threading -from .. import IPy, root_dir -# TODO: @2017.05.01 -#from ui import pluginloader, toolsloader -from . import pluginloader, toolsloader, widgetsloader -from ..core.manager import ConfigManager, PluginsManager, TaskManager, ImageManager -from ..core.engine import Macros -from .canvasframe import CanvasNoteBook -from .tableframe import TableNoteBook -import wx.aui as aui - -class FileDrop(wx.FileDropTarget): - def OnDropFiles(self, x, y, path): - print(["Open>{'path':'%s'}"%i for i in path]) - Macros('noname', ["Open>{'path':'%s'}"%i.replace('\\', '/') for i in path]).start() - return 0 - -class ImagePy(wx.Frame): - def __init__( self, parent ): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImagePy', - size = wx.Size(-1,-1), pos = wx.DefaultPosition, - style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) - - self.auimgr = aui.AuiManager() - self.auimgr.SetManagedWindow( self ) - self.auimgr.SetFlags(aui.AUI_MGR_DEFAULT) - - logopath = os.path.join(root_dir, 'data/logo.ico') - #self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_3DLIGHT ) ) - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - IPy.curapp = self - self.SetSizeHints( wx.Size(900,700) if IPy.uimode() == 'ipy' else wx.Size( 600,-1 )) - - - self.menubar = pluginloader.buildMenuBarByPath(self, 'menus', 'plugins', None, True) - self.SetMenuBar( self.menubar ) - self.shortcut = pluginloader.buildShortcut(self) - self.SetAcceleratorTable(self.shortcut) - #sizer = wx.BoxSizer(wx.VERTICAL) - self.toolbar = toolsloader.build_tools(self, 'tools', 'plugins', None, True) - - - print(IPy.uimode()) - if IPy.uimode()=='ipy': self.load_aui() - else: self.load_ijui() - self.Fit() - - - self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - sizersta = wx.BoxSizer( wx.HORIZONTAL ) - self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImagePy v0.2", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.txt_info.Wrap( -1 ) - #self.txt_info.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_INFOBK ) ) - sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) - self.pro_bar = wx.Gauge( stapanel, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size( 100,15 ), wx.GA_HORIZONTAL ) - sizersta.Add( self.pro_bar, 0, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) - stapanel.SetSizer(sizersta) - stapanel.SetDropTarget(FileDrop()) - self.auimgr.AddPane( stapanel, wx.aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ) - .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) - . MinSize(wx.Size(-1, 20)). MaxSize(wx.Size(-1, 20)).Layer( 10 ) ) - - - self.Centre( wx.BOTH ) - self.Layout() - self.auimgr.Update() - self.Fit() - self.Centre( wx.BOTH ) - if(IPy.uimode()=='ij'): - self.SetMaxSize((-1, self.GetSize()[1])) - self.SetMinSize((-1, self.GetSize()[1])) - self.update = False - - self.Bind(wx.EVT_CLOSE, self.on_close) - self.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_pan_close) - thread = threading.Thread(None, self.hold, ()) - thread.setDaemon(True) - thread.start() - - def load_aui(self): - self.toolbar.GetSizer().SetOrientation(wx.VERTICAL) - self.toolbar.GetSizer().Layout() - self.toolbar.Fit() - self.auimgr.AddPane(self.toolbar, wx.aui.AuiPaneInfo() .Left() .PinButton( True ) - .CaptionVisible( True ).Dock().Resizable().FloatingSize( wx.DefaultSize ).MaxSize(wx.Size( 32,-1 )) - . BottomDockable( True ).TopDockable( False ).Layer( 10 ) ) - self.widgets = widgetsloader.build_widgets(self, 'widgets', 'plugins') - self.auimgr.AddPane( self.widgets, wx.aui.AuiPaneInfo() .Right().Caption('Widgets') .PinButton( True ) - .Dock().Resizable().FloatingSize( wx.DefaultSize ).MinSize( wx.Size( 266,-1 ) ) .Layer( 10 ) ) - - self.canvasnb = CanvasNoteBook( self) - self.auimgr.AddPane( self.canvasnb, wx.aui.AuiPaneInfo() .Center() .CaptionVisible( False ).PinButton( True ).Dock() - .PaneBorder( False ).Resizable().FloatingSize( wx.DefaultSize ). BottomDockable( True ).TopDockable( False ) - .LeftDockable( True ).RightDockable( True ) ) - - self.tablenb = TableNoteBook( self) - self.auimgr.AddPane( self.tablenb, wx.aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Dock().Hide() - .MaximizeButton( True ).Resizable().FloatingSize((800, 600)).BestSize(( 120,120 )). Caption('Tables') . - BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) - #self.canvasnb.Bind( wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_pagevalid) - - def load_ijui(self): - self.auimgr.AddPane(self.toolbar, wx.aui.AuiPaneInfo() .Top() .CaptionVisible( False ).PinButton( True ) - .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) - .BottomDockable( False ).TopDockable( False ).LeftDockable( False ).RightDockable( False ) - .MinSize(wx.Size(-1, 32)). Layer( 10 ) ) - self.widgets = widgetsloader.build_widgets(self, 'widgets', 'plugins') - self.auimgr.AddPane( self.widgets, wx.aui.AuiPaneInfo() .Right().Caption('Widgets') .PinButton( True ) - .Float().Resizable().FloatingSize( wx.DefaultSize ).MinSize( wx.Size( 266,-1 ) ).Hide() .Layer( 10 ) ) - - def load_dev(self): - return - self.devpan = wx.aui.AuiNotebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.aui.AUI_NB_DEFAULT_STYLE ) - self.auimgr.AddPane( self.devpan, wx.aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ).Dock() - .PaneBorder( False ).Resizable().FloatingSize( wx.DefaultSize ) ) - - def on_pan_close(self, event): - return - if event.GetPane().window in [self.toolbar, self.widgets]: - event.Veto() - event.GetPane().Show(False) - self.auimgr.Update() - - def reload_plugins(self, report=False, menus=True, tools=False, widgets=False): - print(menus, tools, widgets) - if menus: pluginloader.buildMenuBarByPath(self, 'menus', 'plugins', self.menubar, report) - if tools: toolsloader.build_tools(self, 'tools', 'plugins', self.toolbar, report) - if widgets: widgetsloader.build_widgets(self, 'widgets', 'plugins', self.widgets) - if IPy.uimode()!='ipy': self.Fit() - - def hold(self): - dire = 1 - while True: - try: - if time == None: break - time.sleep(0.05) - tasks = TaskManager.get() - if(len(tasks)==0): - if self.pro_bar.IsShown(): - wx.CallAfter(self.set_progress, -1) - continue - arr = [i.prgs for i in tasks] - if (None, 1) in arr: - if self.pro_bar.GetValue()<=0: - dire = 1 - if self.pro_bar.GetValue()>=100: - dire = -1 - v = self.pro_bar.GetValue()+dire*5 - wx.CallAfter(self.set_progress, v) - else: - v = max([(i[0]+1)*100.0/i[1] for i in arr]) - wx.CallAfter(self.set_progress, v) - except: - pass - def set_info(self, value): - self.txt_info.SetLabel(value) - - def set_progress(self, value): - v = max(min(value, 100), 0) - self.pro_bar.SetValue(v) - if value==-1: - self.pro_bar.Hide() - elif not self.pro_bar.IsShown(): - self.pro_bar.Show() - self.stapanel.GetSizer().Layout() - self.pro_bar.Update() - - def set_color(self, value): - self.line_color.SetBackgroundColour(value) - - def on_close(self, event): - ConfigManager.write() - self.auimgr.UnInit() - del self.auimgr - self.Destroy() - sys.exit() - - def __del__( self ): - pass - -if __name__ == '__main__': - app = wx.App(False) - mainFrame = ImagePy(None) - mainFrame.Show() - app.MainLoop() diff --git a/imagepy/ui/panelconfig.py b/imagepy/ui/panelconfig.py deleted file mode 100644 index 1268c507..00000000 --- a/imagepy/ui/panelconfig.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -# ConfigPanel used for parameters setting -import wx, platform -from ..core.manager import ImageManager, WindowsManager, TableManager -from .widgets import * - -widgets = { 'ctrl':None, 'slide':FloatSlider, int:NumCtrl, - float:NumCtrl, 'lab':Label, bool:Check, str:TextCtrl, - list:Choice, 'img':ImageList, 'color':ColorCtrl, - 'any':AnyType, 'chos':Choices, 'fields':TableFields, - 'hist':HistCanvas} - -class ParaDialog (wx.Dialog): - def __init__( self, parent, title): - wx.Dialog.__init__ (self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE) - self.lst = wx.BoxSizer( wx.VERTICAL ) - self.tus = [] - - self.on_ok, self.on_cancel = None, None - self.ctrl_dic = {} - boxBack = wx.BoxSizer() - boxBack.Add(self.lst, 0, wx.ALL, 10) - self.SetSizer( boxBack ) - self.Layout() - self.handle = self.handle_ - - def commit(self, state): - self.Destroy() - if state=='ok' and self.on_ok:self.on_ok() - if state=='cancel' and self.on_cancel:self.on_cancel() - - def add_confirm(self, modal=True): - self.lst.AddStretchSpacer(1) - sizer = wx.BoxSizer( wx.HORIZONTAL ) - self.btn_OK = wx.Button( self, wx.ID_OK, 'OK') - sizer.Add( self.btn_OK, 0, wx.ALIGN_RIGHT|wx.ALL, 5 ) - - self.btn_cancel = wx.Button( self, wx.ID_CANCEL, 'Cancel') - sizer.Add( self.btn_cancel, 0, wx.ALIGN_RIGHT|wx.ALL, 5 ) - self.lst.Add(sizer, 0, wx.ALIGN_RIGHT, 5 ) - if not modal: - self.btn_OK.Bind( wx.EVT_BUTTON, lambda e:self.commit('ok')) - self.btn_cancel.Bind( wx.EVT_BUTTON, lambda e:self.commit('cancel')) - - def init_view(self, items, para, preview=False, modal = True): - self.para = para - for item in items: - self.add_ctrl_(widgets[item[0]], item[1], item[2:]) - if preview:self.add_ctrl_(Check, 'preview', ('preview',)) - self.reset(para) - self.add_confirm(modal) - self.pack() - - def parse(self, para) : - self.add_ctrl_(widgets[para[0]], *para[1:]) - #self.funcs[para[0]](*para[1:]) - ''' - def add_ctrl(self, key, ctrl): - self.lst.Add( ctrl, 0, wx.EXPAND, 5 ) - if not key is None: - self.ctrl_dic[key] = ctrl - if hasattr(ctrl, 'set_handle'): - ctrl.set_handle(lambda x=None : self.para_changed(key)) - ''' - - def add_ctrl_(self, Ctrl, key, p): - ctrl = Ctrl(self, *p) - if not p[0] is None: self.ctrl_dic[key] = ctrl - if hasattr(ctrl, 'Bind'): - ctrl.Bind(None, lambda x : self.para_changed(key)) - pre = ctrl.prefix if hasattr(ctrl, 'prefix') else None - post = ctrl.postfix if hasattr(ctrl, 'postfix') else None - self.tus.append((pre, post)) - self.lst.Add( ctrl, 0, wx.EXPAND, 0 ) - - def pack(self): - self.Layout() - mint, minu = [], [] - for t,u in self.tus: - if not t is None: mint.append(t.GetSize()[0]) - if not u is None:minu.append(u.GetSize()[0]) - for t,u in self.tus: - if not t is None:t.SetInitialSize((max(mint),-1)) - if not u is None:u.SetInitialSize((max(minu),-1)) - self.Fit() - - def para_check(self, para, key):pass - - def para_changed(self, key): - - para = self.para - for p in list(para.keys()): - if p in self.ctrl_dic: - para[p] = self.ctrl_dic[p].GetValue() - - sta = sum([i is None for i in list(para.values())])==0 - self.btn_OK.Enable(sta) - if not sta: return - self.para_check(para, key) - if 'preview' not in self.ctrl_dic:return - if not self.ctrl_dic['preview'].GetValue():return - self.handle(para) - - def reset(self, para=None): - if para!=None:self.para = para - #print(para, '====') - for p in list(self.para.keys()): - if p in self.ctrl_dic: - self.ctrl_dic[p].SetValue(self.para[p]) - - def get_para(self): - return self.para - - def set_handle(self, handle): - self.handle = handle - if handle==None: self.handle = self.handle_ - - def handle_(self, para): - print(para) - - def __del__( self ): - pass - -if __name__ == '__main__': - view = [(float, 'r', (0,20), 1, '半径', 'mm'), - ('slide', 'mm', (-20,20), '亮度', 'slide'), - ('color', 'color', '颜色', 'rgb'), - (bool, 'preview', 'preview')] - data = {'r':1.2, 'slide':0, 'preview':True, 'color':(0,255,0)} - - app = wx.PySimpleApp() - pd = ParaDialog(None, 'Test') - pd.init_view(view, para) - pd.pack() - pd.ShowModal() - app.MainLoop() \ No newline at end of file diff --git a/imagepy/ui/plotwindow.py b/imagepy/ui/plotwindow.py deleted file mode 100644 index e71d6286..00000000 --- a/imagepy/ui/plotwindow.py +++ /dev/null @@ -1,210 +0,0 @@ -# -*- coding: utf-8 -*- -import os,wx -from .. import IPy, root_dir -import numpy as np -from math import ceil -from ..core.manager import PlotManager - -class LineCanvas(wx.Panel): - """LineCanvas: derived from wx.core.Panel""" - def __init__(self, parent): - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, - pos = wx.DefaultPosition, size = wx.Size(256,80), - style = wx.SIMPLE_BORDER|wx.TAB_TRAVERSAL ) - self.init_buf() - self.data, self.extent = [], [0,0,1,1] - self.set_title_label('Graph', 'X-unit', 'Y-unit') - self.update = False - - self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) ) - self.Bind(wx.EVT_SIZE, self.on_size) - self.Bind(wx.EVT_PAINT, self.on_paint) - self.Bind(wx.EVT_IDLE, self.on_idle) - self.Bind(wx.EVT_MOTION, self.on_move ) - - def init_buf(self): - box = self.GetClientSize() - self.width, self.height = box.width, box.height - self.buffer = wx.Bitmap(self.width, self.height) - - def on_size(self, event): - self.init_buf() - self.draw() - - def on_paint(self, event): - wx.BufferedPaintDC(self, self.buffer) - - def trans(self, x, y): - l, t, r, b = 35,35,15,35 - w = self.width - l - r - h = self.height - t - b - left, low, right, high = self.extent - x = (x-l)*1.0/w*(right-left)+left - y = (t+h-y)*1.0/(h)*(high-low)+low - return x, y - - def clear(self): - del self.data[:] - - def on_move(self, event): - self.handle_move(*self.trans(event.x, event.y)) - - def handle_move(self, x, y):pass - - def on_idle(self, event): - if self.update == True: - self.draw() - self.update = False - - def set_title_label(self, title, labelx, labely): - self.title, self.labelx, self.labely = title, labelx, labely - - def paint(self): - if len(self.data)==0 : - return - ext = np.array([[x.min(), y.min(), x.max(), y.max()] for x,y,c,w in self.data]) - d = ext[:,3].max() - ext[:,1].min() - top, bot = ext[:,3].max() + 0.1 * d, ext[:,1].min() - 0.1 * d - if top == bot: top, bot = top+1, bot-1 - self.extent = [ext[:,0].min(), bot, ext[:,2].max(), top] - self.update = True - - def add_data(self, xs, ys=None, color=(0,0,255), lw=2): - if ys is None: - ys, xs = xs, np.arange(len(xs)) - self.data.append((xs, ys, color, lw)) - - def draw_coord(self, dc, w, h, l, t, r, b): - xs = [5, 10, 20, 40, 50, 100, 200, 400, 500, 1000, 2000, 4000, 10000] - n, dx, dy = len(self.data), 0, 0 - left, low, right, high = self.extent - for i in xs[::-1]: - if (right-left)*1.0/i<=10:dx=i - for i in xs[::-1]: - if (high-low)*1.0/i<=10:dy=i - dc.SetPen(wx.Pen((0, 0, 0), width=1, style=wx.SOLID)) - dc.DrawRectangle(l, t, w+1, h+1) - dc.SetPen(wx.Pen((100, 100, 100), width=1, style=wx.SOLID)) - for i in range(int(ceil(left*1.0/dx)*dx), int(right)+1, dx): - x = l+(i-left)*1.0/(right-left)*w - dc.DrawLine(x, t, x, t+h) - dc.DrawText(str(i), x-5, t+h) - for i in range(int(ceil(low*1.0/dy)*dy), int(high)+1, dy): - y = h+t-(i-low)*1.0/(high-low)*h - dc.DrawLine(l, y, l+w, y) - dc.DrawText(str(i), 5, y-5) - - titlefont = wx.Font(18, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - dc.SetFont(titlefont) - dw,dh = dc.GetTextExtent(self.title) - dc.DrawText(self.title, l+w/2-dw/2, 3) - - lablelfont = wx.Font(14, wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) - dc.SetFont(lablelfont) - dw,dh = dc.GetTextExtent(self.labelx) - dc.DrawText(self.labelx, l+w-dw, t+h+15) - dc.DrawText(self.labely, 5, 10) - - def draw(self): - l, t, r, b = 35,35,15,35 - w = self.width - l - r - h = self.height - t - b - if self.data is None:return - dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) - dc.Clear() - - left, low, right, high = self.extent - self.draw_coord(dc, w, h, l, t, r, b) - for xs, ys, c, lw in self.data: - ys = h+t - (ys - low)*(h/(high-low)) - xs = l+(xs-left)*(1.0/(right-left)*w) - pts = list(zip(xs, ys)) - dc.SetPen(wx.Pen(c, width=lw, style=wx.SOLID)) - dc.DrawLines(pts) - - def save(self, path): - self.buffer.SaveFile(path, wx.BITMAP_TYPE_PNG) - - -class PlotFrame ( wx.Frame ): - """PlotFrame:derived from wx.core.Frame""" - frms = {} - - @classmethod - def get_frame(cls, title, gtitle='Graph', labelx='X-Unit', labely='Y-Unit'): - if PlotManager.get(title) == None: - PlotManager.add(title, cls(IPy.curapp, title)) - PlotManager.get(title).set_title_label(gtitle, labelx, labely) - return PlotManager.get(title) - - def __init__( self, parent, title): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, - title = title, pos = wx.DefaultPosition, - size = wx.Size( 500,300 ) ) - logopath = os.path.join(root_dir, 'data/logo.ico') - self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_3DLIGHT ) ) - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) - sizer = wx.BoxSizer( wx.VERTICAL ) - self.canvas = LineCanvas( self) - #self.canvas.set_data(np.random.rand(256)*100) - #self.canvas.set_lim(0, 0) - sizer.Add( self.canvas, 1, wx.EXPAND |wx.ALL, 5 ) - sizer2 = wx.BoxSizer( wx.HORIZONTAL ) - self.lab_info = wx.StaticText( self, wx.ID_ANY, "Information", - wx.DefaultPosition, wx.DefaultSize, 0 ) - self.lab_info.Wrap( -1 ) - sizer2.Add( self.lab_info, 0, wx.ALL, 5 ) - sizer2.AddStretchSpacer(1) - self.btn_save = wx.Button( self, wx.ID_ANY, "Save", - wx.DefaultPosition, wx.DefaultSize, 0 ) - sizer2.Add( self.btn_save, 0, wx.ALL, 5 ) - self.btn_cancel = wx.Button( self, wx.ID_ANY, "Cancel", - wx.DefaultPosition, wx.DefaultSize, 0 ) - sizer2.Add( self.btn_cancel, 0, wx.ALL, 5 ) - sizer.Add( sizer2, 0, wx.ALL|wx.EXPAND, 5 ) - self.SetSizer( sizer ) - self.Layout() - self.Centre( wx.BOTH ) - - self.canvas.handle_move = self.handle_move - self.Bind(wx.EVT_CLOSE, self.on_closing) - self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel) - self.btn_save.Bind(wx.EVT_BUTTON, self.on_save) - - self.set_title_label = self.canvas.set_title_label - self.add_data = self.canvas.add_data - self.clear = self.canvas.clear - - def draw(self): - self.canvas.paint() - self.Show() - - def handle_move(self, x, y): - self.lab_info.SetLabel('X = %.1f, Y = %.1f' %(x, y)) - - def on_cancel(self, event): - self.Close() - - def on_save(self, event): - para = {'path':'./'} - filt = 'PNG files (*.png)|*.png' - IPy.getpath('Save..', filt, 'save', para) - self.canvas.save(para['path']) - - def on_closing(self, event): - PlotManager.remove(self.GetTitle()) - event.Skip() - -if __name__ == '__main__': - app = wx.App(False) - xs = np.linspace(10,20,50) - ys = np.sin(xs)+100 - - plotframe = PlotFrame.get_frame('first', 'Histogram', 'Line length', 'value of pix') - plotframe.add_data(xs, ys, (0,0,255), 1) - plotframe.draw() - plotframe.draw() - app.MainLoop() \ No newline at end of file diff --git a/imagepy/ui/pluginloader.py b/imagepy/ui/pluginloader.py deleted file mode 100644 index af691efe..00000000 --- a/imagepy/ui/pluginloader.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -* -import wx -from ..core.loader import loader -from ..core.manager import ShotcutManager, PluginsManager, LanguageManager -from glob import glob - -def buildItem(parent, root, item): - if item=='-': - root.AppendSeparator() - return - sc = ShotcutManager.get(item.title) - LanguageManager.add(item.title) - - title = LanguageManager.get(item.title) if sc==None else LanguageManager.get(item.title)+'\t'+sc - - mi = wx.MenuItem(root, -1, title) - parent.Bind(wx.EVT_MENU, lambda x, p=item:p().start(), mi) - root.Append(mi) - -def buildMenu(parent, data, curpath): - menu = wx.Menu() - for item in data[1]: - if isinstance(item, tuple): - ## TODO: fixed by auss - nextpath = curpath + '.' + item[0].title - #print(nextpath) - LanguageManager.add(item[0].title) - menu.Append(-1, LanguageManager.get(item[0].title), buildMenu(parent, item,nextpath)) - else: - buildItem(parent, menu, item) - return menu - -def buildMenuBar(parent, datas, menuBar=None): - if menuBar==None: - menuBar = wx.MenuBar() - for data in datas[1]: - if len(data[1]) == 0: - continue - LanguageManager.add(data[0].title) - menuBar.Append(buildMenu(parent, data, data[0].title), LanguageManager.get(data[0].title)) - return menuBar - -#!ToDO: tongguo lujing goujian menu -def buildMenuBarByPath(parent, path, extends, menuBar=None, report=False): - datas = loader.build_plugins(path, report) - keydata = {} - for i in datas[1]: - if isinstance(i, tuple): keydata[i[0].__name__.split('.')[-1]] = i[1] - #print(keydata) - extends = glob(extends+'/*/menus') - for i in extends: - plgs = loader.build_plugins(i, report) - for j in plgs[1]: - if not isinstance(j, tuple): continue - name = j[0].__name__.split('.')[-1] - if name in keydata: - keydata[name].extend(j[1]) - else: datas[1].append(j) - #if len(wgts)!=0: datas[1].extend(wgts[1]) - # print(datas) - if not menuBar is None:menuBar.SetMenus([]) - return buildMenuBar(parent, datas, menuBar) - -def codeSplit(txt): - sep = txt.split('-') - acc, code = wx.ACCEL_NORMAL, -1 - if 'Ctrl' in sep: acc|= wx.ACCEL_CTRL - if 'Alt' in sep: acc|= wx.ACCEL_ALT - if 'Shift' in sep: acc|= wx.ACCEL_SHIFT - fs = ['F{}'.format(i) for i in range(1,13)] - if sep[-1] in fs: - code = 340+fs.index(sep[-1]) - elif len(sep[-1])==1: code = ord(sep[-1]) - return acc, code - -def buildShortcut(parent): - shortcuts = [] - for item in list(PluginsManager.plgs.values()): - cut = ShotcutManager.get(item.title) - if cut!=None: - acc, code = codeSplit(cut) - if code==-1: continue; - nid = wx.NewId() - parent.Bind(wx.EVT_MENU, lambda x, p=item:p().start(), id=nid) - shortcuts.append((acc, code, nid)) - return wx.AcceleratorTable(shortcuts) diff --git a/imagepy/ui/tableframe.py b/imagepy/ui/tableframe.py deleted file mode 100644 index 573fcfdf..00000000 --- a/imagepy/ui/tableframe.py +++ /dev/null @@ -1,98 +0,0 @@ -from .tablewindow import * -import wx.aui as aui - -class TablePanel ( wx.Panel ): - def __init__( self, parent): - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 819,488 ), style = wx.TAB_TRAVERSAL ) - self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) ) - WTableManager.add(self) - - bSizer = wx.BoxSizer( wx.VERTICAL ) - - self.lab_info = wx.StaticText( self, wx.ID_ANY, 'MyLabel asdfasfa ', wx.DefaultPosition, wx.DefaultSize, 0 ) - self.lab_info.Wrap( -1 ) - bSizer.Add( self.lab_info, 0, wx.ALL|wx.EXPAND, 0 ) - self.grid = GridBase( self) - self.grid.set_handler(self.set_info) - bSizer.Add( self.grid, 1, wx.ALL|wx.EXPAND, 0 ) - - - self.SetSizer( bSizer ) - self.Layout() - - self.handle = None - - def set_handler(self, handle=None): - self.handle = handle - - def set_tps(self, tps): - self.tps = tps - self.grid.set_tps(tps) - - def __del__( self ): - print('Table Panel Del') - - def set_info(self, tps): - self.lab_info.SetLabel('%sx%s; %.2fK'%(tps.data.shape+(tps.get_nbytes()/1024.0,))) - if not self.handle is None: self.handle(tps) - - def on_test(self, event): - print(self.grid.GetSelectedCols(), self.grid.GetSelectedRows()) - print(self.grid.GetSelectionBlockTopLeft(), self.grid.GetSelectionBlockBottomRight()) - -class TableFrame(wx.Frame): - """CanvasFrame: derived from the wx.core.Frame""" - ## TODO: Main frame ??? - def __init__(self, parent=None): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, - title = wx.EmptyString, - pos = wx.DefaultPosition, - size = wx.Size( -1,-1 ), - style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) - self.tablepanel = TablePanel(self) - logopath = os.path.join(root_dir, 'data/logo.ico') - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - self.Bind(wx.EVT_ACTIVATE, self.on_valid) - #self.SetAcceleratorTable(IPy.curapp.shortcut) - self.Bind(wx.EVT_CLOSE, self.on_close) - self.tablepanel.set_handler(self.set_title) - #self.canvaspanel.set_handler(self.set_title) - - def set_tps(self, tps): - self.tablepanel.set_tps(tps) - - def set_title(self, tps): - self.SetTitle(tps.title) - - def on_valid(self, event): - if event.GetActive(): - TableManager.add(self.tablepanel.tps) - - def on_close(self, event): - self.tablepanel.set_handler() - self.tablepanel.grid.set_handler() - WTableManager.remove(self.tablepanel) - event.Skip() - -class TableNoteBook(wx.aui.AuiNotebook): - def __init__(self, parent): - wx.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, - wx.DefaultPosition, wx.DefaultSize, wx.aui.AUI_NB_DEFAULT_STYLE ) - self.Bind( wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_pagevalid) - self.Bind( wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) - - def add_page(self, panel, tps): - self.AddPage(panel, tps.title, True, wx.NullBitmap ) - panel.set_handler(lambda tps, pan=panel: self.set_title(tps, pan)) - - def set_title(self, tps, panel): - title = tps.title - self.SetPageText(self.GetPageIndex(panel), title) - - def on_pagevalid(self, event): - TableManager.add(event.GetEventObject().GetPage(event.GetSelection()).tps) - - def on_close(self, event): - event.GetEventObject().GetPage(event.GetSelection()).set_handler() - event.GetEventObject().GetPage(event.GetSelection()).grid.set_handler() - WTableManager.remove(event.GetEventObject().GetPage(event.GetSelection())) \ No newline at end of file diff --git a/imagepy/ui/toolsloader.py b/imagepy/ui/toolsloader.py deleted file mode 100644 index 745efb2e..00000000 --- a/imagepy/ui/toolsloader.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -* -# load and build the toolbar -import wx -import os -from .. import IPy -from .. import root_dir -from ..core.engine import Tool, Macros -from ..core.loader import loader -from imagepy.core.manager import ConfigManager -from glob import glob - -def make_bitmap(bmp): - img = bmp.ConvertToImage() - img.Resize((20, 20), (2, 2)) - return img.ConvertToBitmap() - -def build_tools(parent, toolspath, extends, bar=None, report=False): - global host - host = parent - ## get tool datas from the loader.build_tools(toolspath) - ## then generate toolsbar - datas = loader.build_tools(toolspath, report) - extends = glob(extends+'/*/tools') - for i in extends: - tols = loader.build_tools(i, report) - if len(tols)!=0: datas[1].extend(tols[1]) - for i in datas[1]: - if i[0].title == ConfigManager.get('tools'): - datas[1].remove(i) - datas[1].insert(1, i) - toolsbar = buildToolsBar(parent, datas, bar) - #gifpath = os.path.join(root_dir, "tools/drop.gif") - #btn = wx.BitmapButton(parent, wx.ID_ANY, wx.Bitmap(gifpath), wx.DefaultPosition, (30,30), wx.BU_AUTODRAW) - #btn.Bind(wx.EVT_LEFT_DOWN, lambda x:menu_drop(parent, toolsbar, datas, btn, x)) - return toolsbar#, btn - -def buildToolsBar(parent, datas, toolsbar=None): - #if not toolsbar is None:toolsbar.BeginRepositioningChildren() - #toolsbar = wx.ToolBar( parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TB_HORIZONTAL ) - - if toolsbar is None: - box = wx.BoxSizer( wx.HORIZONTAL ) - toolsbar = wx.Panel( parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - toolsbar.SetSizer( box ) - else: - box = toolsbar.GetSizer() - toolsbar.DestroyChildren() - box.Clear() - - - add_tools(toolsbar, datas[1][0][1], clear=True) - - gifpath = os.path.join(root_dir, "tools/drop.gif") - btn = wx.BitmapButton(toolsbar, wx.ID_ANY, make_bitmap(wx.Bitmap(gifpath)), - wx.DefaultPosition, (32, 32), wx.BU_AUTODRAW|wx.RAISED_BORDER) - sp = wx.StaticLine( toolsbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL ) - box.Add( sp, 0, wx.ALL|wx.EXPAND, 2 ) - box.AddStretchSpacer(1) - box.Add(btn) - btn.Bind(wx.EVT_LEFT_DOWN, lambda x, ds=datas, b=btn:menu_drop(parent, toolsbar, ds, b, x)) - add_tools(toolsbar, datas[1][1][1]) - - toolsbar.GetSizer().Layout() - #toolsbar.Fit() - - return toolsbar - -def menu_drop(parent, toolbar, datas, btn, e): - menu = wx.Menu() - for data in datas[1][1:]: - item = wx.MenuItem(menu, wx.ID_ANY, data[0].title, wx.EmptyString, wx.ITEM_NORMAL ) - - menu.Append(item) - parent.Bind(wx.EVT_MENU, lambda x,p=data[1]:add_tools(toolbar, p), item) - parent.PopupMenu( menu ) - menu.Destroy() - -def f(plg, e): - ##! TODO: What's this? for wx.EVT_LEFT_DOWN - plg.start() - #print e.GetEventObject().SetBackgroundColour( - # wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) - if isinstance(plg, Tool): - e.Skip() - -def set_info(value): - IPy.curapp.set_info(value) - -def setting(tol, btn): - if not hasattr(tol, 'view'):return - if isinstance(tol.view, list): - para = dict(tol.para) - rst = IPy.getpara(tol.title, tol.view, para) - if rst!=None: tol.para = rst - else: - tol().view(btn) - -def add_tools(bar, datas, clear=False, curids=[]): - box = bar.GetSizer() - if not clear: - for curid in curids: - #bar.RemoveChild(curid) - #box.Hide(curid) - curid.Destroy() - #box.Detach(curid) - del curids[:] - - for data in datas: - btn = wx.BitmapButton(bar, wx.ID_ANY, make_bitmap(wx.Bitmap(data[1])), - wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER ) - if not clear:curids.append(btn) - if clear: box.Add(btn) - else: - box.Insert(len(box.GetChildren())-2, btn) - - btn.Bind( wx.EVT_LEFT_DOWN, lambda x, p=data[0]:f(p(), x)) - btn.Bind( wx.EVT_ENTER_WINDOW, - lambda x, p='"{}" Tool'.format(data[0].title): set_info(p)) - if not isinstance(data[0], Macros) and issubclass(data[0], Tool): - btn.Bind(wx.EVT_LEFT_DCLICK, lambda x, p=data[0]:p().show()) - btn.SetDefault() - box.Layout() - bar.Refresh() \ No newline at end of file diff --git a/imagepy/ui/widgets/__init__.py b/imagepy/ui/widgets/__init__.py deleted file mode 100644 index 5bf24171..00000000 --- a/imagepy/ui/widgets/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .curvepanel import CurvePanel -from .histpanel import HistCanvas -from .cmappanel import CMapPanel -from .normal import * -from .advanced import * -from .cmapselect import CMapSelPanel -from .viewport import ViewPort \ No newline at end of file diff --git a/imagepy/ui/widgets/advanced.py b/imagepy/ui/widgets/advanced.py deleted file mode 100644 index 42dea12e..00000000 --- a/imagepy/ui/widgets/advanced.py +++ /dev/null @@ -1,14 +0,0 @@ -from . normal import Choice, Choices -from ...core.manager import ImageManager, TableManager - -class ImageList(Choice): - def __init__(self, parent, title, unit): - Choice.__init__(self, parent, ImageManager.get_titles(), str, title, unit) - -class TableFields(Choices): - def __init__(self, parent, title): - self.tps = TableManager.get() - Choices.__init__(self, parent, self.tps.data.columns, title) - - def SetValue(self, value): - Choices.SetValue(self, self.tps.colmsk) \ No newline at end of file diff --git a/imagepy/ui/widgets/histpanel.py b/imagepy/ui/widgets/histpanel.py deleted file mode 100644 index 68ee91e7..00000000 --- a/imagepy/ui/widgets/histpanel.py +++ /dev/null @@ -1,58 +0,0 @@ -import wx -import numpy as np - -class HistCanvas(wx.Panel): - """ HistCanvas: diverid from wx.core.Panel """ - def __init__(self, parent, hist=None): - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, - pos = wx.DefaultPosition, size = wx.Size(256,81), - style = wx.TAB_TRAVERSAL ) - self.init_buf() - self.hist = None - if not hist is None: self.SetValue(hist) - self.update = False - self.x1, self.x2 = 0, 255 - self.Bind(wx.EVT_SIZE, self.on_size) - self.Bind(wx.EVT_IDLE, self.on_idle) - self.Bind(wx.EVT_PAINT, self.on_paint) - self.Bind = lambda z, x:0 - - def init_buf(self): - box = self.GetClientSize() - self.buffer = wx.Bitmap(box.width, box.height) - - def on_size(self, event): - self.init_buf() - self.update = True - - def on_idle(self, event): - if self.update == True: - self.draw() - self.update = False - - def on_paint(self, event): - wx.BufferedPaintDC(self, self.buffer) - - def SetValue(self, hist): - self.hist = (hist*80/hist.max()).astype(np.uint8) - self.update = True - - def set_lim(self, x1, x2): - self.x1, self.x2 = x1, x2 - self.update = True - - def draw(self): - # get client device context buffer - dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) - dc.Clear() - # w, h = self.GetClientSize() - - # the main draw process - print("drawing histogram") - dc.SetPen(wx.Pen((100,100,100), width=1, style=wx.SOLID)) - if not self.hist is None: - for i in range(256): - dc.DrawLine(i,80,i,80-self.hist[i]) - dc.SetPen(wx.Pen((0,0,0), width=1, style=wx.SOLID)) - dc.DrawLine(self.x1, 80, self.x2, 0) - dc.DrawLines([(0,0),(255,0),(255,80),(0,80),(0,0)]) \ No newline at end of file diff --git a/imagepy/ui/widgetsloader.py b/imagepy/ui/widgetsloader.py deleted file mode 100644 index d75cfe7f..00000000 --- a/imagepy/ui/widgetsloader.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -* -# load and build the toolbar -import wx -import numpy as np -from ..core.loader import loader -from glob import glob - -def build_widget(parent, datas): - for i in datas[1]: - parent.AddPage(i(parent), i.title, False ) - - -def build_widgets_panel(parent, datas, wpanel): - if wpanel is None: - wpanel = wx.ScrolledWindow( parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.HSCROLL|wx.VSCROLL ) - else: wpanel.DestroyChildren() - wpanel.SetScrollRate( 5, 5 ) - sizer = wx.BoxSizer( wx.VERTICAL ) - for i in datas[1]: - choicebook = wx.Choicebook( wpanel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.CHB_DEFAULT ) - build_widget(choicebook, i) - sizer.Add( choicebook, 0, wx.EXPAND |wx.ALL, 0 ) - wpanel.SetSizer( sizer ) - wpanel.Layout() - sizer.Fit( wpanel ) - return wpanel - -def build_widgets(parent, toolspath, extends, panel=None): - datas = loader.build_widgets(toolspath) - extends = glob(extends+'/*/widgets') - for i in extends: - wgts = loader.build_widgets(i) - if len(wgts)!=0: datas[1].extend(wgts[1]) - return build_widgets_panel(parent, datas, panel) \ No newline at end of file diff --git a/imagepy/widgets/histogram/channels_wgt.py b/imagepy/widgets/histogram/channels_wgt.py new file mode 100644 index 00000000..5851dd95 --- /dev/null +++ b/imagepy/widgets/histogram/channels_wgt.py @@ -0,0 +1 @@ +from sciwx.plugins.channels import Channels as Plugin \ No newline at end of file diff --git a/imagepy/widgets/histogram/curve_wgt.py b/imagepy/widgets/histogram/curve_wgt.py index 634dc6df..b2823628 100644 --- a/imagepy/widgets/histogram/curve_wgt.py +++ b/imagepy/widgets/histogram/curve_wgt.py @@ -1,94 +1 @@ -from ...ui.widgets import CurvePanel -from imagepy.core.manager import ColorManager -from imagepy import IPy -import numpy as np, wx - -class Plugin(wx.Panel): - title = 'Curve Adjust' - def __init__(self, parent): - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 255,0 ), style = wx.TAB_TRAVERSAL ) - - bSizer1 = wx.BoxSizer( wx.VERTICAL ) - - self.curvepan = CurvePanel(self, l=240) - bSizer1.Add(self.curvepan, 0, wx.ALL|wx.EXPAND, 0 ) - - - bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) - self.btn_apply = wx.Button( self, wx.ID_ANY, u"apply", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_apply.SetMaxSize( wx.Size( -1,40 ) ) - - bSizer2.Add( self.btn_apply, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_clear = wx.Button( self, wx.ID_ANY, u"clear", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_clear.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_clear, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_reset = wx.Button( self, wx.ID_ANY, u"reset", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_reset.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_reset, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_invert = wx.Button( self, wx.ID_ANY, u"invert", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_invert.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_invert, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer1.Add( bSizer2, 0, wx.EXPAND |wx.ALL, 5 ) - - - self.SetSizer( bSizer1 ) - self.Layout() - - self.curvepan.Bind(None, self.handle) - self.btn_apply.Bind( wx.EVT_BUTTON, self.on_apply ) - self.btn_clear.Bind( wx.EVT_BUTTON, self.on_clear ) - self.btn_reset.Bind( wx.EVT_BUTTON, self.on_reset ) - self.btn_invert.Bind( wx.EVT_BUTTON, self.on_invert ) - - def handle(self, event): - ips = IPy.get_ips() - if ips is None:return - lut = CurvePanel.lookup(self.curvepan.pts) - lut = np.vstack((lut,lut,lut)).T - ips.lut = lut - ips.update = 'pix' - - def on_apply(self, event): - ips = IPy.get_ips() - if ips is None:return - hist = ips.histogram() - self.curvepan.set_hist(hist) - self.handle() - - def on_clear(self, event): - ips = IPy.get_ips() - if ips is None:return - hist = ips.histogram() - self.curvepan.set_hist(hist) - ips.lut = ColorManager.get_lut() - ips.update = 'pix' - - def on_reset(self, event): - self.curvepan.SetValue() - ips = IPy.get_ips() - if ips is None:return - hist = ips.histogram() - self.curvepan.set_hist(hist) - self.handle() - - def on_invert(self, event): - self.curvepan.SetValue([(0,255),(255,0)]) - ips = IPy.get_ips() - if ips is None:return - hist = ips.histogram() - self.curvepan.set_hist(hist) - self.handle() \ No newline at end of file +from sciwx.plugins.curve import Curve as Plugin \ No newline at end of file diff --git a/imagepy/widgets/histogram/histogram_wgt.py b/imagepy/widgets/histogram/histogram_wgt.py index a23190ae..699ee663 100644 --- a/imagepy/widgets/histogram/histogram_wgt.py +++ b/imagepy/widgets/histogram/histogram_wgt.py @@ -1,159 +1 @@ -from ...ui.widgets import HistCanvas, CMapPanel, CMapSelPanel, FloatSlider -from imagepy.core.manager import ColorManager -from imagepy import IPy -import numpy as np -import wx - -class Plugin( wx.Panel ): - title = 'Histogram' - def __init__( self, parent ): - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 255,0 ), style = wx.TAB_TRAVERSAL ) - - bSizer1 = wx.BoxSizer( wx.VERTICAL ) - - self.histpan = HistCanvas(self) - bSizer1.Add(self.histpan, 0, wx.ALL|wx.EXPAND, 5 ) - - self.sli_high = FloatSlider(self, (0,255), 0, '') - self.sli_high.SetValue(255) - bSizer1.Add( self.sli_high, 0, wx.ALL|wx.EXPAND, 0 ) - - self.sli_low = FloatSlider(self, (0,255), 0, '') - self.sli_low.SetValue(0) - bSizer1.Add( self.sli_low, 0, wx.ALL|wx.EXPAND, 0 ) - - - bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) - self.btn_8bit = wx.Button( self, wx.ID_ANY, u"0-255", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_8bit.SetMaxSize( wx.Size( -1,40 ) ) - - bSizer2.Add( self.btn_8bit, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_minmax = wx.Button( self, wx.ID_ANY, u"min-max", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_minmax.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_minmax, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_slice = wx.Button( self, wx.ID_ANY, u"slice", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_slice.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_slice, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.btn_stack = wx.Button( self, wx.ID_ANY, u"stack", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) - self.btn_stack.SetMaxSize( wx.Size( -1,40 ) ) - bSizer2.Add( self.btn_stack, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) - - - bSizer1.Add( bSizer2, 0, wx.EXPAND |wx.ALL, 5 ) - - #line = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - #txtlut = wx.StaticText( self, wx.ID_ANY, 'Look Up Table', wx.DefaultPosition, wx.DefaultSize) - #bSizer1.Add( line, 0, wx.EXPAND |wx.ALL, 5 ) - #bSizer1.Add( txtlut, 0, wx.EXPAND |wx.ALL, 5 ) - - self.cmapsel = CMapSelPanel(self) - luts = ColorManager.luts - self.cmapsel.SetItems(list(luts.keys()), list(luts.values())) - bSizer1.Add(self.cmapsel, 0, wx.ALL|wx.EXPAND, 5 ) - - self.cmap = CMapPanel(self) - bSizer1.Add(self.cmap, 0, wx.ALL|wx.EXPAND, 5 ) - - - self.SetSizer( bSizer1 ) - self.Layout() - - # Connect Events - self.sli_high.Bind( wx.EVT_SCROLL, self.on_low ) - self.sli_low.Bind( wx.EVT_SCROLL, self.on_high ) - self.btn_8bit.Bind( wx.EVT_BUTTON, self.on_8bit ) - self.btn_minmax.Bind( wx.EVT_BUTTON, self.on_minmax ) - self.btn_slice.Bind( wx.EVT_BUTTON, self.on_slice ) - self.btn_stack.Bind( wx.EVT_BUTTON, self.on_stack ) - self.cmap.set_handle(self.on_cmap) - self.cmapsel.set_handle(self.on_cmapsel) - - self.range = (0, 255) - - def on_cmap(self): - ips = IPy.get_ips() - if ips is None: return - cmap = CMapPanel.linear_color(self.cmap.GetValue()) - ips.lut = cmap - ips.update = 'pix' - - def on_cmapsel(self): - ips = IPy.get_ips() - if ips is None: return - key = self.cmapsel.GetValue() - ips.lut = ColorManager.get_lut(key) - ips.update = 'pix' - - # Virtual event handlers, overide them in your derived class - def on_low( self, event ): - ips = IPy.get_ips() - if ips is None: return - if self.sli_high.GetValue()self.sli_high.GetValue(): - self.sli_low.SetValue(self.sli_high.GetValue()) - ips.range = (self.sli_low.GetValue(), self.sli_high.GetValue()) - lim1 = 1.0 * (self.sli_low.GetValue() - self.range[0])/(self.range[1]-self.range[0]) - lim2 = 1.0 * (self.sli_high.GetValue() - self.range[0])/(self.range[1]-self.range[0]) - self.histpan.set_lim(lim1*255, lim2*255) - ips.update = 'pix' - - def on_8bit( self, event ): - ips = IPy.get_ips() - if ips is None: return - self.range = ips.range = (0,255) - hist = ips.histogram() - self.histpan.SetValue(hist) - self.sli_low.set_para((0,255), 0) - self.sli_high.set_para((0,255), 0) - self.sli_low.SetValue(0) - self.sli_high.SetValue(255) - self.histpan.set_lim(0,255) - ips.update = 'pix' - - def on_minmax( self, event ): - ips = IPy.get_ips() - if ips is None: return - minv, maxv = ips.get_updown() - self.range = ips.range = (minv, maxv) - hist = ips.histogram() - self.histpan.SetValue(hist) - self.sli_low.set_para(self.range, 10) - self.sli_high.set_para(self.range, 10) - self.sli_low.SetValue(minv) - self.sli_high.SetValue(maxv) - self.histpan.set_lim(0,255) - ips.update = 'pix' - - def on_slice( self, event ): - ips = IPy.get_ips() - if ips is None: return - hist = ips.histogram() - self.histpan.set_hist(hist) - - def on_stack( self, event ): - ips = IPy.get_ips() - if ips is None: return - hists = ips.histogram(stack=True) - self.histpan.set_hist(hists) \ No newline at end of file +from sciwx.plugins.histogram import Histogram as Plugin \ No newline at end of file diff --git a/imagepy/widgets/navigator/navigator_wgt.py b/imagepy/widgets/navigator/navigator_wgt.py index d1aa615d..aa344463 100644 --- a/imagepy/widgets/navigator/navigator_wgt.py +++ b/imagepy/widgets/navigator/navigator_wgt.py @@ -1,113 +1 @@ -from ...ui.widgets import ViewPort -from imagepy import IPy -import numpy as np -import wx - -class Plugin ( wx.Panel ): - title = 'Navigator' - scales = [0.03125, 0.0625, 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 8, 10] - def __init__( self, parent ): - - - wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 255,200 ), style = wx.TAB_TRAVERSAL ) - - bSizer1 = wx.BoxSizer( wx.VERTICAL ) - - bSizer3 = wx.BoxSizer( wx.HORIZONTAL ) - - self.viewport = ViewPort( self) - bSizer3.Add( self.viewport, 1, wx.EXPAND |wx.ALL, 5 ) - - self.slider = wx.Slider( self, wx.ID_ANY, 6, 0, 13, wx.DefaultPosition, wx.DefaultSize, wx.SL_LEFT|wx.SL_VERTICAL|wx.SL_SELRANGE ) - bSizer3.Add( self.slider, 0, wx.ALL|wx.EXPAND, 0 ) - - - bSizer1.Add( bSizer3, 1, wx.EXPAND, 5 ) - - bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_apply = wx.Button( self, wx.ID_ANY, u"Apply", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) - self.btn_apply.SetMaxSize( wx.Size( -1,40 ) ) - - bSizer2.Add( self.btn_apply, 0, wx.ALL, 5 ) - - self.btn_fit = wx.Button( self, wx.ID_ANY, u"Fit", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) - self.btn_fit.SetMaxSize( wx.Size( -1,40 ) ) - - bSizer2.Add( self.btn_fit, 0, wx.ALL, 5 ) - - self.btn_one = wx.Button( self, wx.ID_ANY, u"Normal", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) - self.btn_one.SetMaxSize( wx.Size( -1,40 ) ) - - bSizer2.Add( self.btn_one, 0, wx.ALL, 5 ) - - - bSizer2.AddStretchSpacer(prop=1) - - self.label = wx.StaticText( self, wx.ID_ANY, u" 100.00%", wx.DefaultPosition, wx.DefaultSize, 0 ) - self.label.Wrap( -1 ) - bSizer2.Add( self.label, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer1.Add( bSizer2, 0, wx.EXPAND, 5 ) - - - self.SetSizer( bSizer1 ) - self.Layout() - - self.slider.Bind( wx.EVT_SCROLL_CHANGED, self.on_zoom ) - self.btn_apply.Bind( wx.EVT_BUTTON, self.on_apply ) - self.btn_fit.Bind( wx.EVT_BUTTON, self.on_fit ) - self.btn_one.Bind( wx.EVT_BUTTON, self.on_one ) - self.viewport.set_handle(self.on_handle) - - def on_apply(self, event): - win = IPy.get_window() - if win is None: return - img = win.canvas.ips.img - step = max(max(img.shape[:2])//300,1) - self.viewport.set_img(win.canvas.ips.lookup(img[::step,::step]), img.shape) - self.viewport.set_box(win.canvas.imgbox, win.canvas.box) - - def on_zoom(self, event): - k = self.scales[self.slider.GetValue()] - self.label.SetLabel('%.2f%%'%(k*100)) - win = IPy.get_window() - if win is None: return - a,b,c,d = win.canvas.box - x, y = win.canvas.to_data_coor(c/2, d/2) - win.canvas.scaleidx = self.slider.GetValue() - win.canvas.zoom(k, x, y) - win.canvas.ips.update = 'pix' - self.viewport.set_box(win.canvas.imgbox, win.canvas.box) - - def on_fit(self, event): - win = IPy.get_window() - if win is None: return - win.canvas.self_fit() - win.canvas.ips.update = 'pix' - self.slider.SetValue(win.canvas.scaleidx) - k = self.scales[self.slider.GetValue()] - self.label.SetLabel('%.2f%%'%(k*100)) - self.viewport.set_box(win.canvas.imgbox, win.canvas.box) - - def on_one(self, event): - win = IPy.get_window() - if win is None: return - a,b,c,d = win.canvas.box - x, y = win.canvas.to_data_coor(c/2, d/2) - win.canvas.scaleidx = self.scales.index(1) - win.canvas.zoom(1, x, y) - win.canvas.ips.update = 'pix' - self.slider.SetValue(win.canvas.scaleidx) - self.label.SetLabel('%.2f%%'%100) - self.viewport.set_box(win.canvas.imgbox, win.canvas.box) - - def on_handle(self): - win = IPy.get_window() - if win is None: return - x, y = self.viewport.GetValue() - print(x, y) - win.canvas.center(x, y) - win.canvas.ips.update = 'pix' - self.viewport.set_box(win.canvas.imgbox, win.canvas.box) \ No newline at end of file +from sciwx.plugins.viewport import ViewPort as Plugin \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f6e0cff8..1be86b5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,17 @@ -scikit-image -shapely -wxpython -numba -pydicom \ No newline at end of file +numba +numpy-stl +openpyxl +pandas +pydicom +pypubsub +read-roi +scikit-image +scikit-learn +shapely +wxpython +xlrd +xlwt +markdown +python-markdown-math +moderngl +dulwich diff --git a/sciapp/__init__.py b/sciapp/__init__.py new file mode 100644 index 00000000..11add961 --- /dev/null +++ b/sciapp/__init__.py @@ -0,0 +1,11 @@ +from .app import App +from .manager import Manager + +class Source: + managers = {} + + @classmethod + def manager(cls, name): + if not name in cls.managers: + cls.managers[name] = Manager() + return cls.managers[name] \ No newline at end of file diff --git a/sciapp/action/__init__.py b/sciapp/action/__init__.py new file mode 100644 index 00000000..992eceb9 --- /dev/null +++ b/sciapp/action/__init__.py @@ -0,0 +1,9 @@ +from .action import SciAction +from .imgact import ImgAction +from .tolact import Tool, DefaultTool, ImageTool, ShapeTool, TableTool, MeshTool +from .plugin.mea_tools import * +from .plugin.shp_tools import * +from .plugin.roi_tools import * +from .plugin.img_tools import * +from .plugin.mesh_tools import * +from .advanced import Filter, Free, Simple, Table, Macros, Widget, dataio, Report \ No newline at end of file diff --git a/sciapp/action/action.py b/sciapp/action/action.py new file mode 100644 index 00000000..76343901 --- /dev/null +++ b/sciapp/action/action.py @@ -0,0 +1,8 @@ +class SciAction: + name = 'SciAction' + + def __init__(self): pass + + def start(self, app, para=None, callafter=None): + self.app = app + print(self.name, 'started!') \ No newline at end of file diff --git a/imagepy/core/engine/__init__.py b/sciapp/action/advanced/__init__.py similarity index 63% rename from imagepy/core/engine/__init__.py rename to sciapp/action/advanced/__init__.py index 3bb226b7..8ee8c376 100644 --- a/imagepy/core/engine/__init__.py +++ b/sciapp/action/advanced/__init__.py @@ -1,8 +1,7 @@ from .filter import Filter from .simple import Simple +from .table import Table from .free import Free -from .tool import Tool from .macros import Macros -from .mkdown import MkDown from .widget import Widget -from .table import Table \ No newline at end of file +from .report import Report \ No newline at end of file diff --git a/sciapp/action/advanced/dataio.py b/sciapp/action/advanced/dataio.py new file mode 100644 index 00000000..375b9b7d --- /dev/null +++ b/sciapp/action/advanced/dataio.py @@ -0,0 +1,67 @@ +import os +from sciapp import Source +from . import Free, Simple, Table +#from .macros import Macros +from ... import Manager +import numpy as np + +ReaderManager, WriterManager = Manager(), Manager() + +class Reader(Free): + para = {'path':''} + tag = None + + def show(self): + filt = [i.lower() for i in self.filt] + self.para['path'] = self.app.get_path('Open..', filt, 'open', '') + return not self.para['path'] is None + + #process + def run(self, para = None): + #add_recent(para['path']) + fp, fn = os.path.split(para['path']) + fn = fn.split('.') + print(fn) + fn, fe = fn[0], '.'+'.'.join(fn[1:]) + # fn, fe = os.path.splitext(fn) + readers = ReaderManager.gets(fe[1:].lower(), tag=self.tag) + if len(readers)==0: + return self.app.alert('no reader found for %s file'%fe[1:]) + if not self.tag is None: + self.app.show(self.tag, readers[0][1](para['path']), fn) + else: self.app.show(readers[0][2], readers[0][1](para['path']), fn) + +class ImageWriter(Simple): + tag = 'img' + note = ['all'] + para={'path':''} + + def show(self): + filt = [i.lower() for i in self.filt] + self.para['path'] = self.app.get_path('Save..', filt, 'save', '') + return not self.para['path'] is None + + #process + def run(self, ips, imgs, para = None): + fp, fn = os.path.split(para['path']) + fn, fe = os.path.splitext(fn) + writer = WriterManager.gets(fe[1:].lower(), tag=self.tag) + if len(writer)==1: writer[0][1](para['path'], ips.img if self.tag=='img' else imgs) + +class TableWriter(Table): + tag = 'tab' + note = ['all'] + para={'path':''} + + def show(self): + filt = [i.lower() for i in self.filt] + self.para['path'] = self.app.get_path('Save..', filt, 'save', '') + return not self.para['path'] is None + + #process + def run(self, tps, snap, data, para = None): + fp, fn = os.path.split(para['path']) + fn, fe = os.path.splitext(fn) + + writer = WriterManager.gets(fe[1:], tag=self.tag) + if len(writer)==1: return writer[0][1](para['path'], data) \ No newline at end of file diff --git a/sciapp/action/advanced/filter.py b/sciapp/action/advanced/filter.py new file mode 100644 index 00000000..72a11131 --- /dev/null +++ b/sciapp/action/advanced/filter.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 2 23:48:33 2016 +@author: yxl +""" + +import threading +import numpy as np +from time import time, sleep + +def process_channels(plg, ips, src, des, para): + if ips.channels>1 and not 'not_channel' in plg.note: + for i in range(ips.channels): + rst = plg.run(ips, src if src is None else src[:,:,i], des[:,:,i], para) + if not rst is des and not rst is None: + des[:,:,i] = rst + else: + rst = plg.run(ips, src, des, para) + if not rst is des and not rst is None: + des[:] = rst + return des + +def process_one(plg, ips, src, img, para, callafter=None): + if callafter != 'no record': + plg.app.record_macros('{}>{}'.format(plg.title, para)) + plg.app.add_task(plg) + start = time() + transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) + transfloat = '2float' in plg.note and not ips.dtype in (np.complex128, np.float32, np.float64) + if transint: + buf = img.astype(np.int32) + src = src.astype(np.int32) + if transfloat: + buf = img.astype(np.float32) + src = src.astype(np.float32) + rst = process_channels(plg, ips, src, buf if transint or transfloat else img, para) + if not img is rst and not rst is None: + imgrange = {np.uint8:(0,255), np.uint16:(0, 65535)}[img.dtype.type] + np.clip(rst, imgrange[0], imgrange[1], out=img) + if 'auto_msk' in plg.note and not ips.mask('out') is None: + msk = ips.mask('out') + img[msk] = src[msk] + plg.app.info('%s: cost %.3fs'%(plg.title, time()-start)) + ips.update() + plg.app.remove_task(plg) + if not callafter in (None, 'no record'):callafter() + +def process_stack(plg, ips, src, imgs, para, callafter=None): + plg.app.record_macros('{}>{}'.format(plg.title, para)) + plg.app.add_task(plg) + start = time() + transint = '2int' in plg.note and ips.dtype in (np.uint8, np.uint16) + transfloat = '2float' in plg.note and not ips.dtype in (np.complex128, np.float32, np.float64) + if transint: + buf = imgs[0].astype(np.int32) + src = src.astype(np.int32) + elif transfloat: + buf = imgs[0].astype(np.float32) + src = src.astype(np.float32) + else: src = src * 1 + + for i,n in zip(imgs,list(range(len(imgs)))): + #sleep(0.5) + plg.progress(n+1, len(imgs)) + if 'auto_snap' in plg.note : src[:] = i + if transint or transfloat: buf[:] = i + rst = process_channels(plg, ips, src, buf if transint or transfloat else i, para) + if not i is rst and not rst is None: + imgrange = {np.uint8:(0,255), np.uint16:(0,65535)}[i.dtype.type] + np.clip(rst, imgrange[0], imgrange[1], out=i) + if 'auto_msk' in plg.note and not ips.mask() is None: + msk = ips.mask('out') + i[msk] = src[msk] + plg.app.info('%s: cost %.3fs'%(ips.title, time()-start)) + ips.update() + plg.app.remove_task(plg) + if not callafter is None:callafter() + + +class Filter: + title = 'Filter' + modal = True + asyn = True + note = [] + 'all, 8-bit, 16-bit, int, rgb, float, not_channel, not_slice, req_roi, auto_snap, auto_msk, preview, 2int, 2float' + para = None + view = None + prgs = None + + def __init__(self, ips=None): pass + + def progress(self, i, n): self.prgs = int(i*100/n) + + def show(self): + preview = lambda para, ips=self.ips: self.preview(ips, para) or ips.update() + return self.app.show_para(self.title, self.para, self.view, preview, + on_ok=lambda : self.ok(self.ips), on_help=self.on_help, + on_cancel=lambda : self.cancel(self.ips) or self.ips.update(), + preview='preview' in self.note, modal=self.modal) + + def run(self, ips, snap, img, para = None): + return 255-img + + def check(self, ips): + note = self.note + if ips == None: + return self.app.alert('No image opened!') + return False + elif 'req_roi' in note and ips.roi == None: + return self.app.alert('No Roi found!') + elif not 'all' in note: + if ips.dtype==np.uint8 and ips.channels==3 and not 'rgb' in note: + return self.app.alert('Do not surport rgb image') + elif ips.dtype==np.uint8 and ips.channels==1 and not '8-bit' in note: + return self.app.alert('Do not surport 8-bit image') + elif ips.dtype==np.uint16 and not '16-bit' in note: + return self.app.alert('Do not surport 16-bit uint image') + elif ips.dtype in [np.int32, np.int64] and not 'int' in note: + return self.app.alert('Do not surport int image') + elif ips.dtype in [np.float32, np.float64] and not 'float' in note: + return self.app.alert('Do not surport float image') + return True + + def preview(self, ips, para): + process_one(self, ips, ips.snap, ips.img, para, 'no record') + + def load(self, ips):return True + + def ok(self, ips, para=None, callafter=None): + if para == None: + para = self.para + if not 'not_slice' in self.note and ips.slices>1: + if para == None:para = {} + if para!=None and 'stack' in para:del para['stack'] + # = WidgetsManager.getref('Macros Recorder') + if ips.slices==1 or 'not_slice' in self.note: + # process_one(self, ips, ips.snap, ips.img, para) + if self.asyn and self.app.asyn: + threading.Thread(target = process_one, args = + (self, ips, ips.snap, ips.img, para, callafter)).start() + else: process_one(self, ips, ips.snap, ips.img, para, callafter) + # if win!=None: win.write('{}>{}'.format(self.title, para)) + + elif ips.slices>1: + has, rst = 'stack' in para, None + if not has: + rst = self.app.yes_no('Run every slice in current stacks?') + if 'auto_snap' in self.note and self.modal:ips.img[:] = ips.snap + if has and para['stack'] or rst == 'yes': + para['stack'] = True + #process_stack(self, ips, ips.snap, ips.imgs, para) + if self.asyn and self.app.asyn: + threading.Thread(target = process_stack, args = + (self, ips, ips.snap, ips.imgs, para, callafter)).start() + else: process_stack(self, ips, ips.snap, ips.imgs, para, callafter) + + elif has and not para['stack'] or rst == 'no': + para['stack'] = False + #process_one(self, ips, ips.snap, ips.img, para) + if self.asyn and self.app.asyn: + threading.Thread(target = process_one, args = + (self, ips, ips.snap, ips.img, para, callafter)).start() + else: process_one(self, ips, ips.snap, ips.img, para, callafter) + elif rst == 'cancel': pass + #ips.update() + + def on_help(self): + self.app.show_md(self.__doc__ or 'No Document!', self.title) + + def cancel(self, ips): + if 'auto_snap' in self.note: + ips.img[:] = ips.snap + ips.update() + + def start(self, app, para=None, callafter=None): + self.app, self.ips = app, app.get_img() + if not self.check(self.ips):return + if not self.load(self.ips):return + if 'auto_snap' in self.note:self.ips.snapshot() + + if para!=None: + self.ok(self.ips, para, callafter) + elif self.view==None: + if not self.__class__.show is Filter.show: + if self.show(): + self.ok(self.ips, para, callafter) + else: self.ok(self.ips, para, callafter) + elif self.modal: + if self.show(): + self.ok(self.ips, None, callafter) + else:self.cancel(self.ips) + else: self.show() + + def __del__(self): + print('filter del') diff --git a/sciapp/action/advanced/free.py b/sciapp/action/advanced/free.py new file mode 100644 index 00000000..07d33945 --- /dev/null +++ b/sciapp/action/advanced/free.py @@ -0,0 +1,39 @@ +import threading +from time import time + +class Free: + title = 'Free' + view = None + para = None + prgs = None + asyn = True + + def progress(self, i, n): self.prgs = int(i*100/n) + + def run(self, para=None): print('this is a plugin') + + def runasyn(self, para, callback=None): + self.app.add_task(self) + self.app.record_macros('{}>{}'.format(self.title, para)) + start = time() + self.run(para) + self.app.info('%s: cost %.3fs'%(self.title, time()-start)) + self.app.remove_task(self) + if callback!=None:callback() + + def load(self):return True + + def show(self): + if self.view==None:return True + return self.app.show_para(self.title, self.para, self.view, None) + + def start(self, app, para=None, callback=None): + self.app = app + if not self.load():return + if para!=None or self.show(): + if para==None:para = self.para + if self.asyn and app.asyn: + threading.Thread(target = self.runasyn, + args = (para, callback)).start() + else: + self.runasyn(para, callback) \ No newline at end of file diff --git a/sciapp/action/advanced/macros.py b/sciapp/action/advanced/macros.py new file mode 100644 index 00000000..dff0ec54 --- /dev/null +++ b/sciapp/action/advanced/macros.py @@ -0,0 +1,9 @@ +class Macros: + def __init__(self, title, cmds): + self.title = title + self.cmds = cmds + + def __call__(self): return self + + def start(self, app, para=None, callafter=None): + app.run_macros(self.cmds, callafter) \ No newline at end of file diff --git a/sciapp/action/advanced/report.py b/sciapp/action/advanced/report.py new file mode 100644 index 00000000..a42922bd --- /dev/null +++ b/sciapp/action/advanced/report.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Dec 29 01:48:23 2016 +@author: yxl +""" +import wx +from sciapp import app +from sciapp.action.advanced.dataio import ReaderManager +# from imagepy.core.manager import ReaderManager, ViewerManager +from sciwx.widgets.propertygrid import GridDialog +from sciapp.util import xlreport +from time import time +import openpyxl as pyxl + +class Report: + def __init__(self, title, cont): + self.title = title + self.cont = cont + + def __call__(self): return self + + def runasyn(self, wb, info, key, para = None, callback = None): + self.app.add_task(self) + for i in para: + if i in key and key[i][0] == 'img': + ips = self.app.get_img(para[i]) + para[i] = ips if ips is None else ips.img + + if i in key and key[i][0] == 'tab': + tps = self.app.get_table(para[i]) + para[i] = tps if tps is None else tps.data + + start = time() + xlreport.fill_value(wb, info, para) + wb.save(para['path']) + self.app.info('%s: cost %.3fs'%(self.title, time()-start)) + self.app.remove_task(self) + if callback!=None:callback() + + def start(self, app, para=None, callafter=None): + self.app = app + wb = pyxl.load_workbook(self.cont) + xlreport.repair(wb) + info, key = xlreport.parse(wb) + + if para is not None: + return self.runasyn(wb, info, para, callafter) + dialog = GridDialog(self.app, self.title, info, key) + rst = dialog.ShowModal() + para = dialog.GetValue() + + dialog.Destroy() + if rst != 5100: return + filt = ['XLSX', 'xlsx', 'xlsx'] + path = self.app.get_path('Save..', filt, 'save') + if not path: return + para['path'] = path + self.app.record_macros('{}>{}'.format(self.title, para)) + self.runasyn(wb, info, key, para, callafter) + +def show_rpt(data, title): + wx.CallAfter(Report(title, data).start) + +# ViewerManager.add('rpt', show_rpt) +def read_rpt(path): return path +ReaderManager.add('rpt', read_rpt, tag='rpt') \ No newline at end of file diff --git a/sciapp/action/advanced/simple.py b/sciapp/action/advanced/simple.py new file mode 100644 index 00000000..a4a7e0c0 --- /dev/null +++ b/sciapp/action/advanced/simple.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Dec 3 03:32:05 2016 +@author: yxl +""" +import threading +from time import time +import numpy as np + +class Simple: + title = 'SimpleFilter' + asyn = True + note = [] + para = None + 'all, 8-bit, 16-bit, rgb, float, req_roi, stack, stack2d, stack3d, preview' + view = None + prgs = None + modal = True + + def __init__(self): pass + + def load(self, ips):return True + + def check(self, ips): + note = self.note + if ips == None: + self.app.alert('No image opened!') + return False + if 'req_roi' in note and ips.roi == None: + self.app.alert('No Roi found!') + return False + if not 'all' in note: + if ips.dtype==np.uint8 and ips.channels==3 and not 'rgb' in note: + self.app.alert('Do not surport rgb image') + return False + elif ips.dtype==np.uint8 and ips.channels==1 and not '8-bit' in note: + self.app.alert('Do not surport 8-bit image') + return False + elif ips.dtype==np.uint16 and not '16-bit' in note: + self.app.alert('Do not surport 16-bit uint image') + return False + elif ips.dtype==np.int32 and not 'int' in note: + self.app.alert('Do not surport 32-bit int uint image') + return False + elif (ips.dtype==np.float32 or ips.dtype==np.float64) and not 'float' in note: + self.app.alert('Do not surport float image') + return False + if sum([i in note for i in ('stack','stack2d','stack3d')])>0: + if ips.slices==1: + self.app.alert('Stack required!') + return False + elif 'stack2d' in note and ips.isarray: + self.app.alert('Stack2d required!') + return False + elif 'stack3d' in note and not ips.isarray: + self.app.alert('Stack3d required!') + return False + return True + + def preview(self, ips, para):pass + + def progress(self, i, n): self.prgs = int(i*100/n) + + def show(self): + preview = lambda para, ips=self.ips: self.preview(ips, para) or ips.update() + return self.app.show_para(self.title, self.para, self.view, preview, + on_ok=lambda : self.ok(self.ips), on_help=self.on_help, + on_cancel=lambda : self.cancel(self.ips) or self.ips.update(), + preview='preview' in self.note, modal=self.modal) + + def run(self, ips, imgs, para = None):pass + + def cancel(self, ips):pass + + def on_help(self): + self.app.show_md(self.__doc__ or 'No Document!', self.title) + + def ok(self, ips, para=None, callafter=None): + if para == None: para = self.para + if self.asyn and self.app.asyn: + threading.Thread(target = self.runasyn, + args = (ips, ips.imgs, para, callafter)).start() + else: self.runasyn(ips, ips.imgs, para, callafter) + + def runasyn(self, ips, imgs, para = None, callback = None): + self.app.record_macros('{}>{}'.format(self.title, para)) + self.app.add_task(self) + start = time() + self.run(ips, imgs, para) + self.app.info('%s: cost %.3fs'%(self.title, time()-start)) + ips.update() + self.app.remove_task(self) + if callback!=None:callback() + + def start(self, app, para=None, callback=None): + self.app, self.ips = app, app.get_img() + if not self.check(self.ips):return + if not self.load(self.ips):return + + if para!=None: + self.ok(self.ips, para, callback) + elif self.view==None: + if not self.__class__.show is Simple.show: + if self.show(): + self.ok(self.ips, para, callback) + else: self.ok(self.ips, para, callback) + elif self.modal: + if self.show(): + self.ok(self.ips, para, callback) + else: + self.cancel(self.ips) + self.ips.update() + else: self.show() \ No newline at end of file diff --git a/sciapp/action/advanced/table.py b/sciapp/action/advanced/table.py new file mode 100644 index 00000000..7f9130af --- /dev/null +++ b/sciapp/action/advanced/table.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Dec 3 03:32:05 2016 +@author: yxl +""" +import threading +from time import time + +class Table: + title = 'TableFilter' + note = [] + para = None + 'req_sel, req_row, req_col, auto_snap, auto_msk, msk_not, num_only, preview' + view = None + prgs = None + modal = True + asyn = True + + def __init__(self, tps=None): + + self.dialog = None + + def progress(self, i, n): self.prgs = int(i*100/n) + + def load(self, tps):return True + + def preview(self, tps, para): + self.run(tps, tps.snap, tps.data, para) + tps.update() + + def show(self): + preview = lambda para, tps=self.tps: self.preview(tps, para) or tps.update() + return self.app.show_para(self.title, self.para, self.view, preview, + on_ok=lambda : self.ok(self.tps), on_help=self.on_help, + on_cancel=lambda : self.cancel(self.tps) or self.tps.update(), + preview='preview' in self.note, modal=self.modal) + + def run(self, tps, snap, data, para = None):pass + + def cancel(self, tps): + if 'auto_snap' in self.note: + tps.data[tps.snap.columns] = tps.snap + tps.update() + + def on_help(self): + self.app.show_md(self.__doc__ or 'No Document!', self.title) + + def ok(self, tps, para=None, callafter=None): + if para == None: para = self.para + if self.asyn and self.app.asyn: + threading.Thread(target = self.runasyn, + args = (tps, tps.data, tps.snap, para, callafter)).start() + else: self.runasyn(tps, tps.data, tps.snap, para, callafter) + + def runasyn(self, tps, snap, data, para = None, callback = None): + self.app.record_macros('{}>{}'.format(self.title, para)) + self.app.add_task(self) + start = time() + self.run(tps, data, snap, para) + self.app.info('%s: cost %.3fs'%(tps.title, time()-start)) + tps.update() + self.app.remove_task(self) + if callback!=None:callback() + + def check(self, tps): + print(self.note) + if tps == None: + self.app.alert('no table opened!') + return False + if 'req_sel' in self.note: + print(tps.rowmsk, tps.colmsk) + if isinstance(tps.rowmsk, slice) and\ + isinstance(tps.colmsk, slice): + self.app.alert('no selection!') + return False + if 'req_row' in self.note: + print(tps.rowmsk, tps.colmsk) + if isinstance(tps.rowmsk, slice): + self.app.alert('need row selection!') + return False + if 'req_col' in self.note: + print(tps.rowmsk, tps.colmsk) + if isinstance(tps.colmsk, slice): + self.app.alert('need col selection!') + return False + return True + + def start(self, app, para=None, callback=None): + self.app, self.tps = app, app.get_table() + #print self.title, para + if not self.check(self.tps):return + if not self.load(self.tps):return + if 'auto_snap' in self.note: + if 'auto_msk' in self.note: mode = True + elif 'msk_not' in self.note: mode = False + else: mode = None + self.tps.snapshot(mode, 'num_only' in self.note) + if para!=None: + self.ok(self.tps, para, callback) + elif self.view==None: + if not self.__class__.show is Table.show: + if self.show(): + self.ok(self.tps, para, callback) + else: self.ok(self.tps, para, callback) + elif self.modal: + if self.show(): + self.ok(self.tps, para, callback) + else:self.cancel(self.tps) + if not self.dialog is None: self.dialog.Destroy() + else: self.show() \ No newline at end of file diff --git a/sciapp/action/advanced/widget.py b/sciapp/action/advanced/widget.py new file mode 100644 index 00000000..8684c9a4 --- /dev/null +++ b/sciapp/action/advanced/widget.py @@ -0,0 +1,9 @@ +class Widget(): + def __init__(self, panel): + self.panel = panel + self.title = panel.title + + def __call__(self): return self + + def start(self, app): + app.show_widget(self.panel) \ No newline at end of file diff --git a/sciapp/action/imgact.py b/sciapp/action/imgact.py new file mode 100644 index 00000000..69822678 --- /dev/null +++ b/sciapp/action/imgact.py @@ -0,0 +1,35 @@ +from .action import SciAction + +class ImgAction(SciAction): + title = 'Image Action' + note, para, view = [], None, None + + def __init__(self): pass + + def show(self): + ips, img, snap = self.ips, self.ips.img, self.ips.snap + f = lambda p: self.run(ips, img, snap, p) or self.ips.update() + return self.app.show_para(self.title, self.para, self.view, f, on_ok=None, + on_cancel=lambda x=self.ips:self.cancel(x), + preview='preview' in self.note, modal=True) + + def cancel(self, ips): + ips.img[:] = ips.snap + ips.update() + + def run(self, ips, img, snap, para): + print('I am running!!!') + + def start(self, app, para=None, callback=None): + print('Image Action Started!') + self.app = app + self.ips = app.get_img() + if 'auto_snap' in self.note: self.ips.snapshot() + if para!=None: + self.run(self.ips, self.ips.img, self.ips.snap, para) + elif self.view==None and self.__class__.show is ImgAction.show: + self.run(self.ips, self.ips.img, self.ips.snap, para) + elif self.show(): + self.run(self.ips, self.ips.img, self.ips.snap, self.para) + elif 'auto_snap' in self.note: self.cancel(self.ips) + self.ips.update() diff --git a/sciapp/action/plugin/__init__.py b/sciapp/action/plugin/__init__.py new file mode 100644 index 00000000..93b7946f --- /dev/null +++ b/sciapp/action/plugin/__init__.py @@ -0,0 +1 @@ +from .generalio import OpenFile, SaveImage \ No newline at end of file diff --git a/sciapp/action/plugin/filters.py b/sciapp/action/plugin/filters.py new file mode 100644 index 00000000..77c74677 --- /dev/null +++ b/sciapp/action/plugin/filters.py @@ -0,0 +1,11 @@ +from ..advanced import Filter +from scipy.ndimage import gaussian_filter + +class Gaussian(Filter): + title = 'Gaussian' + note = ['all', 'auto_msk', 'auto_snap','preview'] + para = {'sigma':2} + view = [(float, 'sigma', (0,30), 1, 'sigma', 'pix')] + + def run(self, ips, snap, img, para = None): + gaussian_filter(snap, para['sigma'], output=img) \ No newline at end of file diff --git a/sciapp/action/plugin/generalio.py b/sciapp/action/plugin/generalio.py new file mode 100644 index 00000000..08b0d6ee --- /dev/null +++ b/sciapp/action/plugin/generalio.py @@ -0,0 +1,45 @@ +from ..advanced import dataio +from skimage.io import imread, imsave + +for i in ('bmp', 'jpg', 'jpeg', 'tif', 'png', 'gif'): + dataio.ReaderManager.add(i, imread, 'img') + dataio.WriterManager.add(i, imsave, 'img') + +class OpenFile(dataio.Reader): + title = 'Open' + + def load(self): + self.filt = [i for i in sorted(dataio.ReaderManager.names())] + return True + +class SaveImage(dataio.ImageWriter): + title = 'Save Image' + + def load(self, ips): + self.filt = [i for i in sorted(dataio.WriterManager.names())] + return True + +from pandas import read_csv, read_excel +read_csv2 = lambda p:read_csv(p, index_col=0) +read_excel2 = lambda p:read_excel(p, index_col=0) + +save_csv = lambda path, data:data.to_csv(path) +dataio.ReaderManager.add('csv', read_csv2, 'tab') +dataio.WriterManager.add('csv', save_csv, 'tab') + + +save_excel = lambda path, data:data.to_excel(path) +dataio.ReaderManager.add('xls', read_excel2, 'tab') +dataio.WriterManager.add('xls', save_excel, 'tab') +dataio.ReaderManager.add('xlsx', read_excel2, 'tab') +dataio.WriterManager.add('xlsx', save_excel, 'tab') + +class OpenTable(dataio.Reader): + title = 'Excel Open' + tag = 'tab' + filt = ['csv', 'xls','xlsx'] + +class SaveTable(dataio.TableWriter): + title = 'Excel Save' + tag = 'tab' + filt = ['csv', 'xls', 'xlsx'] \ No newline at end of file diff --git a/sciapp/action/plugin/img_tools.py b/sciapp/action/plugin/img_tools.py new file mode 100644 index 00000000..9858f6d3 --- /dev/null +++ b/sciapp/action/plugin/img_tools.py @@ -0,0 +1,55 @@ +from sciapp.action import ImageTool + +class MoveTool(ImageTool): + title = 'Move And Scale' + def __init__(self): + self.oldxy = None + + def mouse_down(self, obj, x, y, btn, **key): + if btn==1: self.oldxy = key['px'], key['py'] + if btn==3: key['canvas'].fit() + + def mouse_up(self, obj, x, y, btn, **key): + self.oldxy = None + + def mouse_move(self, obj, x, y, btn, **key): + if self.oldxy is None: return + ox, oy = self.oldxy + up = (1,-1)[key['canvas'].up] + key['canvas'].move(key['px']-ox, (key['py']-oy)*up) + self.oldxy = key['px'], key['py'] + + def mouse_wheel(self, obj, x, y, d, **key): + if d>0: key['canvas'].zoomout(x, y, coord='data') + if d<0: key['canvas'].zoomin(x, y, coord='data') + +class ScaleTool(ImageTool): + title = 'Scope' + def __init__(self): + self.ox, self.oy = 0, 0 + + def mouse_down(self, ips, x, y, btn, **key): + if btn==2: + self.ox, self.oy = key['canvas'].to_panel_coor(x,y) + print(self.ox, self.oy) + #print 'down', self.ox, self.oy + if btn==1: key['canvas'].zoomout(x, y, 'data') + if btn==3: key['canvas'].zoomin(x, y, 'data') + ips.update() + + def mouse_up(self, ips, x, y, btn, **key): + pass + + def mouse_move(self, ips, x, y, btn, **key): + if btn==2: + x,y = key['canvas'].to_panel_coor(x,y) + #print 'x,y',x,y + #print 'dx,dy:', x-self.ox, y-self.oy + key['canvas'].move(x-self.ox, y-self.oy) + ips.update() + self.ox, self.oy = x,y + + def mouse_wheel(self, ips, x, y, d, **key): + if d>0:key['canvas'].zoomout(x, y, 'data') + if d<0:key['canvas'].zoomin(x, y, 'data') + ips.update() \ No newline at end of file diff --git a/sciapp/action/plugin/mea_tools.py b/sciapp/action/plugin/mea_tools.py new file mode 100644 index 00000000..c1652d58 --- /dev/null +++ b/sciapp/action/plugin/mea_tools.py @@ -0,0 +1,221 @@ +from .shpbase import pick_obj, pick_point, drag, offset +from .. import ImageTool, ShapeTool +from ...object import Point, Line, Polygon, Layer, Points, Texts +from ...object.roi import * +import numpy as np +from numpy.linalg import norm + +def mark(shp, types = 'all'): + pts = [] + if not (types=='all' or shp.dtype in types): return pts + if shp.dtype == 'point': pts.append([shp.body]) + if shp.dtype == 'line': pts.append(shp.body) + if shp.dtype == 'polygon': pts.append(shp.body[0]) + if shp.dtype == 'layer': + minl, obj = 1e8, None + for i in shp.body: + pts.extend(mark(i, types)) + return pts + +class BaseEditor(ShapeTool): + def __init__(self, dtype='all'): + self.status, self.oldxy, self.p = '', None, None + self.pick_m, self.pick_obj = None, None + + def mouse_down(self, shp, x, y, btn, **key): + self.p = x, y + if btn==2: + self.status = 'move' + self.oldxy = key['px'], key['py'] + if btn==1 and self.status=='pick': + m, obj, l = pick_point(shp, x, y, 5) + self.pick_m, self.pick_obj = m, obj + if btn==1 and self.pick_m is None: + m, l = pick_obj(shp, x, y, 5) + self.pick_m, self.pick_obj = m, None + if btn==3: + obj, l = pick_obj(shp, x, y, 5) + if key['alt'] and not key['ctrl']: + if obj is None: del shp.body[:] + else: shp.body.remove(obj) + shp.measure_mark() + shp.dirty = True + if not (key['shift'] or key['alt'] or key['ctrl']): + key['canvas'].fit() + + def mouse_up(self, shp, x, y, btn, **key): + self.status = '' + if btn==1: + self.pick_m = self.pick_obj = None + if not (key['alt'] and key['ctrl']): return + pts = mark(shp) + if len(pts)>0: + pts = Points(np.vstack(pts), color=(255,0,0)) + key['canvas'].marks['anchor'] = pts + shp.dirty = True + + def mouse_move(self, shp, x, y, btn, **key): + self.cursor = 'arrow' + if self.status == 'move': + ox, oy = self.oldxy + up = (1,-1)[key['canvas'].up] + key['canvas'].move(key['px']-ox, (key['py']-oy)*up) + self.oldxy = key['px'], key['py'] + if key['alt'] and key['ctrl']: + self.status = 'pick' + if not 'anchor' in key['canvas'].marks: + pts = mark(shp) + if len(pts)>0: + pts = Points(np.vstack(pts), color=(255,0,0)) + key['canvas'].marks['anchor'] = pts + if 'anchor' in key['canvas'].marks: + m, obj, l = pick_point(key['canvas'].marks['anchor'], x, y, 5) + if not m is None: self.cursor = 'hand' + elif 'anchor' in key['canvas'].marks: + self.status = '' + del key['canvas'].marks['anchor'] + shp.dirty = True + if not self.pick_obj is None and not self.pick_m is None: + drag(self.pick_m, self.pick_obj, x, y) + shp.measure_mark() + pts = mark(self.pick_m) + if len(pts)>0: + pts = np.vstack(pts) + key['canvas'].marks['anchor'] = Points(pts, color=(255,0,0)) + self.pick_m.dirty = True + shp.dirty = True + if self.pick_obj is None and not self.pick_m is None: + offset(self.pick_m, x-self.p[0], y-self.p[1]) + pts = mark(self.pick_m) + if len(pts)>0: + pts = np.vstack(pts) + key['canvas'].marks['anchor'] = Points(pts, color=(255,0,0)) + shp.measure_mark() + self.p = x, y + self.pick_m.dirty = shp.dirty = True + + def mouse_wheel(self, shp, x, y, d, **key): + if d>0: key['canvas'].zoomout(x, y, coord='data') + if d<0: key['canvas'].zoomin(x, y, coord='data') + +class BaseMeasure(ImageTool): + def __init__(self, base): + base.__init__(self) + self.base = base + + def mouse_down(self, img, x, y, btn, **key): + if img.mark is None: img.mark = Measure() + self.base.mouse_down(self, img.mark, x, y, btn, **key) + + def mouse_up(self, img, x, y, btn, **key): + self.base.mouse_up(self, img.mark, x, y, btn, **key) + if not img.mark is None: + if len(img.mark.body)==0: img.mark = None + + def mouse_move(self, img, x, y, btn, **key): + self.base.mouse_move(self, img.mark, x, y, btn, **key) + + def mouse_wheel(self, img, x, y, d, **key): + self.base.mouse_wheel(self, img.mark, x, y, d, **key) + +def inbase(key, btn): + status = key['ctrl'], key['alt'], key['shift'] + return status == (1,1,0) or btn in {2,3} + +class PointEditor(BaseEditor): + title = 'Point Tool' + def __init__(self): + BaseEditor.__init__(self) + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if btn==1 and not key['alt'] and not key['ctrl']: + shp.body.append(Coordinate([x,y])) + shp.measure_mark() + shp.dirty = True + +class LineEditor(BaseEditor): + title = 'Line Tool' + def __init__(self, tp): + BaseEditor.__init__(self) + self.cur, self.n, self.obj, self.tp = 0, 0, None, tp + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn) and self.obj is None: + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + if self.obj is None: + self.obj = self.tp([(x,y)]) + shp.body.append(self.obj) + else: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + if btn==3 and not self.obj is None : + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + self.obj.dirty, shp.dirty, self.obj = True, True, None + del key['canvas'].marks['buffer'] + shp.measure_mark() + shp.dirty = True + +class AreaEditor(BaseEditor): + title = 'Polygon Tool' + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn) and self.obj is None: + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + if self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + else: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + if btn==3 and not self.obj is None : + body = np.vstack((self.obj.body, [(x,y)])) + shp.body[-1] = Area(body) + shp.measure_mark() + self.obj, shp.dirty = None, True + del key['canvas'].marks['buffer'] + shp.dirty = True + +class DistanceEditor(LineEditor): + def __init__(self): LineEditor.__init__(self, Distance) + +class AngleEditor(LineEditor): + def __init__(self): LineEditor.__init__(self, Angle) + +class SlopEditor(LineEditor): + def __init__(self): LineEditor.__init__(self, Slope) + +class CoordinateTool(BaseMeasure): + title = 'Coordinate Tool' + def __init__(self): + BaseMeasure.__init__(self, PointEditor) + +class DistanceTool(BaseMeasure): + title = 'Distance Tool' + def __init__(self): + BaseMeasure.__init__(self, DistanceEditor) + +class AngleTool(BaseMeasure): + title = 'Angle Tool' + def __init__(self): + BaseMeasure.__init__(self, AngleEditor) + +class SlopeTool(BaseMeasure): + title = 'Slop Tool' + def __init__(self): + BaseMeasure.__init__(self, SlopEditor) + +class AreaTool(BaseMeasure): + title = 'Area Tool' + def __init__(self): + BaseMeasure.__init__(self, AreaEditor) \ No newline at end of file diff --git a/sciapp/action/plugin/mesh_tools.py b/sciapp/action/plugin/mesh_tools.py new file mode 100644 index 00000000..84143726 --- /dev/null +++ b/sciapp/action/plugin/mesh_tools.py @@ -0,0 +1,73 @@ +from math import sin, cos, pi, atan, sqrt, asin +import numpy as np +from .. import MeshTool + +class MeshViewTool(MeshTool): + def __init__(self): + self.oldxy = None + self.status = '' + self.curobj = None + + def mouse_down(self, obj, x, y, btn, **key): + self.oldxy = x, y + if btn == 1 and not key['shift']: + picked = key['canvas'].at(x,y) if key['ctrl'] else None + if not picked is None: + self.curobj = picked + self.curobj.set_data(high_light=0xffaaffff) + self.status = 'view' + if btn == 1 and key['shift']: + self.status = 'offset' + if btn == 2: self.status = 'offset' + if btn == 3: + self.status = 'right' + + def mouse_up(self, obj, x, y, btn, **key): + if self.status == 'right': + key['canvas'].fit() + if not self.curobj is None: + self.curobj.set_data(high_light=False) + self.curobj = None + self.oldxy = None + self.status = '' + + def mouse_move(self, obj, x, y, btn, **key): + if self.status == 'view': + dx = x - self.oldxy[0] + dy = y - self.oldxy[1] + camera = key['canvas'].camera + camera.orbit(-dx/2, dy/2) + self.oldxy = x, y + if self.status == 'offset': + camera = key['canvas'].camera + dx = x - self.oldxy[0] + dy = y - self.oldxy[1] + norm = np.mean(camera._viewbox.size) + k = 1 / norm * camera._scale_factor + camera = key['canvas'].camera + dx, dy, dz = camera._dist_to_trans((-dx*k, dy*k)) + self.oldxy = x, y + cx, cy, cz = camera.center + camera.center = cx + dx, cy + dy, cz + dz + if self.status in {'right', 'light'}: + self.status = 'light' + lx, ly, lz = obj.light_dir + dx = (x - self.oldxy[0])/360 + dy = (y - self.oldxy[1])/360 + ay = asin(lz/sqrt(lx**2+ly**2+lz**2))-dy + xx = cos(dx)*lx - sin(dx)*ly + yy = sin(dx)*lx + cos(dx)*ly + ay = max(min(pi/2-1e-4, ay), -pi/2+1e-4) + zz, k = sin(ay), cos(ay)/sqrt(lx**2+ly**2) + obj.set_style(light_dir = (xx*k, yy*k, zz)) + self.oldxy = x, y + + + def mouse_wheel(self, obj, x, y, d, **key): + s = 1.1 ** - d + camera = key['canvas'].camera + if camera._distance is not None: + camera._distance *= s + camera.scale_factor *= s + +MeshViewTool().start(None) \ No newline at end of file diff --git a/sciapp/action/plugin/roi_tools.py b/sciapp/action/plugin/roi_tools.py new file mode 100644 index 00000000..d20002c7 --- /dev/null +++ b/sciapp/action/plugin/roi_tools.py @@ -0,0 +1,61 @@ +from .shp_tools import * +from .shpbase import BaseEditor +from ..tolact import ImageTool +from ...object import ROI + +class BaseROI(ImageTool): + def __init__(self, base): + base.__init__(self) + self.base = base + + def mouse_down(self, img, x, y, btn, **key): + if img.roi is None: img.roi = ROI() + else: img.roi.msk = None + self.base.mouse_down(self, img.roi, x, y, btn, **key) + + def mouse_up(self, img, x, y, btn, **key): + self.base.mouse_up(self, img.roi, x, y, btn, **key) + if not img.roi is None: + if len(img.roi.body)==0: img.roi = None + else: img.roi.msk = None + + def mouse_move(self, img, x, y, btn, **key): + self.base.mouse_move(self, img.roi, x, y, btn, **key) + + def mouse_wheel(self, img, x, y, d, **key): + self.base.mouse_wheel(self, img.roi, x, y, d, **key) + +class PolygonROI(BaseROI): + title = 'Polygon ROI' + def __init__(self): + BaseROI.__init__(self, PolygonEditor) + +class LineROI(BaseROI): + title = 'Line ROI' + def __init__(self): + BaseROI.__init__(self, LineEditor) + +class PointROI(BaseROI): + title = 'Point ROI' + def __init__(self): + BaseROI.__init__(self, PointEditor) + +class RectangleROI(BaseROI): + title = 'Rectangle ROI' + def __init__(self): + BaseROI.__init__(self, RectangleEditor) + +class EllipseROI(BaseROI): + title = 'Ellipse ROI' + def __init__(self): + BaseROI.__init__(self, EllipseEditor) + +class FreeLineROI(BaseROI): + title = 'Free Line ROI' + def __init__(self): + BaseROI.__init__(self, FreeLineEditor) + +class FreePolygonROI(BaseROI): + title = 'Free Polygon ROI' + def __init__(self): + BaseROI.__init__(self, FreePolygonEditor) \ No newline at end of file diff --git a/sciapp/action/plugin/shp_tools.py b/sciapp/action/plugin/shp_tools.py new file mode 100644 index 00000000..2d9b7f80 --- /dev/null +++ b/sciapp/action/plugin/shp_tools.py @@ -0,0 +1,243 @@ +from .shpbase import * +from ...util import geom_flatten + +def inbase(key, btn): + status = key['ctrl'], key['alt'], key['shift'] + return status == (1,1,0) or btn in {2,3} + +class PointEditor(BaseEditor): + title = 'Point Tool' + def __init__(self): + BaseEditor.__init__(self) + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if btn==1 and not key['alt'] and not key['ctrl']: + shp.body.append(Point([x,y])) + shp.dirty = True + +class LineEditor(BaseEditor): + title = 'Line Tool' + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn) and self.obj is None: + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + if self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + else: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + if btn==3 and not self.obj is None : + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + self.obj.dirty, shp.dirty, self.obj = True, True, None + del key['canvas'].marks['buffer'] + shp.dirty = True + +class PolygonEditor(BaseEditor): + title = 'Polygon Tool' + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn) and self.obj is None: + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + if self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + else: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + if btn==3 and not self.obj is None : + body = np.vstack((self.obj.body, [(x,y)])) + shp.body[-1] = Polygon(body) + if key['alt'] or key['shift']: + obj = shp.body.pop(-1) + rst = geom_union(shp.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + shp.body = layer.body + self.obj, shp.dirty = None, True + del key['canvas'].marks['buffer'] + shp.dirty = True + +class RectangleEditor(BaseEditor): + title = 'Rectangle Tool' + def __init__(self): + BaseEditor.__init__(self) + self.obj, self.p = None, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + self.obj = Rectangle([x, y, 0, 0]) + self.p = x, y + shp.body.append(self.obj) + + def mouse_up(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_up(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if self.p == (x, y) and not self.obj is None: + self.obj = self.p = None + return shp.body.pop(-1) + if (key['alt'] or key['shift']) and not self.obj is None: + obj = shp.body.pop(-1) + rst = geom_union(shp.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + shp.body = layer.body + self.obj, shp.dirty = None, True + if 'buffer' in key['canvas'].marks: + del key['canvas'].marks['buffer'] + + def mouse_move(self, shp, x, y, btn, **key): + BaseEditor.mouse_move(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if not self.obj is None: + self.obj.body[2:] = (x, y) - self.obj.body[:2] + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + self.obj.dirty = shp.dirty = True + +class EllipseEditor(BaseEditor): + title = 'Ellipse Tool' + def __init__(self): + BaseEditor.__init__(self) + self.obj, self.p = None, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1: + self.p = (x, y) + self.obj = Ellipse([x, y, 0, 0, 0]) + shp.body.append(self.obj) + + def mouse_up(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_up(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if self.p == (x, y) and not self.obj is None: + self.obj = self.p = None + return shp.body.pop(-1) + if (key['alt'] or key['shift']) and not self.obj is None: + obj = shp.body.pop(-1) + rst = geom_union(shp.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + shp.body = layer.body + self.obj, shp.dirty = None, True + if 'buffer' in key['canvas'].marks: + del key['canvas'].marks['buffer'] + + def mouse_move(self, shp, x, y, btn, **key): + BaseEditor.mouse_move(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if not self.obj is None: + ox, oy = self.p + self.obj.body[:] = [(x+ox)/2, (y+oy)/2, (x-ox)/2, (y-oy)/2, 0] + anchor = Points(mark(self.obj)[0], color=(255,0,0)) + key['canvas'].marks['buffer'] = anchor + shp.dirty = self.obj.dirty = True + +class FreeLineEditor(BaseEditor): + title = 'Free Line Tool' + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1 and self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + shp.dirty = True + + def mouse_up(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_up(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + self.obj = None + + def mouse_move(self, shp, x, y, btn, **key): + BaseEditor.mouse_move(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if not self.obj is None: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + self.obj.dirty = shp.dirty = True + +class FreePolygonEditor(BaseEditor): + title = 'Free Polygon Tool' + def __init__(self): + BaseEditor.__init__(self) + self.cur, self.n, self.obj = 0, 0, None + + def mouse_down(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_down(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1 and self.obj is None: + self.obj = Line([(x,y)]) + shp.body.append(self.obj) + shp.dirty = True + + def mouse_up(self, shp, x, y, btn, **key): + if inbase(key, btn): + BaseEditor.mouse_up(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if btn==1 and not self.obj is None: + body = np.vstack((self.obj.body, self.obj.body[0])) + shp.body[-1] = Polygon(body) + body = np.vstack((self.obj.body, [(x,y)])) + shp.body[-1] = Polygon(body) + if key['alt'] or key['shift']: + obj = shp.body.pop(-1) + rst = geom_union(shp.to_geom()) + if key['alt'] and not key['shift']: + rst = rst.difference(obj.to_geom()) + if key['shift'] and not key['alt']: + rst = rst.union(obj.to_geom()) + if key['shift'] and key['alt']: + rst = rst.intersection(obj.to_geom()) + layer = geom2shp(geom_flatten(rst)) + shp.body = layer.body + self.obj, shp.dirty = None, True + + def mouse_move(self, shp, x, y, btn, **key): + BaseEditor.mouse_move(self, shp, x, y, btn, **key) + if key['alt'] and key['ctrl']: return + if not self.obj is None: + self.obj.body = np.vstack((self.obj.body, [(x,y)])) + self.obj.dirty = shp.dirty = True \ No newline at end of file diff --git a/sciapp/action/plugin/shpbase.py b/sciapp/action/plugin/shpbase.py new file mode 100644 index 00000000..394488ef --- /dev/null +++ b/sciapp/action/plugin/shpbase.py @@ -0,0 +1,299 @@ +from ..tolact import ShapeTool +from sciapp.object import * +from sciapp.util import geom_union +from numpy.linalg import norm +import numpy as np + +def mark(shp, types = 'all'): + pts = [] + if not (types=='all' or shp.dtype in types): return pts + if shp.dtype == 'point': + pts.append([shp.body]) + if shp.dtype == 'points': + pts.append(shp.body) + if shp.dtype == 'line': + pts.append(shp.body) + if shp.dtype == 'lines': + pts.extend(shp.body) + if shp.dtype == 'polygon' and len(shp.body)==1: + pts.append(shp.body[0]) + if shp.dtype == 'polygons': + for i in shp.body: + if len(i) != 1: continue + pts.append(i[0]) + if shp.dtype == 'rectangle': + l,t,w,h = shp.body + ps = np.mgrid[l:l+w:3j, t:t+h:3j].T.reshape((-1,2)) + pts.append(ps) + if shp.dtype == 'rectangles': + for i in range(len(shp.body)): + l,t,w,h = shp.body[i] + ps = np.mgrid[l:l+w:3j, t:t+h:3j].T.reshape((-1,2)) + pts.append(ps) + if shp.dtype == 'ellipse': + x0, y0, l1, l2, ang = shp.body + mat = np.array([[np.cos(-ang),-np.sin(-ang)], + [np.sin(-ang),np.cos(-ang)]]) + ps = np.mgrid[-l1:l1:3j, -l2:l2:3j].T.reshape((-1,2)) + pts.append(mat.dot(ps.T).T + (x0, y0)) + if shp.dtype == 'ellipses': + for i in range(len(shp.body)): + x0, y0, l1, l2, ang = shp.body[i] + mat = np.array([[np.cos(-ang),-np.sin(-ang)], + [np.sin(-ang),np.cos(-ang)]]) + ps = np.mgrid[-l1:l1:3j, -l2:l2:3j].T.reshape((-1,2)) + pts.append(mat.dot(ps.T).T + (x0, y0)) + if shp.dtype == 'layer': + minl, obj = 1e8, None + for i in shp.body: + pts.extend(mark(i, types)) + return pts + +def pick_obj(shp, x, y, lim, types='all'): + obj, minl = None, lim + if not (types=='all' or shp.dtype in types): + return m, obj, minl + if shp.dtype == 'layer': + for i in shp.body: + o, l = pick_obj(i, x, y, lim, types) + if l < minl: + obj, minl = o, l + elif shp.dtype in 'polygons': + b = shp.to_geom().contains(Point([x, y]).to_geom()) + if b : return shp, 0 + else: + d = shp.to_geom().distance(Point([x, y]).to_geom()) + if d0: + pts = Points(np.vstack(pts), color=(255,0,0)) + key['canvas'].marks['anchor'] = pts + shp.dirty = True + + def mouse_move(self, shp, x, y, btn, **key): + self.cursor = 'arrow' + if self.status == 'move': + ox, oy = self.oldxy + up = (1,-1)[key['canvas'].up] + key['canvas'].move(key['px']-ox, (key['py']-oy)*up) + self.oldxy = key['px'], key['py'] + if key['alt'] and key['ctrl']: + self.status = 'pick' + if not 'anchor' in key['canvas'].marks: + pts = mark(shp) + if len(pts)>0: + pts = Points(np.vstack(pts), color=(255,0,0)) + key['canvas'].marks['anchor'] = pts + if 'anchor' in key['canvas'].marks: + m, obj, l = pick_point(key['canvas'].marks['anchor'], x, y, 5) + if not m is None: self.cursor = 'hand' + elif 'anchor' in key['canvas'].marks: + self.status = '' + del key['canvas'].marks['anchor'] + shp.dirty = True + if not self.pick_obj is None and not self.pick_m is None: + drag(self.pick_m, self.pick_obj, x, y) + pts = mark(self.pick_m) + if len(pts)>0: + pts = np.vstack(pts) + key['canvas'].marks['anchor'] = Points(pts, color=(255,0,0)) + self.pick_m.dirty = True + shp.dirty = True + if self.pick_obj is None and not self.pick_m is None: + offset(self.pick_m, x-self.p[0], y-self.p[1]) + pts = mark(self.pick_m) + if len(pts)>0: + pts = np.vstack(pts) + key['canvas'].marks['anchor'] = Points(pts, color=(255,0,0)) + self.p = x, y + self.pick_m.dirty = shp.dirty = True + + def mouse_wheel(self, shp, x, y, d, **key): + if d>0: key['canvas'].zoomout(x, y, coord='data') + if d<0: key['canvas'].zoomin(x, y, coord='data') \ No newline at end of file diff --git a/sciapp/action/readme.md b/sciapp/action/readme.md new file mode 100644 index 00000000..ef59122b --- /dev/null +++ b/sciapp/action/readme.md @@ -0,0 +1,115 @@ +# SciApp + +SciApp是一个交互式科学计算的后端框架,主要用于搭建科学分析应用。SciApp并不是算法库,也不包含任何界面,SciApp的目的是为算法类应用提供一个标准接口,具体如下: + +1. 定义了科学计算常用的数据结构封装类 +2. 对数据结构定义了一些基础操作函数 +3. 定义了一个通用Manager,将各种数据进行管理,聚合为一个App对象 +4. 定义了Action对象,可以对App对象进行操作 + + + +### Object模块 + +object模块定义了科学计算中常用的基础数据结构封装类,当然,如果仅仅为了计算,绝大多数时候,Numpy,Pandas等数据类型已经可以胜任,这里的封装,主要是面向交互与展示的,例如Image对象是图像数据,里面带了一个lut成员,用于在展示时映射成伪彩色。 + +1. Image:多维图像,基于Numpy +2. Table:表格,基于DataFrame +3. Shape: 点线面,任意多边形,可与GeoJson,Shapely互转 +4. Surface:三维表面 + + + +### Util模块 + +Util定义了一些针对Object数据类型的基础操作函数,这些函数也是为了完成交互与展示,并非为了数据分析。 + +1. imutil: 主要实现图像的快速采样,多图层融合等算法 + +2. shputil: 主要实现多边形与给定点之间的几何关系,用于鼠标编辑。 + + + +### App容器 + +Manager:通用管理器对象,类似一个键值对管理器,里面装入key, value, tag样式的三元组,可以对元素进行增删查改。 + +App:一个科学容器,里面包含若干Manager,用于管理App所持有的Image,Table等Object,同时定义了一套标准展示接口,例如show_img, get_img, close_img等,(Table,Shape类似) + + + +* [[Manager 对象]](./manager.md) 可以增删查改的对象容器 +* [[App 对象]]() 科研应用接口,各类Object的大容器 + + + +### Action模块 + +Action:对App对象的一个操作,例如获取当前图像,做某种滤波,然后从图像中获取某种信息,最后show一个Table处理。其通用模板是Action().start(app)。可以在其子类中重载start,对app进行任何操作。 + +* [如何衍生出图像滤波类Action]() +* [如何衍生出数据读写类Action]() +* [读取-处理-写入,完成具体工作]() + +以上例子是为了说明Action的作用机制,也展示了框架的构建思路,但并不意味着我们需要按照以上方法,从SciAction构建各种模板,我们已经构建了相当丰富,功能也更为完善的Action派生树,以下进行列举。 + + + +### Action的继承树 + +因为绝大多数的Action都和交互有关,而SciApp自身主要实现对象管理功能,交互只能通过print进行展示,所以很多功能这里只是列距,具体用法我们会在sciwx实现的一个App实例中展示。 + + + +**SciAction:** 所有Action的基类,start内获取app对象,进行处理 + +* ImageAction: 用于处理图像,自动获取当前图像,需要重载para,view进行交互,重载run进行图像处理 + +* TableAction: 用于处理表格,自动获取当前表格,需要重载para,view进行交互,重载run进行图像处理 + +* Tool: 工具,用于在某种控件上的鼠标交互 + + * ImageTool: 图像工具,例如画笔,魔术棒等,需要重载一系列鼠标事件(参数坐标已转入图像坐标系) + + * TableTool:表格工具,需要重载一系列鼠标事件(参数坐标已自动转如单元格行列) + * ShapeTool: 矢量编辑,例如点线面,多边形绘制(参数坐标已自动转如数据坐标系) + +**Advanced:** 这个包下面是一些高级的Action模板,也是我们扩展插件主要使用的 + +* dataio: 里面实现了Reader,Writer类的Action,我们只需要将读写函数注册给对于的Manager +* Free: 继承SciAction,添加了para, view交互,添加了多线程支持。 +* Filter: 继承ImageAction,主要用于做图像滤波,自动多通道,自带批量特性,多线程支持。 +* Simple: 继承ImageAction,主要用于图像操作,自带多线程支持。 +* Table: 继承TableAction,主要用于表格操作,比如表格统计,数据绘图等。 +* Macros: 将一段字符串作为宏执行,构造时传入字符串,start后依次质心。 + +**Plugins:** 这个包下面有一些带有具体功能,开箱即用的Action + +* filters:滤波类 + * Gaussian:高斯滤波 +* generalio: 数据读取类 + * bmp, jpg, png, tif格式图像的读写支持 +* ShapeTool: 矢量图像编辑工具 + * PointEditor: 编辑点 + * LineEditor: 编辑线 + * PolygonEditor: 编辑多边形 + * RectangleEditor: 编辑矩形 + * EllipseEditor: 编辑椭圆 + * FreeLineEditor: 编辑任意线 + * FreePolygonEditor: 编辑任意多边形 + +* ROITool: 类似于Shape,但作用对象是图像上的ROI + * PointEditor: 编辑点 + * LineEditor: 编辑线 + * PolygonEditor: 编辑多边形 + * RectangleEditor: 编辑矩形 + * EllipseEditor: 编辑椭圆 + * FreeLineEditor: 编辑任意线 + * FreePolygonEditor: 编辑任意多边形 + +* MeasureTool: 类似于Shape,绘制同时会展示数值 + * CoordinateTool: 坐标测量 + * DistanceTool: 距离测量 + * AngleTool: 角度测量 + * SlopeTool: 梯度测量 + * AreaTool: 面积测量 \ No newline at end of file diff --git a/sciapp/action/tabact.py b/sciapp/action/tabact.py new file mode 100644 index 00000000..1a6147b9 --- /dev/null +++ b/sciapp/action/tabact.py @@ -0,0 +1,42 @@ +from .action import SciAction + +class TabAction(SciAction): + title = 'Table Action' + note, para, view = [], None, None + + def __init__(self): pass + + def show(self): + tps, data, snap = self.tps, self.tps.data, self.tps.snap + f = lambda p: self.run(tps, data, snap, p) or tps.update() + return self.app.show_para(self.title, self.para, self.view, f, on_ok=None, + on_cancel=lambda x=self.tps:self.cancel(x), + preview='preview' in self.note, modal=True) + + def cancel(self, tps): + tps.data[:] = pts.snap + tps.update() + + def run(self, tps, snap, data, para = None): + print('I am running!!!') + + def start(self, app, para=None, callback=None): + self.app, self.tps = app, app.get_table() + if 'auto_snap' in self.note: + if 'auto_msk' in self.note: mode = True + elif 'msk_not' in self.note: mode = False + else: mode = None + self.tps.snapshot(mode, 'num_only' in self.note) + if para!=None: + self.ok(self.tps, para, callback) + elif self.view==None: + if not self.__class__.show is Table.show: + if self.show(): + self.run(self.tps, para, callback) + else: self.ok(self.tps, para, callback) + elif self.modal: + if self.show(): + self.ok(self.tps, para, callback) + else:self.cancel(self.tps) + if not self.dialog is None: self.dialog.Destroy() + else: self.show() \ No newline at end of file diff --git a/sciapp/action/tolact.py b/sciapp/action/tolact.py new file mode 100644 index 00000000..faa85043 --- /dev/null +++ b/sciapp/action/tolact.py @@ -0,0 +1,126 @@ +from .action import SciAction + +class Tool(SciAction): + title = 'Base Tool' + default = None + cursor = 'arrow' + view = None + para = None + + def config(self): pass + def mouse_down(self, canvas, x, y, btn, **key): pass + def mouse_up(self, canvas, x, y, btn, **key): pass + def mouse_move(self, canvas, x, y, btn, **key): pass + def mouse_wheel(self, canvas, x, y, d, **key): pass + def start(self, app, para=None, callafter=None): + self.app, self.default = app, self + if para == 'local': return self + if not app is None: app.tool = self + +class DefaultTool(Tool): + title = 'Move And Scale' + def __init__(self): + self.oldxy = None + + def mouse_down(self, obj, x, y, btn, **key): + if btn==1: self.oldxy = key['px'], key['py'] + if btn==3: key['canvas'].fit() + + def mouse_up(self, obj, x, y, btn, **key): + self.oldxy = None + + def mouse_move(self, obj, x, y, btn, **key): + if not hasattr(self, 'oldxy') or self.oldxy is None: return + ox, oy = self.oldxy + up = (1,-1)[key['canvas'].up] + key['canvas'].move(key['px']-ox, (key['py']-oy)*up) + self.oldxy = key['px'], key['py'] + + def mouse_wheel(self, obj, x, y, d, **key): + if d>0: key['canvas'].zoomout(x, y, coord='data') + if d<0: key['canvas'].zoomin(x, y, coord='data') + + def start(self, app, para=None): + self.app = app + if para == 'local': return self + Tool.default = self + #if not app is None: app.tool = self + +class ImageTool(DefaultTool): + default = None + title = 'Image Tool' + + def mouse_move(self, img, x, y, btn, **key): + if img.img is None: return + DefaultTool.mouse_move(self, img, x, y, btn, **key) + if self.app is None: return + r, c = int(y), int(x) + if (r>0) & (c>0) & (r ') + if i[0]==int: para[i[1]] = int(input(i[4]+': ? '+i[5]+' ')) + if i[0]==float: para[i[1]] = float(input(i[4]+': ? '+i[5]+' ')) + if i[0]=='slide': para[i[1]] = float(input(i[4]+': ? ')) + if i[0]==bool: para[i[1]] = bool(input(i[2]+': ')) + if i[0]==list: para[i[1]] = i[3](input('%s %s: %s'%(i[4],i[5],i[2])+' ')) + if i[0]=='chos':para[i[1]] = input('%s:%s '%(i[3],i[2])).split(',') + if i[0]=='color': para[i[1]] = eval(input(i[2]+': ? '+i[3]+' ')) + return para + + def run_macros(self, cmd, callafter=None): + cmds = [i for i in cmd] + def one(cmds, after): + cmd = cmds.pop(0) + if not isinstance(cmd, str): title, para = cmd + else: title, para = eval('"'+cmd.replace('>', '",')) + plg = self.plugin_manager.get(name=title)() + after = lambda cmds=cmds: one(cmds, one) + if len(cmds)==0: after = callafter + plg.start(self, para, after) + one(cmds, None) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def record_macros(self, cmd): + print('>>>', cmd) \ No newline at end of file diff --git a/sciapp/demo/demo1_manager.py b/sciapp/demo/demo1_manager.py new file mode 100644 index 00000000..3788a0fc --- /dev/null +++ b/sciapp/demo/demo1_manager.py @@ -0,0 +1,117 @@ +import sys +sys.path.append('../../') + +from sciapp import Manager + +def add_get_test(): + ''' + add(name, object, tag=None): add one name, object pair (at first). + has(name=None, tag=None, obj=None): check exists, None matches any. + + get(name=None, tag=None): get the first matched object, or None. + None matches any, so get() return the first object. + gets(name=None, tag=None): get all matched [(name, obj, tag)...] + None matches any, so gets() return all records. + remove(name=None, tag=None, obj=None): remove all matched records + None matches any, so remove() would clear the list. + ''' + print('manager test add, get, gets:') + manager = Manager() + # add a name, value pair + manager.add(name='one', obj='object1') + # you can also omit the name, obj + manager.add('two', 'object2') + + print(manager.names()) + # >>> ['two', 'one'] , the later inserted is at first! + print(manager.get()) + # >>> object1 , return the first object, or None + print(manager.get(name='one')) + # >>> object1 , return the first matched object, or None + print(manager.gets()) + # >>> [('two', 'object2', None), ('one', 'object1', None)] + manager.remove(name='two') + # >>> [('one', 'object1', None)] + print(manager.gets()) + print() + +def tag_test(): + ''' + another information for searching. default tag is None. + ''' + print('manager test with tag:') + manager = Manager() + manager.add('one', 'object1', 'low') + manager.add('two', 'object2', 'low') + manager.add('one', 'OBJECT1', 'up') + manager.add('two', 'OBJECT2', 'up') + + print(manager.gets(tag='low')) + # >>> [('two', 'object2', 'low'), ('one', 'object1', 'low')] + print(manager.gets(tag='up')) + # >>> [('two', 'OBJECT2', 'up'), ('one', 'OBJECT1', 'up')] + print() + +def order_test(): + ''' + add method puts object at first, get() means the lasted one. + you can use active to move record to the first. + ''' + print('manager order test:') + manager = Manager() + manager.add('one', 'object1') + manager.add('two', 'object2') + manager.add('three', 'object3') + print(manager.names()) + # >>> ['three', 'two', 'one'] + manager.active('one') + print(manager.names()) + # >>> ['one', 'three', 'two'] + print() + +def unique_test(): + ''' + unique means add a object with same name and tag would replace the old one. + if you want allow the duplicate records, please use unique=False. + ''' + print('manager unique test:') + manager = Manager() + manager.add('one', 'object1') + manager.add('one', 'OBJECT1') + print(manager.gets()) + # >>> [('one', 'OBJECT1', None)] + + manager = Manager(unique=False) + manager.add('one', 'object1') + manager.add('one', 'OBJECT1') + print(manager.gets()) + # >>> [('one', 'OBJECT1', None), ('one', 'object1', None)] + print() + +def io_test(): + ''' + manager object has read and write method to transform with file. + ''' + print('show how to read and write a manager:') + manager = Manager() + manager.add('one', 'object1') + manager.add('two', 'object2') + manager.write('manager.json') + + manager = Manager() + manager.read('manager.json') + print(manager.gets()) + # >>> [('one', 'object1', None), ('two', 'object2', None)] + + # you can also pass path parameter when init a Manager + manager = Manager(path='manager.json') + print(manager.gets()) + # >>> [('one', 'object1', None), ('two', 'object2', None)] + print() + +if __name__ == '__main__': + add_get_test() + tag_test() + order_test() + unique_test() + io_test() \ No newline at end of file diff --git a/sciapp/demo/demo2_app.py b/sciapp/demo/demo2_app.py new file mode 100644 index 00000000..f819c91f --- /dev/null +++ b/sciapp/demo/demo2_app.py @@ -0,0 +1,53 @@ +import sys +sys.path.append('../../') +from sciapp import App + +def basic_test(): + app = App() + # alert a message + app.alert('Hello!', title='SciApp') + # show a text, here just print it + app.show_txt('Hello', title='SciApp') + # show a markdown text, here just print it + app.show_md('Hello', title='SciApp') + # yes or no + rst = app.yes_no('Are you ok?', 'SciApp') + print(rst) + +def para_test(): + app = App() + para = {'name':'', 'age':5} + view = [(str, 'name', 'your', 'name'), + (int, 'age', (0,120), 0, 'your', 'age')] + rst = app.show_para('Personal Information', para, view) + # >>> your: ? name yxdragon + # >>> your: ? age 32 + print(rst) + # >>> {'name':'yxdragon', 'age':32} + +def object_test(): + ''' + there is a manager for every type of object. + such as img_manager, we can call app's: + show_img, close_img, active_img, get_img + ''' + from sciapp.object import Image + from skimage.data import camera + + app = App() + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + # >>> UINT8 512x512 S:1/1 C:0/1 0.25M + print(app.get_img()) + # >>> + print(app.img_names()) + # >>> ['camera'] + app.close_img('camera') + # >>> close image: camera + print(app.img_names()) + # >>> [] + +if __name__ == '__main__': + basic_test() + para_test() + object_test() diff --git a/sciapp/demo/demo3_img_action.py b/sciapp/demo/demo3_img_action.py new file mode 100644 index 00000000..69819dd7 --- /dev/null +++ b/sciapp/demo/demo3_img_action.py @@ -0,0 +1,136 @@ +import sys +sys.path.append('../../') +from sciapp import App +from sciapp.object import Image +from skimage.data import camera +from scipy.ndimage import gaussian_filter +from skimage.feature import canny +import matplotlib.pyplot as plt + +class SciAction: + '''base action, just has a start method, alert a hello''' + name = 'SciAction' + + def start(self, app, para=None): + self.app = app + app.alert('Hello, I am SciAction!\n') + +def action_demo1(): + app = App() + SciAction().start(app) + +class GaussianAction1(SciAction): + '''get current image object, and do a gaussian filter with sigma 5''' + name = 'GaussianAction1' + + def start(self, app, para=None): + image = app.get_img() + image.img[:] = gaussian_filter(image.img, 5) + +def action_demo2(): + app = App() + + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + GaussianAction1().start(app) + + plt.subplot(121).imshow(camera()) + plt.subplot(122).imshow(image.img) + plt.show() + +class GaussianAction3(SciAction): + '''follow the version 2, use show para to get sigma''' + name = 'GaussianAction3' + + def start(self, app, para=None): + image = app.get_img() + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + app.show_para('GaussianAction3', para, view) + image.img[:] = gaussian_filter(image.img, para['sigma']) + +def action_demo3(): + app = App() + + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + GaussianAction3().start(app) + + plt.subplot(121).imshow(camera()) + plt.subplot(122).imshow(image.img) + plt.show() + +class GaussianAction4(SciAction): + '''split para, view to class field, and split gaussian to run method''' + name = 'GaussianAction4' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + + def start(self, app, para=None): + image = app.get_img() + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) + +def action_demo4(): + app = App() + + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + GaussianAction3().start(app) + + plt.subplot(121).imshow(camera()) + plt.subplot(122).imshow(image.img) + plt.show() + +class ImageAction(SciAction): + ''' + this is a general image filter action + we just need to define the para, view + and overwrite the run method + the start method will help us to check if there is a image opened. + and show parameter if needed (para, view is redefined) + then call the run method with current image and input parameter. + ''' + name = 'ImageAction' + para, view = None, None + + def run(self, img, para=None):pass + + def start(self, app, para=None): + image = app.get_img() + if image is None: return app.alert('no image!') + if self.para != None: + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) + +class Gaussian(ImageAction): + '''now a gaussian filter should be like this''' + name = 'Gaussian' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + +def action_demo5(): + app = App() + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + Gaussian().start(app) + + plt.subplot(121).imshow(camera()) + plt.subplot(122).imshow(image.img) + plt.show() + +if __name__ == '__main__': + action_demo1() + action_demo2() + action_demo3() + action_demo4() + action_demo5() + # action_demo6() + + diff --git a/sciapp/demo/demo4_io_action.py b/sciapp/demo/demo4_io_action.py new file mode 100644 index 00000000..efd1cfc3 --- /dev/null +++ b/sciapp/demo/demo4_io_action.py @@ -0,0 +1,109 @@ +import sys +sys.path.append('../../') + +from sciapp import App, Manager +from sciapp.action import SciAction +import os.path as osp +from skimage.io import imread, imsave +from skimage.data import camera +from pandas import read_csv +import pandas as pd +import numpy as np + +# overwrite the imread, read_csv, to make the demo works +# you can annotation it, but you need give a true path in this demo +def imread(path): return camera() +def read_csv(path): return pd.DataFrame(np.arange(25).reshape(5,5)) + +class ImageReader1(SciAction): + '''read a image and show it''' + name = 'ImageReader1' + + def start(self, app): + path = input('input the file path, or just a.png for test:') + name, ext = osp.splitext(osp.split(path)[1]) + app.show_img([imread(path)], name) + +def image_read_demo1(): + app = App() + ImageReader1().start(app) + print(app.img_names()) + +ReaderManager = Manager() + +class ImageReader(SciAction): + '''supporting different image format''' + name = 'ImageReader' + + def start(self, app): + # input the file path, or just a.png for test + path = app.get_path() + name, ext = osp.splitext(osp.split(path)[1]) + reader = ReaderManager.get(ext[1:]) + if reader is None: + return app.alert('no reader for %s!'%ext[1:]) + app.show_img([reader(path)], name) + +ReaderManager.add('png', imread) +# if you want support other format, just add it to the manager +# ReaderManager.add('dicom', 'xxx') + +def image_read_demo2(): + app = App() + ImageReader().start(app) + +class FileReader(SciAction): + '''supporting different type, image/table or other...''' + name = 'FileReader' + + def start(self, app): + # input the file path, or just a.png/a.csv for test + path = app.get_path() + name, ext = osp.splitext(osp.split(path)[1]) + readers = ReaderManager.gets(ext[1:]) + if len(readers)!=1: + return app.alert('no reader or many readers for %s!'%ext[1:]) + else: key, reader, tag = readers[0] + if tag=='img':app.show_img([reader(path)], name) + if tag=='tab':app.show_table(reader(path), name) + +ReaderManager = Manager() +ReaderManager.add('png', imread, tag='img') +ReaderManager.add('csv', read_csv, tag='tab') + +#ReaderManager.add('dicom', 'xxx') + +def image_read_demo3(): + app = App() + FileReader().start(app) + print('images:', app.img_names(), 'tables:', app.table_names()) + +WriterManager = Manager() + +class ImageWriter(SciAction): + '''write current image''' + name = 'ImageWriter' + + def start(self, app): + img = app.get_img() + if img is None: return app.alert('no image') + # input the file path to save + path = app.get_path() + name, ext = osp.splitext(osp.split(path)[1]) + writer = WriterManager.get(ext[1:]) + if writer is None: + return app.alert('no writer for %s!'%ext[1:]) + writer(path, img.img) + +WriterManager.add('png', imsave) + +def img_write_demo4(): + app = App() + FileReader().start(app) + ImageWriter().start(app) + +if __name__ == '__main__': + image_read_demo1() + image_read_demo2() + image_read_demo3() + img_write_demo4() diff --git a/sciapp/demo/demo5_process_action.py b/sciapp/demo/demo5_process_action.py new file mode 100644 index 00000000..16dffd16 --- /dev/null +++ b/sciapp/demo/demo5_process_action.py @@ -0,0 +1,76 @@ +import sys +sys.path.append('../../') + +from sciapp import App, Manager +from sciapp.action import SciAction +import os.path as osp +from skimage.io import imread, imsave +from skimage.data import camera +from scipy.ndimage import gaussian_filter + +# overwrite the imread, read_csv, to make the demo works +# you can annotation it, but you need give a true path in this demo +def imread(path): return camera() + +ReaderManager = Manager() +ReaderManager.add('png', imread) + +class ImageReader(SciAction): + '''supporting different image format''' + name = 'ImageReader' + + def start(self, app, para=None): + path = input('input the file path, or just a.png for test:') + name, ext = osp.splitext(osp.split(path)[1]) + reader = ReaderManager.get(ext[1:]) + if reader is None: + return app.alert('no reader for %s!'%ext[1:]) + app.show_img([reader(path)], name) + +WriterManager = Manager() +WriterManager.add('png', imsave) + +class ImageWriter(SciAction): + '''write current image''' + name = 'ImageWriter' + + def start(self, app, para=None): + img = app.get_img() + if img is None: return app.alert('no image') + path = input('input the file path to save:') + name, ext = osp.splitext(osp.split(path)[1]) + writer = WriterManager.get(ext[1:]) + if writer is None: + return app.alert('no writer for %s!'%ext[1:]) + writer(path, img.img) + +class ImageAction(SciAction): + name = 'ImageAction' + para, view = None, None + + def run(self, img, para):pass + + def start(self, app, para=None): + image = app.get_img() + if image is None: return app.alert('no image!') + if self.para != None: + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) + +class Gaussian(ImageAction): + '''now a gaussian filter should be like this''' + name = 'Gaussian' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + +def read_gaussian_write(): + app = App() + ImageReader().start(app) + Gaussian().start(app) + ImageWriter().start(app) + +if __name__ == '__main__': + read_gaussian_write() diff --git a/sciapp/demo/demo6_adv_action.py b/sciapp/demo/demo6_adv_action.py new file mode 100644 index 00000000..b1af699c --- /dev/null +++ b/sciapp/demo/demo6_adv_action.py @@ -0,0 +1,66 @@ +import sys +sys.path.append('../../') + +from sciapp.action.advanced import dataio, Filter +from scipy.ndimage import gaussian_filter +from skimage.io import imread, imsave +from skimage.data import camera +from sciapp import App + +def imread(path): return camera() + +for i in ('bmp', 'jpg', 'tif', 'png'): + dataio.ReaderManager.add(i, imread, 'img') + dataio.WriterManager.add(i, imsave, 'img') + +class OpenFile(dataio.Reader): + title = 'Open' + + def load(self): + self.filt = sorted(dataio.ReaderManager.names()) + return True + +class SaveImage(dataio.ImageWriter): + title = 'Save Image' + + def load(self, ips): + self.filt = sorted(dataio.WriterManager.names()) + return True + +class Gaussian(Filter): + title = 'Gaussian' + note = ['all', 'auto_msk', 'auto_snap','preview'] + para = {'sigma':2} + view = [(float, 'sigma', (0,30), 1, 'sigma', 'pix')] + + def run(self, ips, snap, img, para = None): + gaussian_filter(snap, para['sigma'], output=img) + +def io_process_test(): + app = App(asyn=False) + OpenFile().start(app) + Gaussian().start(app) + SaveImage().start(app) + +def macros_test(): + app = App(asyn=False) + for i in [OpenFile, SaveImage, Gaussian]: + app.add_plugin(i.title, i) + + app.run_macros([('Open', None), + ('Gaussian', None), + ('Save Image', None)]) + +def macros_with_para(): + app = App(asyn=False) + for i in [OpenFile, SaveImage, Gaussian]: + app.add_plugin(i.title, i) + + app.run_macros([('Open', {'path':'camera.png'}), + ('Gaussian', {'sigma':2}), + ('Save Image', {'path':'blur.png'})]) + +if __name__ == '__main__': + #io_process_test() + #macros_test() + macros_with_para() diff --git a/sciapp/doc/cn_app.md b/sciapp/doc/cn_app.md new file mode 100644 index 00000000..7daf37e8 --- /dev/null +++ b/sciapp/doc/cn_app.md @@ -0,0 +1,104 @@ +# App + +App是一个科研应用接口,里面有若干Manager,用于管理应用进行中的各种Object,同时App定义了show_img, active_img, close_img, get_img, img_names等方法,对于table等其他类型数据有类似支持。这里的App仅仅实现了容器管理功能,而对于各种交互功能,都只是print实现。因而需要在某个示例中,用UI框架重载这些方法。 + + + +### 方法列举: + +* **alert(self, info, title='sciapp'):** 弹出一个提示框,需要用户确认 + +* **yes_no(self, info, title='sciapp'):** 要求用户输入True/False + +* **show_txt(self, cont, title='sciapp'):** 对用户进行文字提示 + +* **show_md(self, cont, title='sciapp'):** 以MarkDown语法书写,向用户弹出格式化文档 + +* **show_para(self, title, para, view, on_handle=None, on_ok=None, on_cancel=None, on_help=None, preview=False, modal=True):** 展示交互对话框,para是参数字典,view指定了交互方式。而在这个命令行版的App对象中,只能通过打印完成交互,因而其他参数这里没有作用。但App是一个交互式应用接口,对于界面应用,其他参数分别是,参数变化回调,对话框确认,取消回调,是否自动添加预览选项,对话框是否以模态方式展示 + + --- + + **以下功能通过app.img_manager管理器实现** + +* **show_img(self, img, title=None):** 展示一个Image对象,并添加到app.img_manager。 + +* **get_img(self, title=None):** 根据title获取Image,如果缺省则返回manager的第一个Image + +* **img_names(self):** 返回当前app持有的Image对象名称列表 + +* **active_img(self, title=None):** 将指定名称的Image对象置顶,以便于get_img可以优先获得 + +* **close_img(self, title=None):** 关闭指定图像,并从app.img_manager移除 + + --- + **以下功能通过app.table_manager管理器实现** + +* **show_table(self, tab, title=None):** 展示一个Table对象,并添加到app.tab_manager。 + +* **get_table(self, title=None):** 根据title获取Table,如果缺省则返回manager的第一个Table + +* **table_names(self):** 返回当前app持有的Table对象名称列表 + +* **active_table(self, title=None):** 将指定名称的Table对象置顶,以便于get_tab可以优先获得 + +* **close_table(self, title=None):** 关闭指定图像,并从app.tab_manager移除 + + + +### 用法举例 + +这个例子演示app的一些基础交互功能,由于命令行只有简单的打印功能,因而这几个功能这里本质都是print + +```python +def basic_test(): + app = App() + # alert a message + app.alert('Hello!', title='SciApp') + # show a text, here just print it + app.show_txt('Hello', title='SciApp') + # show a markdown text, here just print it + app.show_md('Hello', title='SciApp') + # yes or no + rst = app.yes_no('Are you ok?', 'SciApp') + print(rst) +``` + + + +这个例子演示app进行一组参数交互 + +```python +def para_test(): + app = App() + para = {'name':'', 'age':5} + view = [(str, 'name', 'your', 'name'), + (int, 'age', (0,120), 0, 'your', 'age')] + rst = app.show_para('Personal Information', para, view) + # >>> your: ? name yxdragon + # >>> your: ? age 32 + print(rst) + # >>> {'name':'yxdragon', 'age':32} +``` + + + +这个例子用Image说明,通过app对象的show,get,close方法来展示图像 + +```python +def object_test(): + from sciapp.object import Image + from skimage.data import camera + + app = App() + image = Image([camera()], 'camera') + app.show_img(image, 'camera') + # >>> UINT8 512x512 S:1/1 C:0/1 0.25M + print(app.get_img()) + # >>> + print(app.img_names()) + # >>> ['camera'] + app.close_img('camera') + # >>> close image: camera + print(app.img_names()) + # >>> [] +``` diff --git a/sciapp/doc/cn_imgaction.md b/sciapp/doc/cn_imgaction.md new file mode 100644 index 00000000..99718322 --- /dev/null +++ b/sciapp/doc/cn_imgaction.md @@ -0,0 +1,137 @@ +# ImageAction + +App是科学应用容器,具备各种数据类型的展示,关闭功能,并对其进行管理。SciAction是对app对象的某种操作,而后续SciApp功能的丰富,主要就是依赖于Action的丰富。 + + + +### 原始SciAction + +SciAction是对app对象的某种操作,接口函数非常简单,只有一个 **start(self, app, para)**,你可以在start内对app对象进行各种操作。而原始的SciApp只是用app对象弹出了一个提示。 + +```python +class SciAction: + '''base action, just has a start method, alert a hello''' + name = 'SciAction' + + def start(self, app, para=None): + self.app = app + app.alert('Hello, I am SciAction!\n') + +app = App() +SciAction().start(app) +``` + + + +### 用SciAction实现滤波 + +这里我们在start内,获取当前图像,并对其进行一个sigma为5的高斯滤波 + +```python +class GaussianAction1(SciAction): + name = 'GaussianAction1' + + def start(self, app, para=None): + image = app.get_img() + image.img[:] = gaussian_filter(image.img, 5) + +app = App() +from skimage.data import camera +app.show_img([camera()], 'camera') +GaussianAction1().start(app) +``` + + + +### 带参数交互的滤波 + +这里我们使用app.show_para来和用户进行交互,获取参数。当然,这个例子中,我们可以使用python自带的input函数,然而app对象作为通用接口,我们在action中进来使用通用函数,以便在App对象的界面实现中依然可以使用。 + +```python +class GaussianAction3(SciAction): + name = 'GaussianAction3' + + def start(self, app, para=None): + image = app.get_img() + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + app.show_para('GaussianAction3', para, view) + image.img[:] = gaussian_filter(image.img, para['sigma']) + +app = App() +from skimage.data import camera +app.show_img([camera()], 'camera') +GaussianAction3().start(app) +``` + + + +### 参数标准化分离 + +在上一个例子的基础上,我们将para,view提升为类变量,把滤波函数提出来,定义为run函数,这样做的好处是,start部分就相对固定化了。 + +```python +class GaussianAction4(SciAction): + name = 'GaussianAction4' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + + def start(self, app, para=None): + image = app.get_img() + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) + +app = App() +from skimage.data import camera +app.show_img([camera()], 'camera') +GaussianAction4().start(app) +``` + + + +### ImageAction 初步版 + +上一个版本已经非常完善,我们把具体功能移除,形成一个抽象版的ImageAction,start内做如下事情: + +1. 获取当前图像,如果没有打开图像则弹出提示,终止后续 +2. 判断是否有参数,如果有则调用show_para进行交互 +3. 将当前图像和交互完成后的para送入run进行处理 + +```python +class ImageAction(SciAction): + name = 'ImageAction' + para, view = None, None + + def run(self, img, para=None):pass + + def start(self, app, para=None): + image = app.get_img() + if image is None: return app.alert('no image!') + if self.para != None: + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) +``` + +现在我们的Gaussian滤波器写成了这样,只需继承ImageAction,定义para,view,然后重载run函数即可。 + +```python +class Gaussian(ImageAction): + '''now a gaussian filter should be like this''' + name = 'Gaussian' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + +app = App() +from skimage.data import camera +app.show_img([camera()], 'camera') +Gaussian().start(app) +``` + +**备注:**本章只是为了讲解Action如何作用,并且如何通过逐层继承,使得Action子类分化出越来越具体的功能,但并不是说我们需要按照这里的方法从底层搭建Action,况且这里的ImageAction依然不够完善,比如可以继续添加图像类型的判定,多线程支持等。好在advanced里面已经定义好了更为丰富的Action模板,我们可以直接继承高级模板。 + diff --git a/sciapp/doc/cn_ioaction.md b/sciapp/doc/cn_ioaction.md new file mode 100644 index 00000000..db01f89a --- /dev/null +++ b/sciapp/doc/cn_ioaction.md @@ -0,0 +1,88 @@ +# IOAction + +继上一篇ImageAction之后,其实可以类似的拓展到TableAction,然而上一篇的例子中的图像,我们都是利用代码show,进而载入到app中的,本章我们就来编写数据读写的IOAction。 + + + +### 原始实现 + +我们在start内部获取用户输入的路径,然后解析出文件名和扩展名,调用app.show_img进行展示 + +```python +from skimage.io import imread + +class ImageReader1(SciAction): + '''read a image and show it''' + name = 'ImageReader1' + + def start(self, app, para=None): + path = input('input the file path, or just a.png for test:') + name, ext = osp.splitext(osp.split(path)[1]) + app.show_img([imread(path)], name) + +app = App() +ImageReader1().start(app) +``` + + + +### 支持多种格式数据 + +这个例子我们通过扩展名对读取方法进行管理,放入ReadManager,这样当添加新的读取格式时,只需将扩展名和读取方法加入ReaderManager。同样我们这里使用了get_path函数来获取路径,这同样是为了后续界面版本的App可以通用。 + +```python +from skimage.io import imread +ReaderManager = Manager() + +class ImageReader(SciAction): + name = 'ImageReader' + + def start(self, app, para=None): + # input the file path, or just a.png for test + path = app.get_path() + name, ext = osp.splitext(osp.split(path)[1]) + reader = ReaderManager.get(ext[1:]) + if reader is None: + return app.alert('no reader for %s!'%ext[1:]) + app.show_img([reader(path)], name) + +ReaderManager.add('png', imread) +ReaderManager.add('jpg', imread) +# ReaderManager.add('dcm', read_dicom) other format + +app = App() +ImageReader().start(app) +``` + + + +### ImageWriter + +保存操作和打开类似,不同的是需要获取当前图像,其实某种意义上,可以当作一个ImageAction。 + +```python +from skimage.io import imsave +WriterManager = Manager() + +class ImageWriter(SciAction): + '''write current image''' + name = 'ImageWriter' + + def start(self, app): + img = app.get_img() + if img is None: return app.alert('no image') + # input the file path to save + path = app.get_path() + name, ext = osp.splitext(osp.split(path)[1]) + writer = WriterManager.get(ext[1:]) + if writer is None: + return app.alert('no writer for %s!'%ext[1:]) + writer(path, img.img) + +WriterManager.add('png', imsave) +``` + + + +**备注:**本章只是为了讲解Action如何作用,并且如何通过逐层继承,使得Action子类分化出越来越具体的功能,但并不是说我们需要按照这里的方法从底层搭建Action,况且这里的IOAction依然不够完善,具体我们可以直接使用advanced.dataio模板。 + diff --git a/sciapp/doc/cn_iprocesso.md b/sciapp/doc/cn_iprocesso.md new file mode 100644 index 00000000..96a3d202 --- /dev/null +++ b/sciapp/doc/cn_iprocesso.md @@ -0,0 +1,94 @@ +# Process Demo + +我们这里用一个具体的例子,演示如何读取图像,滤波处理,保存图像。我们结合前两节的内容, + + + +### IO操作 + +如同前一节,我们添加ImageReader, ImageWriter。 + +```python +from sciapp import App, Manager +from sciapp.action import SciAction +import os.path as osp +from skimage.io import imread, imsave +from skimage.data import camera +from scipy.ndimage import gaussian_filter + +ReaderManager = Manager() +ReaderManager.add('png', imread) + +class ImageReader(SciAction): + '''supporting different image format''' + name = 'ImageReader' + + def start(self, app, para=None): + path = input('input the file path, or just a.png for test:') + name, ext = osp.splitext(osp.split(path)[1]) + reader = ReaderManager.get(ext[1:]) + if reader is None: + return app.alert('no reader for %s!'%ext[1:]) + app.show_img([reader(path)], name) + +WriterManager = Manager() +WriterManager.add('png', imsave) + +class ImageWriter(SciAction): + '''write current image''' + name = 'ImageWriter' + + def start(self, app, para=None): + img = app.get_img() + if img is None: return app.alert('no image') + path = input('input the file path to save:') + name, ext = osp.splitext(osp.split(path)[1]) + writer = WriterManager.get(ext[1:]) + if writer is None: + return app.alert('no writer for %s!'%ext[1:]) + writer(path, img.img) +``` + + + +### Gaussian滤镜 + +如同前一节,我们添加Gaussian滤波器。然后我们依次start三个Action,按照提示,输入读取图像路径,高斯滤波sigma,输出图像路径,最后完成。 + +```python +class ImageAction(SciAction): + name = 'ImageAction' + para, view = None, None + + def run(self, img, para):pass + + def start(self, app, para=None): + image = app.get_img() + if image is None: return app.alert('no image!') + if self.para != None: + app.show_para(self.name, self.para, self.view) + self.run(image.img, self.para) + +class Gaussian(ImageAction): + '''now a gaussian filter should be like this''' + name = 'Gaussian' + para = {'sigma':5} + view = [(int, 'sigma', (0,30), 0, 'sigma', 'px')] + + def run(self, img, para): + img[:] = gaussian_filter(img, para['sigma']) + + +app = App() +ImageReader().start(app) +Gaussian().start(app) +ImageWriter().start(app) + +# >>> input the file path, or just a.png for test:camera.png +# >>> UINT8 512x512 S:1/1 C:0/1 0.25M +# >>> sigma: ? px 5 +# >>> input the file path to save:blur.png +``` + +**备注:**本章只是为了讲解Action如何作用,并且如何通过逐层继承,使得Action子类分化出越来越具体的功能,但并不是说我们需要按照这里的方法从底层搭建Action,况且这里的IOAction依然不够完善,具体我们可以直接使用advanced.dataio模板。 + diff --git a/sciapp/doc/cn_manager.md b/sciapp/doc/cn_manager.md new file mode 100644 index 00000000..1f957eed --- /dev/null +++ b/sciapp/doc/cn_manager.md @@ -0,0 +1,139 @@ +# Manager + +Manager是一个通用容器,类似一个内存数据库,负责对象的增删查改 + + + +### 方法列举: + +* **add(self, name, obj, tag=None):** 添加一个对象,tag作为可选标签,对象添加在有序列表的最前面 + +* **adds(self, objs):** 批量添加name, obj, tag列表 + +* **gets(self, name=None, tag=None, obj=None):** 获取满足条件的对象,返回name, obj, tag列表,None表示不作为条件 + +* **get(self, name=None, tag=None, obj=None):** 获取满足条件的第一个object,如果没有满足返回None,空缺参数用来获取第一个对象。 + +* **has(self, name=None, tag=None, obj=None):** 返回是否存在匹配条件的对象 + +* **active(self, name=None, tag=None, obj=None):** 将满足筛选条件的记录移动到列表最前 + +* **set(self, name, obj, tag=None):** 通过name,tag筛选记录,将obj赋值 + +* **remove(self, name=None, tag=None, obj=None):** 删除满足条件的记录 + +* **names(self, tag=None):** 返回所有的name列表 + +* **write(self, path=None):** 将记录写入文件,只有可以json序列号的对象才支持写入。 + +* **def read(self, path):** 从文件读取记录到当前manager + + + +### 用法举例 + +这个例子演示如何使用add, get + +```python +def add_get_test(): + print('manager test add, get, gets:') + manager = Manager() + # add a name, value pair + manager.add(name='one', obj='object1') + # you can also omit the name, obj + manager.add('two', 'object2') + + print(manager.names()) + # >>> ['two', 'one'] , the later inserted is at first! + print(manager.get()) + # >>> object1 , return the first object, or None + print(manager.get(name='one')) + # >>> object1 , return the first matched object, or None + print(manager.gets()) + # >>> [('two', 'object2', None), ('one', 'object1', None)] + manager.remove(name='two') + print(manager.gets()) + # >>> [('one', 'object1', None)] +``` + + + +这个例子演示如何利用tag进行筛选 + +```python +def tag_test(): + print('manager test with tag:') + manager = Manager() + manager.add('one', 'object1', 'low') + manager.add('two', 'object2', 'low') + manager.add('one', 'OBJECT1', 'up') + manager.add('two', 'OBJECT2', 'up') + + print(manager.gets(tag='low')) + # >>> [('two', 'object2', 'low'), ('one', 'object1', 'low')] + print(manager.gets(tag='up')) + # >>> [('two', 'OBJECT2', 'up'), ('one', 'OBJECT1', 'up')] +``` + + + +这个例子说明容器内部是按照添加的倒序存放的,调用active可以将满足条件的元素置顶。 + +```python +def order_test(): + print('manager order test:') + manager = Manager() + manager.add('one', 'object1') + manager.add('two', 'object2') + manager.add('three', 'object3') + print(manager.names()) + # >>> ['three', 'two', 'one'] + manager.active('one') + print(manager.names()) + # >>> ['one', 'three', 'two'] +``` + + + +这个例子演示了默认情况下,相同name, tag的对象重复添加,会覆盖之前的记录,但是在构造时用unique=False则允许重复添加。 + +```python +def unique_test(): + print('manager unique test:') + manager = Manager() + manager.add('one', 'object1') + manager.add('one', 'OBJECT1') + print(manager.gets()) + # >>> [('one', 'OBJECT1', None)] + + manager = Manager(unique=False) + manager.add('one', 'object1') + manager.add('one', 'OBJECT1') + print(manager.gets()) + # >>> [('one', 'OBJECT1', None), ('one', 'object1', None)] +``` + + + +manager对象可以write到文件,也可以从文件read,构造时如果传入path参数,则自动从路径进行read。记录为name, obj, tag的列表,以json格式存储,因此manager可以读写的条件是,所有元素都可以json化。 + +```python +def io_test(): + print('show how to read and write a manager:') + manager = Manager() + manager.add('one', 'object1') + manager.add('two', 'object2') + manager.write('manager.json') + + manager = Manager() + manager.read('manager.json') + print(manager.gets()) + # >>> [('one', 'object1', None), ('two', 'object2', None)] + + # you can also pass path parameter when init a Manager + manager = Manager(path='manager.json') + print(manager.gets()) + # >>> [('one', 'object1', None), ('two', 'object2', None)] + print() +``` + diff --git a/sciapp/doc/cn_readme.md b/sciapp/doc/cn_readme.md new file mode 100644 index 00000000..9ccb6ac5 --- /dev/null +++ b/sciapp/doc/cn_readme.md @@ -0,0 +1,115 @@ +# SciApp + +SciApp是一个交互式科学计算的后端框架,主要用于搭建科学分析应用。SciApp并不是算法库,也不包含任何界面,SciApp的目的是为算法类应用提供一个标准接口,具体如下: + +1. 定义了科学计算常用的数据结构封装类 +2. 对数据结构定义了一些基础操作函数 +3. 定义了一个通用Manager,将各种数据进行管理,聚合为一个App对象 +4. 定义了Action对象,可以对App对象进行操作 + +![SciApp](https://user-images.githubusercontent.com/24822467/86324215-cd99f700-bc70-11ea-8851-1de44e313a1f.png) + +### Object模块 + +object模块定义了科学计算中常用的基础数据结构封装类,当然,如果仅仅为了计算,绝大多数时候,Numpy,Pandas等数据类型已经可以胜任,这里的封装,主要是面向交互与展示的,例如Image对象是图像数据,里面带了一个lut成员,用于在展示时映射成伪彩色。 + +1. Image:多维图像,基于Numpy +2. Table:表格,基于DataFrame +3. Shape: 点线面,任意多边形,可与GeoJson,Shapely互转 +4. Surface:三维表面 + + + +### Util模块 + +Util定义了一些针对Object数据类型的基础操作函数,这些函数也是为了完成交互与展示,并非为了数据分析。 + +1. imutil: 主要实现图像的快速采样,多图层融合等算法 + +2. shputil: 主要实现多边形与给定点之间的几何关系,用于鼠标编辑。 + + + +### App容器 + +Manager:通用管理器对象,类似一个键值对管理器,里面装入key, value, tag样式的三元组,可以对元素进行增删查改。 + +App:一个科学容器,里面包含若干Manager,用于管理App所持有的Image,Table等Object,同时定义了一套标准展示接口,例如show_img, get_img, close_img等,(Table,Shape类似) + + + +* [[Manager 对象]](./cn_manager.md) 可以增删查改的对象容器 +* [[App 对象]](./cn_app.md) 科研应用接口,各类Object的大容器 + + + +### Action模块 + +Action:对App对象的一个操作,例如获取当前图像,做某种滤波,然后从图像中获取某种信息,最后show一个Table处理。其通用模板是Action().start(app)。可以在其子类中重载start,对app进行任何操作。 + +* [[如何衍生出图像滤波类Action](./cn_imgaction.md) +* [[如何衍生出数据读写类Action](./cn_ioaction.md) +* [[读取-处理-写入,完成具体工作](./cn_iprocesso.md) + +以上例子是为了说明Action的作用机制,也展示了框架的构建思路,但并不意味着我们需要按照以上方法,从SciAction构建各种模板,我们已经构建了相当丰富,功能也更为完善的Action派生树,以下进行列举。 + + + +### Action的继承树 + +因为绝大多数的Action都和交互有关,而SciApp自身主要实现对象管理功能,交互只能通过print进行展示,所以很多功能这里只是列举,具体用法我们会在sciwx实现的一个App实例中展示。 + + + +**SciAction:** 所有Action的基类,start内获取app对象,进行处理 + +* ImageAction: 用于处理图像,自动获取当前图像,需要重载para,view进行交互,重载run进行图像处理 + +* TableAction: 用于处理表格,自动获取当前表格,需要重载para,view进行交互,重载run进行图像处理 + +* Tool: 工具,用于在某种控件上的鼠标交互 + + * ImageTool: 图像工具,例如画笔,魔术棒等,需要重载一系列鼠标事件(参数坐标已转入图像坐标系) + + * TableTool:表格工具,需要重载一系列鼠标事件(参数坐标已自动转入单元格行列) + * ShapeTool: 矢量编辑,例如点线面,多边形绘制(参数坐标已自动转入数据坐标系) + +**Advanced:** 这个包下面是一些高级的Action模板,也是我们扩展插件主要使用的 + +* dataio: 里面实现了Reader,Writer类的Action,我们只需要将读写函数注册给对应的Manager +* Free: 继承SciAction,添加了para, view交互,添加了多线程支持。 +* Filter: 继承ImageAction,主要用于做图像滤波,自动多通道,自带批量特性,多线程支持。 +* Simple: 继承ImageAction,主要用于图像操作,自带多线程支持。 +* Table: 继承TableAction,主要用于表格操作,比如表格统计,数据绘图等。 +* Macros: 将一段字符串作为宏执行,构造时传入字符串,start后依次执行。 + +**Plugins:** 这个包下面有一些带有具体功能,开箱即用的Action + +* filters:滤波类 + * Gaussian:高斯滤波 +* generalio: 数据读取类 + * bmp, jpg, png, tif格式图像的读写支持 +* ShapeTool: 矢量图像编辑工具 + * PointEditor: 编辑点 + * LineEditor: 编辑线 + * PolygonEditor: 编辑多边形 + * RectangleEditor: 编辑矩形 + * EllipseEditor: 编辑椭圆 + * FreeLineEditor: 编辑任意线 + * FreePolygonEditor: 编辑任意多边形 + +* ROITool: 类似于Shape,但作用对象是图像上的ROI + * PointEditor: 编辑点 + * LineEditor: 编辑线 + * PolygonEditor: 编辑多边形 + * RectangleEditor: 编辑矩形 + * EllipseEditor: 编辑椭圆 + * FreeLineEditor: 编辑任意线 + * FreePolygonEditor: 编辑任意多边形 + +* MeasureTool: 类似于Shape,绘制同时会展示数值 + * CoordinateTool: 坐标测量 + * DistanceTool: 距离测量 + * AngleTool: 角度测量 + * SlopeTool: 梯度测量 + * AreaTool: 面积测量 \ No newline at end of file diff --git a/sciapp/manager.py b/sciapp/manager.py new file mode 100644 index 00000000..41d9d4f9 --- /dev/null +++ b/sciapp/manager.py @@ -0,0 +1,58 @@ +import json, os.path as osp + +class Manager: + def __init__(self, unique=True, path=None): + self.objs, self.unique, self.path = [], unique, path + if not path is None: self.read(path) + + def add(self, name, obj, tag=None): + if self.unique: self.remove(name, tag) + self.objs.insert(0, (name, obj, tag)) + + def active(self, name=None, tag=None, obj=None): + objs = self.gets(name, tag, obj) + for i in objs: self.objs.remove(i) + for i in objs: self.objs.insert(0, i) + + def set(self, name, obj, tag=None): + self.remove(name, tag) + self.objs.insert(0, (name, obj, tag)) + + def adds(self, objs): + for i in objs: self.add(*i) + + def get(self, name=None, tag=None, obj=None): + rst = self.gets(name, tag, obj) + return None if len(rst)==0 else rst[0][1] + + def gets(self, name=None, tag=None, obj=None): + rst = [i for i in self.objs if name is None or name == i[0]] + rst = [i for i in rst if obj is None or obj is i[1]] + return [i for i in rst if tag is None or tag == i[2]] + + def has(self, name=None, tag=None, obj=None): + return len(self.gets(name, tag, obj))>0 + + def remove(self, name=None, tag=None, obj=None): + for i in self.gets(name, tag, obj): self.objs.remove(i) + + def names(self, tag=None): + return [i[0] for i in self.gets(tag=tag)] + + def name(self, name): + names = self.names() + if not name in names : return name + for i in range(1, 100) : + n = "%s-%s"%(name, i) + if not n in names: return n + + def write(self, path=None): + with open(path or self.path, 'w') as f: + f.write(json.dumps(self.objs)) + + def read(self, path): + self.path = path + if not osp.exists(path): return + with open(path) as f: + self.adds(json.loads(f.read())) + return self \ No newline at end of file diff --git a/sciapp/object/__init__.py b/sciapp/object/__init__.py new file mode 100644 index 00000000..de8ba6a9 --- /dev/null +++ b/sciapp/object/__init__.py @@ -0,0 +1,5 @@ +from .shape import * +from .image import Image +from .table import Table +from .roi import * +from .surface import * \ No newline at end of file diff --git a/sciapp/object/image.py b/sciapp/object/image.py new file mode 100644 index 00000000..9f9daa0f --- /dev/null +++ b/sciapp/object/image.py @@ -0,0 +1,179 @@ +import numpy as np + +default_lut = np.arange(256*3, dtype=np.uint8).reshape((3,-1)).T + +def get_updown(imgs, slices='all', chans='all', step=1): + c = chans if isinstance(chans, int) else slice(None) + if isinstance(slices, int): imgs = [imgs[slices]] + if step<=1: step = int(1/step+0.5) + else: step = int(min(imgs[0].shape[:2])/step+0.5) + s = slice(None, None, max(step, 1)) + s = (s,s,c)[:imgs[0].ndim] + mins = [i[s].min(axis=(0,1)) for i in imgs] + maxs = [i[s].max(axis=(0,1)) for i in imgs] + mins = np.array(mins).reshape((len(mins),-1)) + maxs = np.array(maxs).reshape((len(maxs),-1)) + mins, maxs = mins.min(axis=0), maxs.max(axis=0) + if np.iscomplexobj(mins): + mins, maxs = np.zeros(mins.shape), np.abs(maxs) + if chans!='all': return mins.min(), maxs.max() + return [(i,j) for i,j in zip(mins, maxs)] + +def lookup(img, cn, rgs, lut): + if isinstance(cn, int): cn = [cn] + img = img.reshape(img.shape[:2]+(-1,)) + buf = np.zeros(img.shape[:2]+(len(cn),), dtype=np.uint8) + for i in range(len(cn)): + rg = rgs[cn[i]] + k = 255.0/(max(1e-10, rg[1]-rg[0])) + bf = np.clip(img[:,:,cn[i]], rg[0], rg[1]) + np.subtract(bf, rg[0], out=bf, casting='unsafe') + np.multiply(bf, k, out=buf[:,:,i], casting='unsafe') + return buf if len(cn)==3 else lut[buf.reshape(img.shape[:2])] + +def histogram(imgs, rg=(0,256), slices='all', chans='all', step=1): + c = chans if isinstance(chans, int) else slice(None) + if isinstance(slices, int): imgs = [imgs[slices]] + if step<=1: step = int(1/step+0.5) + else: step = int(min(imgs[0].shape[:2])/step+0.5) + s = slice(None, None, max(step,1)) + s = (s,s,c)[:imgs[0].ndim] + rg = np.linspace(rg[0], rg[1], 257) + hist = [np.histogram(i[s], rg)[0] for i in imgs] + return np.sum(hist, axis=0) + +class Image: + def __init__(self, imgs=None, name='Image'): + self.name = name + self.cur = 0 + self.rg = [(0, 255)] + self.set_imgs(imgs) + self.roi = None + self.mark = None + self.unit = 1, 'pix' + self.msk = None + self.pos = (0,0) + self.cn = 0 + + self.lut = default_lut + self.log = False + self.mode = 'set' + self.dirty = False + self.snap = None + self.back = None + self.tool = None + self.data = {} + + @property + def box(self): + (x, y), (h, w) = self.pos, self.shape + return [x, y, x+w, y+h] + + @property + def title(self): return self.name + + @property + def img(self): return self.imgs[self.cur] + + @img.setter + def img(self, value): + self.imgs[self.cur] = value + self.reset() + + def set_imgs(self, imgs): + self.imgs = [imgs] if imgs is None else imgs + if not imgs is None: self.reset() + + @property + def channels(self): + if self.img.ndim==2: return 1 + else: return self.img.shape[2] + + @property + def slices(self): return len(self.imgs) + + @property + def isarray(self): return isinstance(self.imgs, np.ndarray) + + @property + def nbytes(self): + return sum([i.nbytes for i in self.imgs]) + + @property + def dtype(self): return self.img.dtype + + @property + def shape(self): return self.img.shape[:2] + + @property + def info(self): + return '%s %sx%s S:%s/%s C:%s/%s %.2fM'%(str(self.dtype).upper(), *self.shape, + self.cur+1, self.slices, self.cn, self.channels, self.nbytes/1024/1024) + + @property + def range(self): + rg = np.array(self.rg).reshape((-1,2)) + return (rg[:,0].min(), rg[:,1].max()) + + @range.setter + def range(self, value): + self.rg = [value] * len(self.rg) + + def mask(self, mode='in'): + if self.roi==None: return None + if self.roi.msk != mode: + self.msk = self.roi.to_mask(self.shape, mode) + return self.msk + + @property + def rect(self): + if self.roi is None: return slice(None), slice(None) + box, shape = self.roi.box, self.shape + l, r = max(0, int(box[0])), min(shape[1], int(box[2])) + t, b = max(0, int(box[1])), min(shape[0], int(box[3])) + return slice(t,b), slice(l,r) + + def subimg(self, s1=None, s2=None): + s1 = s1 or self.rect[0] + s2 = s2 or self.rect[1] + if self.isarray: return self.imgs[s1, s2] + else: return [i[s1, s2] for i in self.imgs] + + def update(self): self.dirty = True + + def reset(self): + self.cn = [0, [0,1,2]][self.channels==3] + if self.dtype == np.uint8: + self.rg = [(0, 255)] * self.channels + else: + self.rg = self.get_updown('all', 'all', step=512) + + def snapshot(self): + dif = self.snap is None + dif = dif or self.snap.shape != self.img.shape + dif = dif or self.snap.dtype != self.img.dtype + if dif: self.snap = self.img.copy() + else: self.snap[:] = self.img + + def swap(self): + if self.snap is None:return + buf = self.img.copy() + self.img[:], self.snap[:] = self.snap, buf + + def histogram(self, rg=None, slices=None, chans=None, step=1): + if slices is None: slices = self.cur + if chans is None: chans = self.cn + if rg is None: rg = self.range + return histogram(self.imgs, rg, slices, chans, step) + + def get_updown(self, slices='all', chans='all', step=512): + if slices is None: slices = self.cur + if chans is None: chans = self.cn + return get_updown(self.imgs, slices, chans, step) + + def lookup(self, img=None): + if img is None: img = self.img + return lookup(img, self.cn, self.rg, self.lut) + +if __name__ == '__main__': + img = Image(np.zeros((5,5))) diff --git a/sciapp/object/roi.py b/sciapp/object/roi.py new file mode 100644 index 00000000..214b3bd2 --- /dev/null +++ b/sciapp/object/roi.py @@ -0,0 +1,112 @@ +from .shape import * +import numpy as np +from numpy.linalg import norm +import shapely.geometry as geom + +class ROI(Layer): + default = {'color':(255,255,0), 'fcolor':(255,255,255), + 'fill':False, 'lw':1, 'tcolor':(255,0,0), 'size':8} + + def __init__(self, body=None, **key): + if isinstance(body, Layer): body = body.body + if not body is None and not isinstance(body, list): + body = [body] + Layer.__init__(self, body, **key) + self.fill = False + self.msk = None + + @property + def roitype(self): + roitype = '' + for i in self.body: + if roitype == '': roitype = i.dtype + if roitype != i.dtype: return 'multi' + return roitype + + def to_mask(self, msk, mode): + from ..util import draw_shp + if isinstance(msk, tuple): + msk = np.zeros(msk, dtype=np.int8) + else: msk[:] = 0 + msk.dtype = np.int8 + if mode=='in': + draw_shp(self.to_geom(), msk, 1, 0) + np.clip(msk, 0, 1, out=msk) + if mode=='out': + draw_shp(self.to_geom(), msk, 1, 0) + np.clip(msk, 0, 1, out=msk) + msk -= 1 + if isinstance(mode, int): + draw_shp(self.to_geom(), msk, 1, mode) + np.clip(msk, 0, 1, out=msk) + msk.dtype, self.msk = 'bool', mode + return msk + +class Measure(Layer): + default = {'color':(255,255,0), 'fcolor':(255,255,255), + 'fill':False, 'lw':1, 'tcolor':(255,0,0), 'size':8} + + def measure(self, shp): + txt = [] + if shp.dtype == 'layer': + for i in shp.body: + txt.extend(self.measure(i)) + if not hasattr(shp, 'mtype'): return txt + if shp.mtype=='coordinate': + txt.append(tuple(shp.body)+('%.2f,%.2f'%tuple(shp.body),)) + if shp.mtype=='distance': + for s,e in zip(shp.body[:-1], shp.body[1:]): + p = ((s[0]+e[0])/2, (s[1]+e[1])/2) + l = ((s[0]-e[0])**2+(s[1]-e[1])**2)**0.5 + txt.append(p+('%.2f'%l,)) + if shp.mtype=='angle': + pts = np.array(shp.body) + v1 = pts[:-2]-pts[1:-1] + v2 = pts[2:]-pts[1:-1] + a = np.sum(v1*v2, axis=1)*1.0 + a/=norm(v1,axis=1)*norm(v2,axis=1) + ang = np.arccos(a)/np.pi*180 + for v, p in zip(ang, shp.body[1:-1]): + txt.append(tuple(p[:2])+('%.2f'%v,)) + if shp.mtype=='slope': + pts = np.array(shp.body) + mid = (pts[:-1]+pts[1:])/2 + + dxy = (pts[:-1]-pts[1:]) + dxy[:,1][dxy[:,1]==0] = 1 + l = norm(dxy, axis=1)*-np.sign(dxy[:,1]) + ang = np.arccos(dxy[:,0]/l)/np.pi*180 + for v, p in zip(ang, mid): + txt.append(tuple(p[:2])+('%.2f'%v,)) + if shp.mtype=='area': + geom = shp.to_geom() + o = geom.centroid + txt.append((o.x, o.y)+('%.2f'%geom.area,)) + return txt + + def measure_mark(self): + txt = self.measure(self) + if len(txt)>0: + rms = [i for i in self.body if isinstance(i, Texts)] + for i in rms: self.body.remove(i) + self.body.append(Texts(txt)) + +class Coordinate(Point): mtype = 'coordinate' + +class Distance(Line): mtype = 'distance' + +class Area(Polygon): mtype = 'area' + +class Angle(Line): mtype = 'angle' + +class Slope(Line): mtype = 'slope' + +if __name__ == '__main__': + pts = Points([(10,10),(15,20)]) + line = Line([(10,10),(15,20),(20,20)]) + lines = Lines([[(10,10),(15,20),(20,20)]]) + polygon = Polygon([[(1,1),(1,20),(20,20),(20,1)],[(5,5),(5,10),(10,10),(10,5)]]) + + im = draw_shp(pts.to_geom(), (30,30), lw=0) + plt.imshow(im) + plt.show() diff --git a/sciapp/object/shape.py b/sciapp/object/shape.py new file mode 100644 index 00000000..bbf7e060 --- /dev/null +++ b/sciapp/object/shape.py @@ -0,0 +1,369 @@ +import numpy as np +from time import time +from collections.abc import Iterable +import shapely.geometry as geom +from shapely.ops import unary_union + +def merge(a, b): + x1, y1 = min(a[0],b[0]), min(a[1],b[1]) + x2, y2 = max(a[2],b[2]), max(a[3],b[3]) + return [x1, y1, x2, y2] + +class Shape: + default = {'color':(255,255,0), 'fcolor':(255,255,255), + 'fill':False, 'lw':1, 'tcolor':(255,0,0), 'size':8} + dtype = 'shape' + def __init__(self, body=None, **key): + self.name = 'shape' + self.body = [] if body is None else body + self.color = key['color'] if 'color' in key else None + self.fcolor = key.get('fcolor', None) + self.lstyle = key.get('style', None) + self.tcolor = key.get('tcolor', None) + self.lw = key.get('lw', None) + self.r = key.get('r', None) + self.fill = key.get('fill', None) + self._box = None + self.dirty = True + + @property + def box(self): + if self._box is None or self.dirty: + self._box = self.count_box() + return self._box + + @property + def style(self): + styledic = {'type':self.dtype} + if not self.color is None: styledic['color']=self.color + if not self.fcolor is None: styledic['fcolor']=self.fcolor + if not self.lstyle is None: styledic['lstyle']=self.lstyle + if not self.lw is None: styledic['lw']=self.lw + if not self.fill is None: styledic['fill']=self.fill + return styledic + + def count_box(self, body=None, box=None): + if body is None: + box = [1e10, 1e10,-1e10,-1e10] + self.count_box(self.body, box) + return box + if isinstance(body, np.ndarray): + body = body.reshape((-1,2)) + minx, miny = body.min(axis=0) + maxx, maxy = body.max(axis=0) + newbox = [minx, miny, maxx, maxy] + box.extend(merge(box, newbox)) + del box[:4] + else: + for i in body: self.count_box(i, box) + + @property + def info(self): + minx, miny, maxx, maxy = self.box + return 'Type:%s minX:%.3f maxX:%.3f, minY:%.3f maxY%.3f'%( + self.dtype, minx, maxx, miny, maxy) + + def to_mark(self, body): + mark = self.style + mark['body'] = body + return mark + + def to_json(self): + return geom.mapping(self.to_geom()) + + def to_geom(self): pass + + def __str__(self): + return str(self.to_mark()) + +class Point(Shape): + dtype = 'point' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def to_mark(self): + return Shape.to_mark(self, tuple(self.body.tolist())) + + def to_geom(self): + return geom.Point(self.body) + +class Points(Shape): + dtype = 'points' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def to_mark(self): + return Shape.to_mark(self, [tuple(i.tolist()) for i in self.body]) + + def to_geom(self): + return geom.MultiPoint(self.body) + +class Line(Shape): + dtype = 'line' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def to_mark(self): + return Shape.to_mark(self, [tuple(i.tolist()) for i in self.body]) + + def to_geom(self): + return geom.LineString(self.body) + +class Lines(Shape): + dtype = 'lines' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = [np.array(i, dtype=np.float32) for i in body] + + def to_mark(self): + return Shape.to_mark(self, [[tuple(i.tolist()) for i in j] for j in self.body]) + + def to_geom(self): + return geom.MultiLineString(self.body) + +class Polygon(Shape): + dtype = 'polygon' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + if len(body)>0 and not isinstance(body[0][0], Iterable): body = [body] + self.body = [np.array(i, dtype=np.float32) for i in body] + + def to_mark(self): + return Shape.to_mark(self, [[tuple(i.tolist()) for i in j] for j in self.body]) + + def to_geom(self): + return geom.Polygon(self.body[0], self.body[1:]) + +class Polygons(Shape): + dtype = 'polygons' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + for i in range(len(body)): + if not isinstance(body[i][0][0], Iterable): body[i] = [body[i]] + self.body = [[np.array(i, dtype=np.float32) for i in j] for j in body] + + def to_mark(self): + mark = self.style + f = lambda x:[[tuple(i.tolist()) for i in j] for j in x] + return Shape.to_mark(self, [[[tuple(i.tolist()) for i in j] for j in k] for k in self.body]) + + def to_geom(self): + return geom.MultiPolygon([[i[0], i[1:]] for i in self.body]) + +class Circle(Shape): + dtype = 'circle' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self): + pts = self.body + return [pts[0]-pts[2], pts[1]-pts[2], pts[0]+pts[2], pts[1]+pts[2]] + + def to_mark(self): + return Shape.to_mark(self, tuple(self.body.tolist())) + + def to_geom(self): + return geom.Point(self.body[:2]).buffer(self.body[2]) + +class Circles(Shape): + dtype = 'circles' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self): + pts = self.body.T + return [(pts[0]-pts[2]).min(), (pts[1]-pts[2]).min(), (pts[0]+pts[2]).max(), (pts[1]+pts[2]).max()] + + def to_mark(self): + return Shape.to_mark(self, [tuple(i.tolist()) for i in self.body]) + + def to_geom(self): + return geom.MultiPolygon([geom.Point(i[:2]).buffer(i[2]) for i in self.body]) + +class Rectangle(Shape): + dtype = 'rectangle' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self): + return [self.body[0], self.body[1], self.body[0]+self.body[2], self.body[1]+self.body[3]] + + def to_mark(self): + return Shape.to_mark(self, tuple(self.body.tolist())) + + def to_geom(self): + f = lambda x:[(x[0],x[1]),(x[0],x[1]+x[3]),(x[0]+x[2],x[1]+x[3]),(x[0]+x[2],x[1])] + return geom.Polygon(f(self.body)) + +class Rectangles(Shape): + dtype = 'rectangles' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self, body=None, box=None): + self.body[:,2:] += self.body[:,:2] + minx, miny = self.body.reshape((-1,2)).min(axis=0) + maxx, maxy = self.body.reshape((-1,2)).max(axis=0) + self.body[:,2:] -= self.body[:,:2] + return [minx, miny, maxx, maxy] + + def to_mark(self): + return Shape.to_mark(self, [tuple(i.tolist()) for i in self.body]) + + def to_geom(self): + f = lambda x:[(x[0],x[1]),(x[0],x[1]+x[3]),(x[0]+x[2],x[1]+x[3]),(x[0]+x[2],x[1])] + return geom.MultiPolygon([[f(i),[]] for i in self.body]) + +def make_ellipse(x0, y0, l1, l2, ang): + m = np.array([[l1*np.cos(-ang),-l2*np.sin(-ang)], + [l1*np.sin(-ang),l2*np.cos(-ang)]]) + a = np.linspace(0, np.pi*2, 36) + xys = np.array((np.cos(a), np.sin(a))) + return np.dot(m, xys).T + (x0, y0) + +class Ellipse(Shape): + dtype = 'ellipse' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self): + xy = make_ellipse(*self.body) + (minx, miny), (maxx, maxy) = xy.min(axis=0), xy.max(axis=0) + return [minx, miny, maxx, maxy] + + def to_mark(self): + return Shape.to_mark(self, tuple(self.body.tolist())) + + def to_geom(self): + return geom.Polygon(make_ellipse(*self.body)) + +class Ellipses(Shape): + dtype = 'ellipses' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.body = np.array(body, dtype=np.float32) + + def count_box(self): + xy = np.vstack([make_ellipse(*i) for i in self.body]) + (minx, miny), (maxx, maxy) = xy.min(axis=0), xy.max(axis=0) + return [minx, miny, maxx, maxy] + + def to_mark(self): + return Shape.to_mark(self, [tuple(i.tolist()) for i in self.body]) + + def to_geom(self): + return geom.MultiPolygon([[make_ellipse(*i),[]] for i in self.body]) + +class Text(Shape): + dtype = 'text' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.offset = key.get('offset', [0,0]) + self.body = np.array(body[:2], dtype=np.float32) + self.txt = body[2] + + def to_mark(self): + mark = Shape.to_mark(self, tuple(self.body.tolist())+(self.txt,)) + mark['offset'] = self.offset; return mark + + def to_geom(self): + return geom.Point(self.body) + +class Texts(Shape): + dtype = 'texts' + def __init__(self, body=[], **key): + Shape.__init__(self, body, **key) + self.offset = key.get('offset', [0,0]) + body = np.array(body, dtype=object).reshape(-1,3) + self.body = body[:,:2].astype(np.float32) + self.txt = body[:,2] + + def to_mark(self): + mark = Shape.to_mark(self, [(i,j,t) for (i,j), t in zip(self.body.tolist(), self.txt.tolist())]) + mark['offset'] = self.offset; return mark + + def to_geom(self): + return geom.MultiPoint(self.body) + +class Layer(Shape): + dtype = 'layer' + def __init__(self, body=None, **key): + Shape.__init__(self, body, **key) + + @property + def box(self): + if len(self.body)==0: return None + boxs = np.array([i.box for i in self.body]).T + return [boxs[0].min(), boxs[1].min(), boxs[2].max(), boxs[3].max()] + + def to_mark(self): + return Shape.to_mark(self, [i.to_mark() for i in self.body]) + + def to_geom(self): + return geom.GeometryCollection([i.to_geom() for i in self.body]) + +class Layers(Shape): + dtype = 'layers' + def __init__(self, body=None, **key): + Shape.__init__(self, body, **key) + + @property + def box(self): + if len(self.body)==0: return None + boxs = np.array([i.box for i in self.body.values()]).T + return [boxs[0].min(), boxs[1].min(), boxs[2].max(), boxs[3].max()] + + def to_mark(self): + body = dict(zip(self.body.keys(), [i.to_mark() for i in self.body.values()])) + return Shape.to_mark(self, body) + +def mark2shp(mark): + style = mark.copy() + style.pop('body') + keys = {'point':Point, 'points':Points, 'line':Line, 'lines':Lines, + 'polygon':Polygon, 'polygons':Polygons, 'circle':Circle, + 'circles':Circles, 'rectangle':Rectangle, 'rectangles':Rectangles, + 'ellipse':Ellipse, 'ellipses':Ellipses, 'text':Text, 'texts':Texts} + if mark['type'] in keys: return keys[mark['type']](mark['body'], **style) + if mark['type']=='layer': + return Layer([mark2shp(i) for i in mark['body']], **style) + if mark['type']=='layers': + return Layers(dict(zip(mark['body'].keys(), + [mark2shp(i) for i in mark['body'].values()])), **style) + +def json2shp(obj): + if obj['type']=='Point': + return Point(obj['coordinates']) + if obj['type']=='MultiPoint': + return Points(obj['coordinates']) + if obj['type']=='LineString': + return Line(obj['coordinates']) + if obj['type']=='MultiLineString': + return Lines(obj['coordinates']) + if obj['type']=='Polygon': + return Polygon(obj['coordinates']) + if obj['type']=='MultiPolygon': + return Polygons(obj['coordinates']) + if obj['type']=='GeometryCollection': + return Layer([json2shp(i) for i in obj['geometries']]) + +def geom2shp(obj): return json2shp(geom.mapping(obj)) + +if __name__ == '__main__': + import json + + layer = {'type': 'layer', 'body': [{'type': 'circle', 'body': (256, 256, 5)}, {'type': 'circle', 'body': (256, 256, 50)}, {'type': 'circle', 'body': (306.0, 256.0, 3)}]} + layers = {'type':'layers', 'body':{1:{'type':'layer', 'body':[]}}} + a = mark2shp(layers) + print(a) + #import geonumpy.io as gio + #shp = gio.read_shp('C:/Users/54631/Documents/projects/huangqu/demo/shape/province.shp') + #feas = json.loads(shp.to_json())['features'] diff --git a/sciapp/object/surface.py b/sciapp/object/surface.py new file mode 100644 index 00000000..6a946aac --- /dev/null +++ b/sciapp/object/surface.py @@ -0,0 +1,191 @@ +import numpy as np +import scipy.ndimage as ndimg + +class Scene: + def __init__(self, **key): + self.name = key.get('name', 'scene') + self.objects = {} + self._light_dir = (10, 5, -5) + self._light_color = (0.7, 0.7, 0.7, 0.7) + self._bg_color = (0.3, 0.3, 0.3, 1.0) + self._ambient_color = (0.3, 0.3, 0.3, 1.0) + self.set_style(**key) + self.dirty = True + + @property + def title(self): return self.name + + @property + def surface(self): return self.name + + def apply(self): + for i in self.objects.values(): + i.light_dir = self.light_dir + i.light_color = self.light_color + i.ambient_color = self.ambient_color + + def set_style(self, bg_color=None, light_dir=None, light_color=None, ambient_color=None, **key): + self._bg_color = bg_color or self._bg_color + self._light_dir = light_dir or self._light_dir + self._light_color = light_color or self._light_color + self._ambient_color = ambient_color or self._ambient_color + self.dirty = True; self.apply() + + + @property + def bg_color(self): return self._bg_color + + @bg_color.setter + def bg_color(self, value): + return self.set_style(bg_color=value) + + @property + def light_dir(self): return self._light_dir + + @light_dir.setter + def light_dir(self, value): + return self.set_style(light_dir=value) + + @property + def light_color(self): return self._light_color + + @light_color.setter + def light_color(self, value): + return self.set_style(light_color=value) + + @property + def ambient_color(self): return self._ambient_color + + @ambient_color.setter + def ambient_color(self, value): + return self.set_style(ambient_color=value) + + def add_obj(self, name, obj): + self.objects[name] = obj + + def get_obj(self, name): + return self.objects[name] + + @property + def names(self): + return list(self.objects.keys()) + +class Mesh: + def __init__(self, verts=None, faces=None, colors=None, cmap=None, **key): + if faces is None and not verts is None: + faces = np.arange(len(verts), dtype=np.uint32) + self.verts = verts.astype(np.float32, copy=False) if not verts is None else None + self.faces = faces.astype(np.uint32, copy=False) if not faces is None else None + self.colors = colors + self.mode, self.visible, self.dirty = 'mesh', True, 'geom' + self.alpha = 1; self.edges = None; self.shiness = 60 + self.high_light = False; self.cmap = 'gray' if cmap is None else cmap + self.set_data(**key) + + def set_data(self, verts=None, faces=None, colors=None, **key): + if faces is None and not verts is None: + faces = np.arange(len(verts), dtype=np.uint32) + if not verts is None: self.verts = verts.astype(np.float32, copy=False) + if not faces is None: self.faces = faces.astype(np.uint32, copy=False) + if not colors is None: self.colors = colors + if not faces is None: self.edge = None + if sum([i is None for i in [verts, faces]])<2: self.dirty = 'geom' + if not self.faces is None and self.faces.ndim==1: key['mode'] = 'points' + elif not self.faces is None and self.faces.shape[1]==2: + if key.get('mode', self.mode)=='mesh': key['mode'] = 'grid' + if key.get('mode', self.mode) != self.mode: self.dirty = 'geom' + self.mode = key.get('mode', self.mode) + self.visible = key.get('visible', self.visible) + self.alpha = key.get('alpha', self.alpha) + self.high_light = key.get('high_light', False) + self.shiness = key.get('shiness', self.shiness) + self.cmap = key.get('cmap', self.cmap) + self.dirty = self.dirty or True + + def get_edges(self): + if self.faces.ndim==1 or self.faces.shape[1]==2: return self.faces + if not self.edges is None: return self.edges + edges = np.vstack([self.faces[:,i] for i in ([0,1],[0,2],[1,2])]) + edges = np.sort(edges, axis=-1).ravel().view(dtype=np.uint64) + self.edges = np.unique(edges).view(dtype=np.uint32).reshape(-1,2) + return self.edges + + def update(self, state=True): self.dirty = state + +class TextSet: + def __init__(self, texts=None, verts=None, colors=(1,1,1), size=12, **key): + self.texts, self.verts, self.size, self.colors = texts, verts, size, colors + self.visible, self.dirty = True, 'geom' + self.alpha = 1; self.edges = None + self.high_light = False; self.shiness = 0 + self.set_data(**key) + + def set_data(self, texts=None, verts=None, colors=None, size=None, **key): + if not texts is None: self.texts = texts + if not verts is None: self.verts = verts + if not colors is None: self.colors = colors + if not size is None: self.size = size + if sum([i is None for i in [texts, verts, colors, size]])<4: self.dirty = 'geom' + self.visible = key.get('visible', self.visible) + self.alpha = key.get('alpha', self.alpha) + self.high_light = key.get('high_light', False) + self.shiness = key.get('shiness', self.shiness) + self.dirty = self.dirty or True + +class Surface2d(Mesh): + def __init__(self, img=None, sample=1, sigma=0, k=0.3, **key): + self.img, self.sample, self.sigma, self.k = img, sample, sigma, k + Mesh.__init__(self, **key) + self.set_data(img, sample, sigma, k) + + def set_data(self, img=None, sample=None, sigma=None, k=None, **key): + if not img is None: self.img = img + if not sample is None: self.sample = sample + if not sigma is None: self.sigma = sigma + if not k is None: self.k = k + if sum([not i is None for i in (img, sample, sigma, k)])>0: + from ..util import meshutil + vert, fs = meshutil.create_surface2d(self.img, self.sample, self.sigma, self.k) + Mesh.set_data(self, verts=vert, faces=fs.astype(np.uint32), colors=vert[:,2], **key) + else: Mesh.set_data(self, **key) + +class Surface3d(Mesh): + def __init__(self, imgs=None, level=0, sample=1, sigma=0, step=1, **key): + self.imgs, self.sample, self.sigma, self.step = imgs, sample, sigma, step + self.level, self.step = level, step + Mesh.__init__(self, **key) + self.set_data(imgs, level, sample, sigma, step) + + def set_data(self, imgs=None, level=None, sample=None, sigma=None, step=None, **key): + if not imgs is None: self.imgs = imgs + if not level is None: self.level = level + if not sample is None: self.sample = sample + if not sigma is None: self.sigma = sigma + if not step is None: self.step = step + if sum([not i is None for i in (imgs, level, sample, sigma, step)])>0: + from ..util import meshutil + vert, fs = meshutil.create_surface3d(self.imgs, self.level, self.sample, self.sigma, self.step) + Mesh.set_data(self, verts=vert, faces=fs.astype(np.uint32), **key) + else: Mesh.set_data(self, **key) + +class Volume3d: + def __init__(self, imgs=None, level=0.25, sample=1, step=1, cmap=None, **key): + self.imgs, self.level, self.sample, self.step = imgs, level, sample, step + self.visible, self.dirty = True, 'geom' + self.cmap = 'gray' if cmap is None else cmap + self.alpha = 1; self.shiness = 0 + self.high_light = False; + self.set_data(**key) + + def set_data(self, imgs=None, level=None, step=None, **key): + if not imgs is None: self.imgs = imgs + if not level is None: self.level = level + if not step is None: self.step = step + if sum([not i is None for i in (imgs, level, step)])>0: + self.dirty = 'geom' + self.visible = key.get('visible', self.visible) + self.alpha = key.get('alpha', self.alpha) + self.high_light = key.get('high_light', False) + self.shiness = key.get('shiness', self.shiness) + self.cmap = key.get('cmap', self.cmap) + self.dirty = self.dirty or True diff --git a/sciapp/object/table.py b/sciapp/object/table.py new file mode 100644 index 00000000..980e7a0d --- /dev/null +++ b/sciapp/object/table.py @@ -0,0 +1,85 @@ +import numpy as np + +class Table(): + def __init__(self, df=None, name='Table'): + self.name = name + self.df = df + self.rg = None + self.props = None + self.snap = None + self.dirty = False + if not df is None: + self.data = df + + @property + def title(self): return self.name + + @property + def nbytes(self): + return self.data.memory_usage().sum() + + @property + def columns(self):return self.data.columns + + @property + def index(self):return self.data.index + + @property + def shape(self): return self.data.shape + + @property + def data(self): return self.df + + def update(self): self.dirty = True + + @data.setter + def data(self, df): + self.df, self.props = df, None + self.rowmsk, self.colmsk = [], [] + self.count_range() + + @property + def style(self): + props, data = self.props, self.data + if props is None or set(props)!=set(data.columns): + ps = [[3, (0,0,0), (0,0,255), 'Text'] for i in data.columns] + self.props = dict(zip(data.columns, ps)) + return self.props + + def subtab(self, mode=True, num=False): + rs, cs = len(self.rowmsk), len(self.colmsk) + mskr = slice(None) if rs==0 else self.rowmsk + mskc = slice(None) if cs==0 else self.colmsk + if mode is None: mskr = mskc = slice(None) + mskr, mskc = self.data.index[mskr], self.data.columns[mskc] + if mode is False and rs>0: mskr = self.data.index.difference(mskr) + if mode is False and cs>0: mskc = self.data.columns.difference(mskc) + subtab = self.data.loc[mskr, mskc] + if num: subtab = subtab.select_dtypes(include=[np.number]) + return subtab + + def snapshot(self, mode=True, num=False): + self.snap = self.subtab(mode, num).copy() + + def set_style(self, col, **key): + for i, name in enumerate(['accu', 'tc', 'lc', 'ln']): + if name in key: self.style[col][i] = key[name] + + def count_range(self): + rg = list(zip(self.data.min(), self.data.max())) + self.rg = dict(zip(self.data.columns, rg)) + + def select(self, rs=[], cs=[], byidx=False): + if byidx: rs, cs = self.df.index[rs], self.df.columns[cs] + self.rowmsk, self.colmsk = rs, cs + + @property + def info(self): + return '%sx%s %.2fM'%(*self.shape, self.nbytes/1024/1024) + +if __name__ == '__main__': + import numpy as np + import pandas as pd + df = pd.DataFrame(np.zeros((10,5)), columns=list('abcde')) + table = Table(df) + diff --git a/sciapp/util/__init__.py b/sciapp/util/__init__.py new file mode 100644 index 00000000..b24cca4c --- /dev/null +++ b/sciapp/util/__init__.py @@ -0,0 +1,4 @@ +from .surfutil import * +from .meshutil import * +from .shputil import * +from .imgutil import * \ No newline at end of file diff --git a/sciapp/util/imgutil.py b/sciapp/util/imgutil.py new file mode 100644 index 00000000..5ff38814 --- /dev/null +++ b/sciapp/util/imgutil.py @@ -0,0 +1,277 @@ +import numpy as np +from scipy.ndimage import affine_transform +try: from numba import njit as jit +except: + print('install numba may be several times faster!') + jit = None + +def affine_jit(img, m, offset, output_shape=0, output=0, order=0, prefilter=0): + kr=m[0]; kc=m[1]; ofr=offset[0]; ofc=offset[1]; + for r in range(output_shape[0]): + for c in range(output_shape[1]): + rr = int(r*kr+ofr) + cc = int(c*kc+ofc) + output[r,c] = img[rr,cc] +if not jit is None: affine_transform = jit(affine_jit) + +def blend(img, out, msk, mode): + if mode=='set': out[:] = img + if mode=='min': np.minimum(out, img, out=out) + if mode=='max': np.maximum(out, img, out=out) + if mode=='msk': + msk = np.logical_not(msk) + out.T[:] *= msk.T + out += img + if isinstance(mode, float): + np.multiply(out, 1-mode, out=out, casting='unsafe') + np.multiply(img, mode, out=img, casting='unsafe') + out += img + +if not jit is None: + @jit + def blend_set(img, out, msk, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = img[r,c] + @jit + def blend_min(img, out, msk, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = min(img[r,c], out[r,c]) + @jit + def blend_max(img, out, msk, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = max(img[r,c], out[r,c]) + @jit + def blend_msk(img, out, msk, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + if img[r,c] != 0: out[r,c] = img[r,c] + @jit + def blend_mix(img, out, msk, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = img[r,c]*mode + out[r,c]*(1-mode) + + def blend_jit(img, out, msk, mode): + if mode=='set': blend_set(img, out, msk, mode) + if mode=='min': blend_min(img, out, msk, mode) + if mode=='max': blend_max(img, out, msk, mode) + if mode=='msk': blend_msk(img, out, msk, mode) + if isinstance(mode, float): blend_mix(img, out, msk, mode) + blend = blend_jit + +def stretch(img, out, rg, log=False): + if img.dtype==np.uint8 and not log and (rg==[(0,255)] or rg==(0,255)): + out[:] = img + elif not log: + ptp = max(rg[1]-rg[0], 1e-6) + np.clip(img, rg[0], rg[1], out=img) + np.subtract(img, rg[0], out=img, casting='unsafe') + np.multiply(img, 255/ptp, out=out, casting='unsafe') + elif img.itemsize<3: + length = 2**(img.itemsize*8) + lut = np.arange(length, dtype=np.float32) + if img.dtype in (np.int8, np.int16): + lut[length//2:] -= length + np.clip(lut, rg[0], rg[1], out=lut) + np.subtract(lut, rg[0]-1, out=lut) + ptp = np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + np.log(lut, out=lut) + lut *= 255/np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + out[:] = lut[img] + else: + fimg = img.ravel().view(np.float32) + fimg = fimg[:img.size].reshape(img.shape) + np.clip(img, rg[0], rg[1], out=fimg) + np.subtract(fimg, rg[0]-1, out=fimg) + ptp = np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + np.log(fimg, out=fimg) + np.multiply(fimg, 255/ptp, out=out, casting='unsafe') + +if not jit is None: + @jit + def stretch_linear(img, out, rg): + ptp = max(rg[1]-rg[0], 1e-6) + for r in range(img.shape[0]): + for c in range(img.shape[1]): + v = (img[r,c]-rg[0])/ptp*255 + out[r,c] = min(max(v, 0), 255) + @jit + def stretch_log(img, out, rg): + ptp = 255/np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + for r in range(img.shape[0]): + for c in range(img.shape[1]): + v = np.log(img[r,c]-rg[0]+1)*ptp + out[r,c] = min(max(v, 0), 255) + @jit + def stretch_lut(img, out, lut): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = lut[img[r,c]] + + def stretch_jit(img, out, rg, log=False): + if img.dtype==np.uint8 and not log and (rg==[(0,255)] or rg==(0,255)): + out[:] = img + elif not log: + stretch_linear(img, out, rg) + elif img.itemsize<3: + length = 2**(img.itemsize*8) + lut = np.arange(length, dtype=np.float32) + if img.dtype in (np.int8, np.int16): + lut[length//2:] -= length + np.clip(lut, rg[0], rg[1], out=lut) + np.subtract(lut, rg[0]-1, out=lut) + ptp = np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + np.log(lut, out=lut) + lut *= 255/np.log(max(rg[1]-rg[0]+1, 1+1e-6)) + stretch_lut(img, out, lut) + else: + stretch_log(img, out, rg) + + stretch = stretch_jit + +def complex_norm(ori, real, img, out): + np.abs(ori, out=out) + return out + +if not jit is None: + @jit + def complex_norm(ori, real, img, out): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + out[r,c] = (img[r,c]**2+real[r,c]**2)**0.5 + return out + +def lookup(img, lut, out, mode='set'): + blend(lut[img], out, img, mode) + +if not jit is None: + @jit + def lookup_set(img, lut, out, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + for i in (0,1,2): + out[r,c,i] = lut[img[r,c],i] + @jit + def lookup_min(img, lut, out, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + for i in (0,1,2): + out[r,c,i] = min(lut[img[r,c],i], out[r,c,i]) + @jit + def lookup_max(img, lut, out, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + for i in (0,1,2): + out[r,c,i] = max(lut[img[r,c],i], out[r,c,i]) + @jit + def lookup_msk(img, lut, out, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + if img[r,c] != 0: + for i in (0,1,2): out[r,c,i] = 0 + for i in (0,1,2): + out[r,c,i] += lut[img[r,c],i] + @jit + def lookup_mix(img, lut, out, mode): + for r in range(img.shape[0]): + for c in range(img.shape[1]): + for i in (0,1,2): + out[r,c,i] = lut[img[r,c],i]*mode + out[r,c,i]*(1-mode) + + def lookup_jit(img, lut, out, mode): + if mode == 'set': lookup_set(img, lut, out, mode) + if mode == 'msk': lookup_msk(img, lut, out, mode) + if mode == 'max': lookup_max(img, lut, out, mode) + if mode == 'min': lookup_min(img, lut, out, mode) + if isinstance(mode, float): lookup_mix(img, lut, out, mode) + + lookup = lookup_jit + +# mode: set, min, max, mix, nor +def mix_img(img, m, o, shp, buf, rgb, byt, rg=(0,255), lut=None, log=True, cns=0, mode='set'): + if img is None or mode == 'hide': return + img = img.reshape((img.shape[0], img.shape[1], -1)) + if isinstance(rg, tuple): rg = [rg]*img.shape[2] + + if isinstance(cns, int): + if np.iscomplexobj(buf): + affine_transform(img[:,:,0].real, m, o, shp, buf.real, 0, prefilter=False) + affine_transform(img[:,:,0].imag, m, o, shp, buf.imag, 0, prefilter=False) + buf = complex_norm(buf, buf.real, buf.imag, buf.real) + else: + affine_transform(img[:,:,cns], m, o, shp, buf, 0, prefilter=False) + stretch(buf, byt, rg[cns], log) + return lookup(byt, lut, rgb, mode) + for i,v in enumerate(cns): + if v==-1: rgb[:,:,i] = 0 + elif mode=='set' and img.dtype==np.uint8 and rg[v]==(0,255) and not log: + affine_transform(img[:,:,v], m, o, shp, rgb[:,:,i], 0, prefilter=False) + else: + affine_transform(img[:,:,v], m, o, shp, buf, 0, prefilter=False) + stretch(buf, byt, rg[v], log) + blend(byt, rgb[:,:,i], byt, mode) + +def cross(winbox, conbox): + two = np.array([winbox, conbox]) + x1, y1 = two[:,:2].max(axis=0) + x2, y2 = two[:,2:].min(axis=0) + return [x1, y1, x2, y2] + +def merge(winbox, conbox): + two = np.array([winbox, conbox]) + x1, y1 = two[:,:2].min(axis=0) + x2, y2 = two[:,2:].max(axis=0) + return [x1, y1, x2, y2] + +def multiply(rect, kx, ky): + return rect * [kx, ky, kx, ky] + +def layx(winbox, conbox): + conw = conbox[2]-conbox[0] + winw = winbox[2]-winbox[0] + if conw winbox[0]: + conbox[0] = winbox[0] + conbox[2] = conbox[0] + conw + elif conbox[2] < winbox[2]: + conbox[2] = winbox[2] + conbox[0] = conbox[2] - conw + +def layy(winbox, conbox): + winh = winbox[3]-winbox[1] + conh = conbox[3]-conbox[1] + if conh winbox[1]: + conbox[1] = winbox[1] + conbox[3] = conbox[1] + conh + elif conbox[3] < winbox[3]: + conbox[3] = winbox[3] + conbox[1] = conbox[3] - conh + +def lay(winbox, conbox): + layx(winbox, conbox) + layy(winbox, conbox) + +def like(ori, cont, cell): + kx = (cont[2]-cont[0])/(ori[2]-ori[0]) + ky = (cont[3]-cont[1])/(ori[3]-ori[1]) + ox = cont[0] - ori[0]*kx + oy = cont[1] - ori[1]*kx + return [cell[0]*kx+ox, cell[1]*ky+oy, + cell[2]*kx+ox, cell[3]*kx+oy] + +def mat(ori, con, cell, cros): + kx = (ori[2]-ori[0])/(con[2]-con[0]) + ky = (ori[3]-ori[1])/(con[3]-con[1]) + ox = (cros[1]-cell[1])*ky + oy = (cros[0]-cell[0])*kx + return (ox, oy), (kx, ky) \ No newline at end of file diff --git a/sciapp/util/meshutil.py b/sciapp/util/meshutil.py new file mode 100644 index 00000000..6bff12d2 --- /dev/null +++ b/sciapp/util/meshutil.py @@ -0,0 +1,304 @@ +import numpy as np + +def create_cube(p1=(0,0,0), p2=(1,1,1)): + p = np.array([[1, 1, 1], [0, 1, 1], [0, 0, 1], [1, 0, 1], + [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 0]]) + faces_p = [0, 1, 2, 3, 0, 3, 4, 5, 0, 5, 6, 1, 1, 6, 7, 2, 7, 4, 3, 2, 4, 7, 6, 5] + vertices = p[faces_p] + vertices *= np.subtract(p2, p1); vertices += p1 + filled = np.resize(np.array([0, 1, 2, 0, 2, 3], dtype=np.uint32), 6 * (2 * 3)) + filled += np.repeat(4 * np.arange(6, dtype=np.uint32), 6) + filled = filled.reshape((len(filled) // 3, 3)) + outline = np.resize(np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=np.uint32), 6 * (2 * 4)) + outline += np.repeat(4 * np.arange(6, dtype=np.uint32), 8) + return vertices, filled, outline.reshape(-1,2) + + +def create_plane(width=1, height=1, width_segments=1, height_segments=1, + direction='+z'): + x_grid = width_segments + y_grid = height_segments + + x_grid1 = x_grid + 1 + y_grid1 = y_grid + 1 + + # Positions, normals and texcoords. + positions = np.zeros(x_grid1 * y_grid1 * 3) + normals = np.zeros(x_grid1 * y_grid1 * 3) + texcoords = np.zeros(x_grid1 * y_grid1 * 2) + + y = np.arange(y_grid1) * height / y_grid - height / 2 + x = np.arange(x_grid1) * width / x_grid - width / 2 + + positions[::3] = np.tile(x, y_grid1) + positions[1::3] = -np.repeat(y, x_grid1) + + normals[2::3] = 1 + + texcoords[::2] = np.tile(np.arange(x_grid1) / x_grid, y_grid1) + texcoords[1::2] = np.repeat(1 - np.arange(y_grid1) / y_grid, x_grid1) + + # Faces and outline. + faces, outline = [], [] + for i_y in range(y_grid): + for i_x in range(x_grid): + a = i_x + x_grid1 * i_y + b = i_x + x_grid1 * (i_y + 1) + c = (i_x + 1) + x_grid1 * (i_y + 1) + d = (i_x + 1) + x_grid1 * i_y + + faces.extend(((a, b, d), (b, c, d))) + outline.extend(((a, b), (b, c), (c, d), (d, a))) + + positions = np.reshape(positions, (-1, 3)) + texcoords = np.reshape(texcoords, (-1, 2)) + normals = np.reshape(normals, (-1, 3)) + + faces = np.reshape(faces, (-1, 3)).astype(np.uint32) + outline = np.reshape(outline, (-1, 2)).astype(np.uint32) + + direction = direction.lower() + if direction in ('-x', '+x'): + shift, neutral_axis = 1, 0 + elif direction in ('-y', '+y'): + shift, neutral_axis = -1, 1 + elif direction in ('-z', '+z'): + shift, neutral_axis = 0, 2 + + sign = -1 if '-' in direction else 1 + + positions = np.roll(positions, shift, -1) + normals = np.roll(normals, shift, -1) * sign + colors = np.ravel(positions) + colors = np.hstack((np.reshape(np.interp(colors, + (np.min(colors), + np.max(colors)), + (0, 1)), + positions.shape), + np.ones((positions.shape[0], 1)))) + colors[..., neutral_axis] = 0 + + vertices = np.zeros(positions.shape[0], + [('position', np.float32, 3), + ('texcoord', np.float32, 2), + ('normal', np.float32, 3), + ('color', np.float32, 4)]) + + vertices['position'] = positions + vertices['texcoord'] = texcoords + vertices['normal'] = normals + vertices['color'] = colors + + return vertices, faces, outline + + +def create_box(width=1, height=1, depth=1, width_segments=1, height_segments=1, + depth_segments=1, planes=None): + planes = (('+x', '-x', '+y', '-y', '+z', '-z') + if planes is None else + [d.lower() for d in planes]) + + w_s, h_s, d_s = width_segments, height_segments, depth_segments + + planes_m = [] + if '-z' in planes: + planes_m.append(create_plane(width, depth, w_s, d_s, '-z')) + planes_m[-1][0]['position'][..., 2] -= height / 2 + if '+z' in planes: + planes_m.append(create_plane(width, depth, w_s, d_s, '+z')) + planes_m[-1][0]['position'][..., 2] += height / 2 + + if '-y' in planes: + planes_m.append(create_plane(height, width, h_s, w_s, '-y')) + planes_m[-1][0]['position'][..., 1] -= depth / 2 + if '+y' in planes: + planes_m.append(create_plane(height, width, h_s, w_s, '+y')) + planes_m[-1][0]['position'][..., 1] += depth / 2 + + if '-x' in planes: + planes_m.append(create_plane(depth, height, d_s, h_s, '-x')) + planes_m[-1][0]['position'][..., 0] -= width / 2 + if '+x' in planes: + planes_m.append(create_plane(depth, height, d_s, h_s, '+x')) + planes_m[-1][0]['position'][..., 0] += width / 2 + + positions = np.zeros((0, 3), dtype=np.float32) + texcoords = np.zeros((0, 2), dtype=np.float32) + normals = np.zeros((0, 3), dtype=np.float32) + + faces = np.zeros((0, 3), dtype=np.uint32) + outline = np.zeros((0, 2), dtype=np.uint32) + + offset = 0 + for vertices_p, faces_p, outline_p in planes_m: + positions = np.vstack((positions, vertices_p['position'])) + texcoords = np.vstack((texcoords, vertices_p['texcoord'])) + normals = np.vstack((normals, vertices_p['normal'])) + + faces = np.vstack((faces, faces_p + offset)) + outline = np.vstack((outline, outline_p + offset)) + offset += vertices_p['position'].shape[0] + + vertices = np.zeros(positions.shape[0], + [('position', np.float32, 3), + ('texcoord', np.float32, 2), + ('normal', np.float32, 3), + ('color', np.float32, 4)]) + + colors = np.ravel(positions) + colors = np.hstack((np.reshape(np.interp(colors, + (np.min(colors), + np.max(colors)), + (0, 1)), + positions.shape), + np.ones((positions.shape[0], 1)))) + + vertices['position'] = positions + vertices['texcoord'] = texcoords + vertices['normal'] = normals + vertices['color'] = colors + + return vertices, faces, outline + + +def create_sphere(rows, cols): + verts = np.empty((rows+1, cols, 3), dtype=np.float32) + + # compute vertices + phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) + s = np.sin(phi) + verts[..., 2] = np.cos(phi) + th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) + th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1, 1)) + verts[..., 0] = s * np.cos(th) + verts[..., 1] = s * np.sin(th) + # remove redundant vertices from top and bottom + verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] + + # compute faces + faces = np.empty((rows*cols*2, 3), dtype=np.uint32) + rowtemplate1 = (((np.arange(cols).reshape(cols, 1) + + np.array([[1, 0, 0]])) % cols) + + np.array([[0, 0, cols]])) + rowtemplate2 = (((np.arange(cols).reshape(cols, 1) + + np.array([[1, 0, 1]])) % cols) + + np.array([[0, cols, cols]])) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * cols + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols + # cut off zero-area triangles at top and bottom + faces = faces[cols:-cols] + + # adjust for redundant vertices that were removed from top and bottom + vmin = cols-1 + faces[faces < vmin] = vmin + faces -= vmin + vmax = verts.shape[0]-1 + faces[faces > vmax] = vmax + return verts, faces + +def create_grid_mesh(xs, ys, zs): + h, w = xs.shape + vts = np.array([xs, ys, zs], dtype=np.float32) + did = np.array([[0, 1, 1+w, 0, 1+w, w]], dtype=np.uint32) + rcs = np.arange(0,w*h-w,w)[:,None] + np.arange(0,w-1,1) + faces = rcs.reshape(-1,1) + did + return vts.reshape(3,-1).T.copy(), faces.reshape(-1,3) + +def create_ball(o, r, rows=16, cols=16): + ball = create_sphere(rows, cols) + ball[0][:] *= r; ball[0][:] += o; + return ball + +def create_balls(os, rs, cs=None, rows=16, cols=16): + os, rs = np.asarray(os), np.asarray(rs) + verts, faces = create_sphere(rows, cols) + if not isinstance(cs, tuple) and not cs is None: + cs = np.repeat(cs, len(verts)) + offset = np.arange(len(os)) * len(verts) + verts = verts[None, :, :] * rs[:, None, None] + verts += os[:, None, :] + faces = faces[None,:,:] + offset[:,None,None] + return verts.reshape(-1,3), faces.reshape(-1,3), cs + +def create_line(xs, ys, zs): + vts = np.array([xs, ys, zs], dtype=np.float32) + fs = np.arange(len(xs)) + return vts.T, np.array([fs[:-1], fs[1:]], np.uint32).T + +def mesh_merge(vts, fs, cs): + cc = [None]*len(vts) if isinstance(cs, tuple) else cs + def makecolor(c, vts): + if c is None: return None + if len(c)==len(vts): return c + return np.repeat([c], len(vts), axis=0) + cc = [makecolor(c,v) for c,v in zip(cc, vts)] + offset = np.cumsum([0]+[len(i) for i in vts[:-1]]) + if not isinstance(cs, tuple): cs = np.vstack(cc) + for f,s in zip(fs, offset): f += s + return np.vstack(vts), np.vstack(fs), cs + +def create_lines(xs, ys, zs, cs): + vtsfs = [create_line(x,y,z) for x,y,z in zip(xs,ys,zs)] + return mesh_merge(*list(zip(*vtsfs)), cs) + +def create_bound(p1, p2, nx=1, ny=1, nz=1): + vts, fs, ls = create_box(1, 1, 1, nx, ny, nz) + vts['position'] *= np.subtract(p2, p1) + vts['position'] += p1 - vts['position'].min(axis=0) + return vts['position'], ls + +def create_surface2d(img, sample=1, sigma=0, k=0.3): + from scipy.ndimage import gaussian_filter + #start = time() + img = img[::sample, ::sample].astype(np.float32) + if sigma>0: img = gaussian_filter(img, sigma) + xs, ys = np.mgrid[:img.shape[0],:img.shape[1]] + xs *= sample; ys *= sample + return create_grid_mesh(xs, ys, img*k) + +def create_surface3d(imgs, level, sample=1, sigma=0, step=1): + from skimage.measure import marching_cubes_lewiner + from scipy.ndimage import gaussian_filter + imgs = imgs[::sample,::sample,::sample] + if sigma>0: imgs = gaussian_filter(imgs, sigma) + vts, fs, ns, cs = marching_cubes_lewiner(imgs, level, step_size=step) + return vts * sample, fs + +def build_arrow(v1, v2, rs, re, ts, te, c): + v = (v2-v1)/np.linalg.norm(v2-v1) + ss, ee = v1 + v*rs*ts, v2 - v*re*te + vx = np.cross(v, np.random.rand(3)) + vx /= np.linalg.norm(vx) + vy = np.cross(vx, v) + angs = np.linspace(0, np.pi*2, 17) + vas = np.array([np.cos(angs), np.sin(angs)]) + vxy = np.dot(vas.T, np.array([vx, vy])) + vts = np.vstack((v1, ss + rs * vxy, ee + re * vxy, v2)) + fs1 = build_pringidx(0, 16, 1) + fs = build_twringidx(16, 1) + fs2 = build_pringidx(35, 16, 18) + face = np.vstack((fs1, fs, fs2)) + ns = np.vstack((-v, vxy, vxy, v)).astype(np.float32) + cs = (np.ones((len(vts), 3))*c).astype(np.float32) + return vts.astype(np.float32), face, ns, cs + +def build_arrows(v1s, v2s, rss, res, tss, tes, cs): + if not isinstance(cs, list): cs = [cs] * len(v1s) + if not isinstance(tss, list): tss = [tss] * len(v1s) + if not isinstance(tes, list): tes = [tes] * len(v1s) + if not isinstance(rss, list): rss = [rss] * len(v1s) + if not isinstance(res, list): res = [res] * len(v1s) + vtss, fss, nss, css = [], [], [], [] + s = 0 + for v1, v2, rs, re, ts, te, c in zip(v1s, v2s, rss, res, tss, tes, cs): + if np.linalg.norm(v1-v2) < 0.1: continue + vv, ff, nn, cc = build_arrow(v1, v2, rs, re, ts, te, c) + fss.append(ff+s) + s += len(vv) + vtss.append(vv) + nss.append(nn) + css.append(cc) + print(np.vstack(vtss).shape, np.vstack(fss).shape, np.vstack(nss).shape, np.vstack(css).shape) + return np.vstack(vtss), np.vstack(fss), np.vstack(nss), np.vstack(css) diff --git a/sciapp/util/shputil.py b/sciapp/util/shputil.py new file mode 100644 index 00000000..b5cd9506 --- /dev/null +++ b/sciapp/util/shputil.py @@ -0,0 +1,116 @@ +import numpy as np +from skimage import draw +from ..object.shape import * + +def offset(shp, dx, dy): + if shp.dtype in {'rectangle', 'ellipse', 'circle'}: + shp.body[:2] += dx, dy + elif shp.dtype in {'rectangles', 'ellipses', 'circles'}: + shp.body[:,:2] += dx, dy + elif isinstance(shp, np.ndarray): + shp += dx, dy + elif isinstance(shp.body, list): + for i in shp.body: offset(i, dx, dy) + +def mark2shp(mark): + style = mark.copy() + style.pop('body') + keys = {'point':Point, 'points':Points, 'line':Line, 'lines':Lines, + 'polygon':Polygon, 'polygons':Polygons, 'circle':Circle, + 'circles':Circles, 'rectangle':Rectangle, 'rectangles':Rectangles, + 'ellipse':Ellipse, 'ellipses':Ellipses, 'text':Text, 'texts':Texts} + if mark['type'] in keys: return keys[mark['type']](mark['body'], **style) + if mark['type']=='layer': + return Layer([mark2shp(i) for i in mark['body']], **style) + if mark['type']=='layers': + return Layers(dict(zip(mark['body'].keys(), + [mark2shp(i) for i in mark['body'].values()])), **style) + +def json2shp(obj): + if obj['type']=='Point': + return Point(obj['coordinates']) + if obj['type']=='MultiPoint': + return Points(obj['coordinates']) + if obj['type']=='LineString': + return Line(obj['coordinates']) + if obj['type']=='MultiLineString': + return Lines(obj['coordinates']) + if obj['type']=='Polygon': + return Polygon(obj['coordinates']) + if obj['type']=='MultiPolygon': + return Polygons(obj['coordinates']) + if obj['type']=='GeometryCollection': + return Layer([json2shp(i) for i in obj['geometries']]) + +def geom2shp(obj): return json2shp(geom.mapping(obj)) + +def geom_flatten(obj, geoms=None): + geoms, root = ([], True) if geoms is None else (geoms, False) + if isinstance(obj, geom.GeometryCollection): + for i in obj: geom_flatten(i, geoms) + elif type(obj) in {geom.MultiPolygon, geom.MultiPoint, geom.MultiLineString}: + geoms.extend(list(obj.geoms)) + else: geoms.append(obj) + if root: return geom.GeometryCollection(geoms) + +def geom_union(obj): + return geom_flatten(unary_union(geom_flatten(obj))) + + +def draw_circle(r): + xs, ys = np.mgrid[-r:r+1, -r:r+1] + rcs = np.where((xs**2 + ys**2)=0) & (rr=0) & (cc0: rr, cc = draw_lines(rr, cc, img.shape, lw) + else: rr, cc = draw.polygon(rr, cc, img.shape) + img[rr, cc] += color + if type(shp) is geom.MultiLineString: + for i in shp: draw_shp(i, img, color, lw) + if type(shp) is geom.Polygon and lw>0: + draw_shp(shp.exterior, img, color, lw) + for i in shp.interiors: draw_shp(i, img, color, lw) + if type(shp) is geom.Polygon and lw==0: + draw_shp(shp.exterior, img, color, lw) + for i in shp.interiors: draw_shp(i, img, -color, lw) + if type(shp) is geom.MultiPolygon: + for i in shp: draw_shp(i, img, color, lw) + if type(shp) is geom.GeometryCollection: + for i in shp: draw_shp(i, img, color, lw) + return img \ No newline at end of file diff --git a/imagepy/core/myvi/util.py b/sciapp/util/surfutil.py similarity index 56% rename from imagepy/core/myvi/util.py rename to sciapp/util/surfutil.py index 0c6409b8..607b45c5 100644 --- a/imagepy/core/myvi/util.py +++ b/sciapp/util/surfutil.py @@ -1,7 +1,20 @@ from time import time import numpy as np from math import pi -from .txtmark import lib + +lib = {'0':([(0,0.5,0.5,0,0)],[(1,1,0,0,1)],0.5), + '1':([(0.25,0.25)], [(0,1)], 0.5), + '2':([(0,0.5,0.5,0,0,0.5)], [(1,1,0.5,0.5,0,0)], 0.5), + '3':([(0,0.5,0.5,0),(0,0.5)],[(1,1,0,0),(0.5,0.5)], 0.5), + '4':([(0,0,0.5),(0.5,0.5)],[(1,0.5,0.5),(1,0)],0.5), + '5':([(0.5,0,0,0.5,0.5,0)], [(1,1,0.5,0.5,0,0)], 0.5), + '6':([(0.5,0,0,0.5,0.5,0,0)], [(1,1,0.5,0.5,0,0,0.5)], 0.5), + '7':([(0,0.5,0.5)], [(1,1,0)], 0.5), + '8':([(0.5,0.5,0,0,0.5,0.5,0,0)], [(0.5,1,1,0.5,0.5,0,0,0.5)], 0.5), + '9':([(0.5,0.5,0,0,0.5,0.5,0)], [(0.5,1,1,0.5,0.5,0,0)], 0.5), + 'I':([(0,0.5),(0.25,0.25),(0,0.5)],[(1,1),(1,0),(0,0)],0.5), + 'D':([(0,0.25,0.4,0.5,0.5,0.4,0.25,0),(0.1,0.1)],[(1,1,0.9,0.75,0.25,0.1,0,0),(0,1)],0.5), + ':':([(0.2,0.3),(0.2,0.3)],[(0.75,0.75),(0.25,0.25)],0.5)} def count_ns(vts, fs): dv1 = vts[fs[:,1]] - vts[fs[:,2]] @@ -14,6 +27,17 @@ def count_ns(vts, fs): buf /= np.linalg.norm(buf, axis=1).reshape((-1,1)) return buf +def build_twringidx(n, offset=0): + idx = np.array([[0,1,n+1],[n+1,n+2,1]]) + idx = idx[np.arange(n*2)%2].T + np.arange(n*2)//2 + return (idx.T+offset).astype(np.uint32) + +def build_pringidx(p, n, offset=0): + ridx = np.array([[0,0,1]]*n, dtype=np.uint32) + ridx += np.arange(n, dtype=np.uint32).reshape((-1,1))+offset + ridx[:,0] = p + return ridx + def build_grididx(r, c): idx = np.arange(r*c, dtype=np.uint32) rs, cs = idx//c, idx%c @@ -24,7 +48,7 @@ def build_grididx(r, c): def build_surf2d(img, ds=1, sigma=0, k=0.2): from skimage.filters import sobel_h, sobel_v from scipy.ndimage import gaussian_filter - start = time() + #start = time() img = img[::-ds, ::ds] img = gaussian_filter(img, sigma) r, c = img.shape @@ -40,9 +64,6 @@ def build_surf2d(img, ds=1, sigma=0, k=0.2): cy[:,1], cy[:,2] = 1, dy.ravel() ns = np.cross(cx, cy) ns = (ns.T/np.linalg.norm(ns, axis=1)).astype(np.float32).T - - #ns = count_ns(vts, fs) - print(time()-start) return vts, fs, ns, cs def build_surf3d(imgs, ds, level, step=1, c=(1,0,0)): @@ -108,6 +129,43 @@ def build_lines(xs, ys, zs, cs): css.append(cc) return np.vstack(vtss), np.vstack(fss), np.vstack(nss), np.vstack(css) +def build_arrow(v1, v2, rs, re, ts, te, c): + v = (v2-v1)/np.linalg.norm(v2-v1) + ss, ee = v1 + v*rs*ts, v2 - v*re*te + vx = np.cross(v, np.random.rand(3)) + vx /= np.linalg.norm(vx) + vy = np.cross(vx, v) + angs = np.linspace(0, np.pi*2, 17) + vas = np.array([np.cos(angs), np.sin(angs)]) + vxy = np.dot(vas.T, np.array([vx, vy])) + vts = np.vstack((v1, ss + rs * vxy, ee + re * vxy, v2)) + fs1 = build_pringidx(0, 16, 1) + fs = build_twringidx(16, 1) + fs2 = build_pringidx(35, 16, 18) + face = np.vstack((fs1, fs, fs2)) + ns = np.vstack((-v, vxy, vxy, v)).astype(np.float32) + cs = (np.ones((len(vts), 3))*c).astype(np.float32) + return vts.astype(np.float32), face, ns, cs + +def build_arrows(v1s, v2s, rss, res, tss, tes, cs): + if not isinstance(cs, list): cs = [cs] * len(v1s) + if not isinstance(tss, list): tss = [tss] * len(v1s) + if not isinstance(tes, list): tes = [tes] * len(v1s) + if not isinstance(rss, list): rss = [rss] * len(v1s) + if not isinstance(res, list): res = [res] * len(v1s) + vtss, fss, nss, css = [], [], [], [] + s = 0 + for v1, v2, rs, re, ts, te, c in zip(v1s, v2s, rss, res, tss, tes, cs): + if np.linalg.norm(v1-v2) < 0.1: continue + vv, ff, nn, cc = build_arrow(v1, v2, rs, re, ts, te, c) + fss.append(ff+s) + s += len(vv) + vtss.append(vv) + nss.append(nn) + css.append(cc) + print(np.vstack(vtss).shape, np.vstack(fss).shape, np.vstack(nss).shape, np.vstack(css).shape) + return np.vstack(vtss), np.vstack(fss), np.vstack(nss), np.vstack(css) + def build_mark(cont, pos, dz, h, color): vts, fss = [], [] s, sw = 0, 0 @@ -137,6 +195,40 @@ def build_marks(conts, poss, dz, h, color): return np.vstack(vtss), np.vstack(fss), np.vstack(pps), h, color +def build_cube(p1, p2, color=(1,1,1)): + (x1,y1,z1),(x2,y2,z2) = p1, p2 + xs = (x1,x2,x2,x1,x1,x1,x1,x1,x1,x2,x2,x1,x2,x2,x2,x2) + ys = (y1,y1,y1,y1,y1,y2,y2,y1,y2,y2,y2,y2,y2,y1,y1,y2) + zs = (z1,z1,z2,z2,z1,z1,z2,z2,z2,z2,z1,z1,z1,z1,z2,z2) + return build_line(xs, ys, zs, color) + +def build_img_cube(imgs, ds=1): + imgs = imgs[::ds,::ds,::ds] + (h, r, c), total = imgs.shape[:3], 0 + print(h, r, c) + vtss, fss, nss, css = [], [], [], [] + shp = [(h,r,c,h*r), (h,c,r,h*c), (r,c,h,r*c)] + nn = [[(0,0,-1),(0,0,1)], [(0,1,0),(0,-1,0)], [(1,0,0),(-1,0,0)]] + for i in (0,1,2): + rs, cs, fs12 = build_grididx(*shp[i][:2]) + idx1, idx2 = [rs*ds, cs*ds], [rs*ds, cs*ds] + rcs1, rcs2 = [rs, cs], [rs, cs] + rcs1.insert(2-i, 0); rcs2.insert(2-i, -1) + vs1, vs2 = imgs[tuple(rcs1)]/255, imgs[tuple(rcs2)]/255 + idx1.insert(2-i, rs*0); idx2.insert(2-i, cs*0+shp[i][2]*ds-1) + vtss.append(np.array(idx1, dtype=np.float32).T) + vtss.append(np.array(idx2, dtype=np.float32).T) + css.append((np.ones((1, 3))*vs1.reshape((len(vs1),-1))).astype(np.float32)) + css.append((np.ones((1, 3))*vs2.reshape((len(vs1),-1))).astype(np.float32)) + nss.append((np.ones((shp[i][3],1))*[nn[i][0]]).astype(np.float32)) + nss.append((np.ones((shp[i][3],1))*[nn[i][1]]).astype(np.float32)) + fss.extend([fs12+total, fs12+(total+shp[i][0]*shp[i][1])]) + total += shp[i][3] * 2 + return np.vstack(vtss), np.vstack(fss), np.vstack(nss), np.vstack(css) + +def build_img_box(imgs, color=(1,1,1)): + return build_cube((-1,-1,-1), imgs.shape[:3], color) + cmp = {'rainbow':[(127, 0, 255), (43, 126, 246), (42, 220, 220), (128, 254, 179), (212, 220, 127), (255, 126, 65), (255, 0, 0)], 'jet':[(0, 0, 127), (0, 40, 255), (0, 212, 255), (124, 255, 121), (255, 229, 0), (255, 70, 0), (127, 0, 0)], 'ocean':[(0, 127, 0), (0, 64, 42), (0, 0, 85), (0, 64, 128), (0, 127, 170), (129, 192, 213), (255, 255, 255)], diff --git a/sciapp/util/xlreport.py b/sciapp/util/xlreport.py new file mode 100644 index 00000000..18b3931c --- /dev/null +++ b/sciapp/util/xlreport.py @@ -0,0 +1,116 @@ +import openpyxl as pyxl +from openpyxl.utils.units import cm_to_EMU, EMU_to_pixels +from io import BytesIO +from openpyxl.drawing.image import Image +from PIL import Image as PImage +import numpy as np +import pandas as pd +from copy import copy + +if not '.rpt' in pyxl.reader.excel.SUPPORTED_FORMATS: + pyxl.reader.excel.SUPPORTED_FORMATS += ('.rpt',) + +def parse(wb): + rst, key = [], {} + for ws in wb.worksheets: + rst.append((ws.title, [])) + for row in ws.rows: + for cell in row: + if not isinstance(cell.value, str):continue + if cell.value[0]+cell.value[-1] != '{}': continue + cont = cell.value[1:-1].strip() + tp = cont.split(' ')[0] + cont = cont[len(tp):].strip() + note, value = 'no description', None + if '#' in cont: + note = cont.split('#')[-1].strip() + cont = cont[:cont.index('#')].strip() + if '=' in cont: + value = cont.split('=')[1].strip() + name = cont[:cont.index('=')].strip() + else: name = cont + + rst[-1][-1].append(((cell.row, cell.col_idx), + [tp, name, value, note])) + key[name] = [tp, name, value, note] + return rst, key + +def trans(img, W, H, margin, scale): + h, w = img.shape[:2] + h2, w2 = int(h/margin), int(w/margin) + if scale: + if W/H > w/h: w2 = int(W/H*h2) + if H/W > h/w: h2 = int(H/W*w2) + newshp = (h2, w2) if img.ndim==2 else (h2, w2, 3) + blank = np.ones(newshp, dtype=np.uint8) * 255 + blank[(h2-h)//2:(h2-h)//2+h, (w2-w)//2:(w2-w)//2+w] = img + return blank + +def add_image(wb, ws, pos, key, img): + if img is None: return + w, h, margin, scale = eval(key[2]) + img = trans(img, w, h, margin, scale==0) + img = PImage.fromarray(img) + image_file = BytesIO() + img.save(image_file, 'png') + ref = BytesIO(image_file.getvalue()) + image = Image(img) + image.ref = ref + image.height = EMU_to_pixels(cm_to_EMU(h)) + image.width = EMU_to_pixels(cm_to_EMU(w)) + wb[ws].add_image(image, wb[ws].cell(*pos).coordinate) + +def add_table(wb, ws, pos, key, data): + if data is None: return + vs = data.values + idx, cols = data.index, data.columns + dr, dc, ir, ic = 1, 1, 0, 0 + if key[2] != None: dr, dc, ir, ic = eval(key[2]) + for r in range(vs.shape[0]): + if ir!=0: wb[ws].cell(pos[0]+r*dr, pos[1]+ir, idx[r]) + for c in range(vs.shape[1]): + if ic!=0: wb[ws].cell(pos[0]+ic, pos[1]+c*dc, cols[c]) + for r in range(vs.shape[0]): + for c in range(vs.shape[1]): + wb[ws].cell(pos[0]+r*dr, pos[1]+c*dc, vs[r,c]) + +def fill_value(wb, infos, para): + for worksheet in infos: + ws, info = worksheet + for pos, key in info: + if not key[1] in para: continue + if key[0] in ('str', 'int', 'float', 'bool', 'txt', 'list', 'date'): + wb[ws].cell(pos[0], pos[1], para[key[1]]) + if key[0] == 'img': + add_image(wb, ws, pos, key, para[key[1]]) + if key[0] == 'tab': + add_table(wb, ws, pos, key, para[key[1]]) + +def repair(wb): + for ws in wb.worksheets: + for cr in ws.merged_cells: + ltc = ws.cell(cr.min_row, cr.min_col) + vb, hb = ltc.border.left, ltc.border.top + for r in range(cr.min_row, cr.max_row+1): + for c in range(cr.min_col, cr.max_col+1): + cur = copy(ws.cell(r, c).border) + cur.left, cur.right = copy(vb), copy(vb) + cur.top, cur.bottom = copy(hb), copy(hb) + ws.cell(r, c).border = cur + +if __name__ == '__main__': + rst = pd.read_csv('rst.csv') + img = np.arange(10000, dtype=np.uint8).reshape((100,100)) + data = {'Sample_ID':'Coins-0001', 'Operator_Name':'YX Dragon', 'Date':'2019-02-05', + 'Record':rst, 'Original_Image':img, 'Mask_Image':img} + + wb = pyxl.load_workbook('Coins Report.xlsx',) + repair(wb) + ws = wb.active + + + infos = parse(wb) + print(infos) + fill_value(wb, infos, data) + wb.save('new.xlsx') + diff --git a/sciwx/__init__.py b/sciwx/__init__.py new file mode 100644 index 00000000..2a406043 --- /dev/null +++ b/sciwx/__init__.py @@ -0,0 +1,15 @@ +from sciapp import Manager +import numpy as np + +ColorManager = Manager() +import matplotlib.pyplot as plt +for i in plt.colormaps()[::-1]: + cm = plt.get_cmap(i) + if i[-2:]=='_r': continue + vs = np.linspace(0, cm.N, 256, endpoint=False) + lut = cm(vs.astype(np.uint8), bytes=True)[:,:3] + ColorManager.add(i, lut) +del plt +graylut = ColorManager.get('gray') +ColorManager.add('Grays', graylut) +ColorManager.remove('gray') \ No newline at end of file diff --git a/sciwx/app/__init__.py b/sciwx/app/__init__.py new file mode 100644 index 00000000..81a20e74 --- /dev/null +++ b/sciwx/app/__init__.py @@ -0,0 +1,4 @@ +from sciapp import App, Source +# from .sciapp import SciApp +from .imgapp import ImageApp +from .miniapp import MiniApp \ No newline at end of file diff --git a/sciwx/app/canvasapp.py b/sciwx/app/canvasapp.py new file mode 100644 index 00000000..d3de4d35 --- /dev/null +++ b/sciwx/app/canvasapp.py @@ -0,0 +1,71 @@ +import wx, wx.lib.agw.aui as aui +from ..canvas.mcanvas import MCanvas +from ..widgets import ToolBar, MenuBar, ParaDialog +from sciapp import App + +class CanvasApp(wx.Frame, App): + def __init__(self, parent=None, autofit=False): + App.__init__(self) + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'CanvasFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.canvas = MCanvas(self, autofit=autofit) + sizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0) + self.SetSizer(sizer) + + self.set_rg = self.canvas.set_rg + self.set_lut = self.canvas.set_rg + self.set_log = self.canvas.set_log + self.set_mode = self.canvas.set_mode + self.set_tool = self.canvas.set_tool + self.set_cn = self.canvas.set_cn + + self.Bind(wx.EVT_IDLE, self.on_idle) + self.Bind(wx.EVT_ACTIVATE, self.on_valid) + self.Bind(wx.EVT_CLOSE, self.on_close) + + self.status = self.CreateStatusBar( 1 ) + + def set_imgs(self, imgs): + self.remove_img(self.canvas.image) + self.canvas.set_imgs(imgs) + self.add_img(self.canvas.image) + + def set_img(self, img, b=False): + self.remove_img(self.canvas.image) + self.canvas.set_img(img, b) + self.add_img(self.canvas.image) + + def on_idle(self, event): + if self.GetTitle()!=self.canvas.image.title: + self.SetTitle(self.canvas.image.title) + + def set_title(self, ips): self.SetTitle(ips.title) + + def info(self, info): self.status.SetStatusText(info) + + def on_valid(self, event): pass + + def on_close(self, event): + event.Skip() + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def add_menubar(self): + menubar = MenuBar(self) + self.SetMenuBar(menubar) + return menubar + + def show_para(self, title, view, para, on_handle=None, on_ok=None, on_cancel=None, preview=False, modal=True): + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + return dialog.show() \ No newline at end of file diff --git a/sciwx/app/imgapp.py b/sciwx/app/imgapp.py new file mode 100644 index 00000000..aad2ed0d --- /dev/null +++ b/sciwx/app/imgapp.py @@ -0,0 +1,182 @@ +import wx, os, sys +import time, threading +sys.path.append('../../') +import wx.lib.agw.aui as aui +from sciwx.widgets import MenuBar, ToolBar, ParaDialog +from sciwx.canvas import CanvasNoteBook +from sciwx.widgets import ProgressBar +from sciwx.grid import GridFrame +from sciwx.mesh import Canvas3DFrame +from sciwx.text import MDFrame, TextFrame +from sciwx.plot import PlotFrame +from sciapp import App, Source +from sciapp.object import Image, Table + +class ImageApp(wx.Frame, App): + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImageApp', + size = wx.Size(800,600), pos = wx.DefaultPosition, + style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + App.__init__(self) + self.SetSizeHints( wx.Size(600,-1) ) + + sizer = wx.BoxSizer(wx.VERTICAL) + self.toolbar = ToolBar(self) + self.toolbar.Fit() + sizer.Add(self.toolbar, 0, wx.EXPAND |wx.ALL, 0) + + self.canvasnb = CanvasNoteBook(self) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_img) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_img) + sizer.Add(self.canvasnb, 1, wx.EXPAND |wx.ALL, 0) + + self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizersta = wx.BoxSizer( wx.HORIZONTAL ) + self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImageApp", wx.DefaultPosition, wx.DefaultSize, 0 ) + sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) + self.pro_bar = ProgressBar(stapanel) + sizersta.Add( self.pro_bar, 0, wx.ALL, 2 ) + stapanel.SetSizer(sizersta) + sizer.Add(self.stapanel, 0, wx.EXPAND | wx.ALL, 0) + self.SetSizer(sizer) + self.Layout() + self.Centre( wx.BOTH ) + self.Bind(wx.EVT_CLOSE, self.on_close) + + def record_macros(self, x): print(x) + + def add_task(self, task): + self.task_manager.add(task.title, task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def remove_task(self, task): + self.task_manager.remove(obj=task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def on_active_img(self, event): + self.active_img(self.canvasnb.canvas().image.name) + # self.add_img_win(self.canvasnb.canvas()) + + def on_close_img(self, event): + canvas = event.GetEventObject().GetPage(event.GetSelection()) + App.close_img(self, canvas.image.title) + + def on_new_tab(self, event): + self.add_tab(event.GetEventObject().grid.table) + self.add_tab_win(event.GetEventObject().grid) + + def on_close_tab(self, event): + self.remove_tab_win(event.GetEventObject().grid) + self.remove_tab(event.GetEventObject().grid.table) + event.Skip() + + def info(self, value): + wx.CallAfter(self.txt_info.SetLabel, value) + + def set_progress(self, value): + v = max(min(value, 100), 0) + self.pro_bar.SetValue(v) + if value==-1: + self.pro_bar.Hide() + elif not self.pro_bar.IsShown(): + self.pro_bar.Show() + self.stapanel.GetSizer().Layout() + self.pro_bar.Update() + + def on_close(self, event): + self.Destroy() + sys.exit() + + def _show_img(self, img, title=None): + canvas = self.canvasnb.add_canvas() + if not isinstance(img, Image): + img = Image(img, title) + App.show_img(self, img, img.title) + canvas.set_img(img) + + def show_img(self, img, title=None): + wx.CallAfter(self._show_img, img, title) + + def _show_table(self, tab, title): + cframe = GridFrame(self) + grid = cframe.grid + grid.set_data(tab) + if not title is None: + grid.table.name = title + cframe.Bind(wx.EVT_ACTIVATE, self.on_new_tab) + cframe.Bind(wx.EVT_CLOSE, self.on_close_tab) + cframe.Show() + + def show_table(self, tab, title=None): + wx.CallAfter(self._show_table, tab, title) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def info(self, cont): + wx.CallAfter(self.txt_info.SetLabel, cont) + + def _alert(self, info, title='ImagePy'): + dialog=wx.MessageDialog(self, info, title, wx.OK) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + + def alert(self, info, title='ImagePy'): + wx.CallAfter(self._alert, info, title) + + def yes_no(self, info, title='ImagePy'): + dialog = wx.MessageDialog(self, info, title, wx.YES_NO | wx.CANCEL) + rst = dialog.ShowModal() + dialog.Destroy() + dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} + return dic[rst] + + def get_path(self, title, filt, io, name=''): + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + dialog = wx.FileDialog(self, title, '', name, filt, dic[io]) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + + def show_para(self, title, para, view, on_handle=None, on_ok=None, on_cancel=None, preview=False, modal=True): + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + return dialog.show() + + def start(imgs=[], plgs=[]): + app = wx.App(False) + frame = ImageApp(None) + for name, i in imgs: frame.show_img([i], name) + for name, i in plgs: frame.toolbar.add_tool(name, i) + frame.Show() + app.MainLoop() + +if __name__ == '__main__': + from skimage.data import camera + from sciwx.plugins.filters import Gaussian + + ImageApp.start( + imgs = [('camera', camera())], + plgs=[('G', Gaussian), ('T', Gaussian)]) \ No newline at end of file diff --git a/sciwx/app/miniapp.py b/sciwx/app/miniapp.py new file mode 100644 index 00000000..baee093b --- /dev/null +++ b/sciwx/app/miniapp.py @@ -0,0 +1,378 @@ +import wx, os, sys +import time, threading +sys.path.append('../../') +import wx.lib.agw.aui as aui +from sciwx.widgets import MenuBar, ToolBar, RibbonBar, ParaDialog +from sciwx.canvas import CanvasNoteBook +from sciwx.grid import GridNoteBook +from sciwx.widgets import ProgressBar +from sciwx.mesh import Canvas3DFrame +from sciwx.text import MDFrame, TextFrame +from sciwx.plot import PlotFrame +from sciapp.object import Image, Table +from sciapp import App, Source + +from sciapp.action.plugin.filters import Gaussian +from sciapp.action.plugin.generalio import OpenFile, SaveImage + +class MiniApp(wx.Frame, App): + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImagePy', + size = wx.Size(800,600), pos = wx.DefaultPosition, + style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + App.__init__(self) + self.auimgr = aui.AuiManager() + self.auimgr.SetManagedWindow( self ) + self.SetSizeHints( wx.Size(800, 600) ) + + self.init_menu() + self.init_canvas() + self.init_table() + self.init_status() + + self.Layout() + self.auimgr.Update() + self.Fit() + self.Centre( wx.BOTH ) + + self.Bind(wx.EVT_CLOSE, self.on_close) + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_pan_close) + + def init_status(self): + self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizersta = wx.BoxSizer( wx.HORIZONTAL ) + self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImagePy v0.2", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.txt_info.Wrap( -1 ) + sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) + #self.pro_bar = wx.Gauge( stapanel, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size( 100,15 ), wx.GA_HORIZONTAL ) + self.pro_bar = ProgressBar(stapanel) + sizersta.Add( self.pro_bar, 0, wx.ALL, 2 ) + stapanel.SetSizer(sizersta) + class OpenDrop(wx.FileDropTarget): + def __init__(self, app): + wx.FileDropTarget.__init__(self) + self.app = app + def OnDropFiles(self, x, y, path): + self.app.run_macros(["Open>{'path':'%s'}"%i.replace('\\', '/') for i in path]) + stapanel.SetDropTarget(OpenDrop(self)) + self.auimgr.AddPane( stapanel, aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ) + .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) + . MinSize(wx.Size(-1, 20)). MaxSize(wx.Size(-1, 20)).Layer( 10 ) ) + + def init_menu(self): + self.menubar = MenuBar(self) + + def init_menu(self): + self.menubar = RibbonBar(self) + self.auimgr.AddPane( self.menubar, aui.AuiPaneInfo() .CaptionVisible(False) .Top() .PinButton( True ).Dock().Resizable().FloatingSize( wx.DefaultSize ).Layer(5) ) + + + def load_menu(self, data): + self.menubar.load(data, {}) + + def init_canvas(self): + self.canvasnbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.canvasnb = CanvasNoteBook(self.canvasnbwrap) + + sizer.Add( self.canvasnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.canvasnbwrap.SetSizer( sizer ) + self.canvasnbwrap.Layout() + self.auimgr.AddPane( self.canvasnbwrap, aui.AuiPaneInfo() .Center() .CaptionVisible( False ).PinButton( True ).Dock() + .PaneBorder( False ).Resizable().FloatingSize( wx.DefaultSize ). BottomDockable( True ).TopDockable( False ) + .LeftDockable( True ).RightDockable( True ) ) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_img) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_img) + + def init_table(self): + self.tablenbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.tablenb = GridNoteBook( self.tablenbwrap) + sizer.Add( self.tablenb, 1, wx.EXPAND |wx.ALL, 0 ) + self.tablenbwrap.SetSizer( sizer ) + self.tablenbwrap.Layout() + + self.auimgr.AddPane( self.tablenbwrap, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Dock().Hide() + .MaximizeButton( True ).Resizable().FloatingSize((800, 600)).BestSize(( 120,120 )). Caption('Table') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_table) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_table) + + def init_tool(self): + sizer = wx.BoxSizer(wx.VERTICAL) + self.toolbar = ToolBar(self, False) + self.toolbar.Fit() + + self.auimgr.AddPane(self.toolbar, aui.AuiPaneInfo() .Top() .PinButton( True ).PaneBorder( False ) + .CaptionVisible( False ).Dock().FloatingSize( wx.DefaultSize ).MinSize(wx.Size( -1,34 )).DockFixed( True ) + . BottomDockable( False ).TopDockable( False ).Layer( 10 ) ) + + def add_task(self, task): + self.task_manager.add(task.title, task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def remove_task(self, task): + self.task_manager.remove(obj=task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def init_text(self): return + #self.mdframe = MDNoteFrame(self, 'Sci Document') + #self.txtframe = TextNoteFrame(self, 'Sci Text') + + def on_pan_close(self, event): + if event.GetPane().window in [self.toolbar, self.widgets]: + event.Veto() + if hasattr(event.GetPane().window, 'close'): + event.GetPane().window.close() + + def on_new_img(self, event): + self.add_img(self.canvasnb.canvas().image) + self.add_img_win(self.canvasnb.canvas()) + + def on_active_img(self, event): + self.active_img(self.canvasnb.canvas().image.name) + #self.add_img_win(self.canvasnb.canvas()) + + def on_close_img(self, event): + canvas = event.GetEventObject().GetPage(event.GetSelection()) + #self.remove_img_win(canvas) + App.close_img(self, canvas.image.title) + + def on_new_tab(self, event): + self.add_tab(event.GetEventObject().grid.table) + self.add_tab_win(event.GetEventObject().grid) + + def on_active_table(self, event): + self.active_table(self.tablenb.grid().table.title) + + def on_close_table(self, event): + grid = event.GetEventObject().GetPage(event.GetSelection()) + App.close_table(self, grid.table.title) + + def on_new_mesh(self, event): + self.add_mesh(event.GetEventObject().canvas.mesh) + self.add_mesh_win(event.GetEventObject().canvas) + + def on_close_mesh(self, event): + self.remove_mesh(event.GetEventObject().canvas.mesh) + self.remove_mesh_win(event.GetEventObject().canvas) + event.Skip() + + def info(self, value): + wx.CallAfter(self.txt_info.SetLabel, value) + + def set_progress(self, value): + v = max(min(value, 100), 0) + self.pro_bar.SetValue(v) + if value==-1: + self.pro_bar.Hide() + elif not self.pro_bar.IsShown(): + self.pro_bar.Show() + self.stapanel.GetSizer().Layout() + self.pro_bar.Update() + + def on_close(self, event): + self.Destroy() + sys.exit() + + def _show_img(self, img, title=None): + canvas = self.canvasnb.add_canvas() + if not isinstance(img, Image): + img = Image(img, title) + App.show_img(self, img, img.title) + canvas.set_img(img) + + def show_img(self, img, title=None): + wx.CallAfter(self._show_img, img, title) + + def _show_table(self, tab, title): + grid = self.tablenb.add_grid() + if not isinstance(tab, Table): + tab = Table(tab, title) + App.show_table(self, tab, tab.title) + grid.set_data(tab) + info = self.auimgr.GetPane(self.tablenbwrap) + info.Show(True) + self.auimgr.Update() + + def show_table(self, tab, title=None): + wx.CallAfter(self._show_table, tab, title) + + def show_plot(self, title): + fig = PlotFrame(self) + fig.figure.title = title + return fig + + def _show_md(self, cont, title='ImagePy'): + mdframe = MDFrame(self) + mdframe.set_cont(cont) + mdframe.mdpad.title = title + mdframe.Show(True) + + def show_md(self, cont, title='ImagePy'): + wx.CallAfter(self._show_md, cont, title) + + def _show_workflow(self, cont, title='ImagePy'): + pan = WorkFlowPanel(self) + pan.SetValue(cont) + info = aui.AuiPaneInfo(). DestroyOnClose(True). Left(). Caption(title) .PinButton( True ) \ + .Resizable().FloatingSize( wx.DefaultSize ).Dockable(False).Float().Top().Layer( 5 ) + pan.Bind(None, lambda x:self.run_macros(['%s>None'%x])) + self.auimgr.AddPane(pan, info) + self.auimgr.Update() + + def show_workflow(self, cont, title='ImagePy'): + wx.CallAfter(self._show_workflow, cont, title) + + def _show_txt(self, cont, title='ImagePy'): + TextFrame(self, title, cont).Show() + + def show_txt(self, cont, title='ImagePy'): + wx.CallAfter(self._show_txt, cont, title) + + def _show_mesh(self, mesh=None, title=None): + if mesh is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + + elif hasattr(mesh, 'vts'): + canvas = self.get_mesh_win() + if canvas is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + canvas.add_surf(title, mesh) + else: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.set_mesh(mesh) + canvas.GetParent().Show() + canvas.GetParent().Bind(wx.EVT_ACTIVATE, self.on_new_mesh) + canvas.GetParent().Bind(wx.EVT_CLOSE, self.on_close_mesh) + self.add_mesh(canvas.mesh) + self.add_mesh_win(canvas) + + def show_mesh(self, mesh=None, title=None): + wx.CallAfter(self._show_mesh, mesh, title) + + def show_widget(self, panel, title='Widgets'): + obj = self.manager('widget').get(panel.title) + if obj is None: + pan = panel(self) + self.manager('widget').add(obj=pan, name=panel.title) + self.auimgr.AddPane(pan, aui.AuiPaneInfo().Caption(panel.title).Left().Layer( 15 ).PinButton( True ) + .Float().Resizable().FloatingSize( wx.DefaultSize ).Dockable(True)) #.DestroyOnClose()) + else: + info = self.auimgr.GetPane(obj) + info.Show(True) + self.Layout() + self.auimgr.Update() + + def close_img(self, name=None): + names = self.img_names() if name is None else [name] + for name in names: + idx = self.canvasnb.GetPageIndex(self.get_img_win(name)) + self.remove_img(self.get_img_win(name).image) + self.remove_img_win(self.get_img_win(name)) + self.canvasnb.DeletePage(idx) + + def close_table(self, name=None): + names = self.get_tab_name() if name is None else [name] + for name in names: + idx = self.tablenb.GetPageIndex(self.get_tab_win(name)) + self.remove_tab(self.get_tab_win(name).table) + self.remove_tab_win(self.get_tab_win(name)) + self.tablenb.DeletePage(idx) + + def run_macros(self, cmd, callafter=None): + cmds = [i for i in cmd] + def one(cmds, after): + cmd = cmds.pop(0) + title, para = cmd.split('>') + plg = self.app.plugin_manager.get(name=title)() + after = lambda cmds=cmds: one(cmds, one) + if len(cmds)==0: after = callafter + wx.CallAfter(plg.start, self, eval(para), after) + one(cmds, None) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def info(self, cont): + wx.CallAfter(self.txt_info.SetLabel, cont) + + def _alert(self, info, title='ImagePy'): + dialog=wx.MessageDialog(self, info, title, wx.OK) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + + def alert(self, info, title='ImagePy'): + wx.CallAfter(self._alert, info, title) + + def yes_no(self, info, title='ImagePy'): + dialog = wx.MessageDialog(self, info, title, wx.YES_NO | wx.CANCEL) + rst = dialog.ShowModal() + dialog.Destroy() + dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} + return dic[rst] + + def get_path(self, title, filt, io, name=''): + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + dialog = wx.FileDialog(self, title, '', name, filt, dic[io]) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + + def show_para(self, title, para, view, on_handle=None, on_ok=None, + on_cancel=None, on_help=None, preview=False, modal=True): + on_help = lambda x=None:self.show_md(x or 'No Document!', title) + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + dialog.Bind('help', on_help) + return dialog.show() + +if __name__ == '__main__': + import numpy as np + import pandas as pd + + app = wx.App(False) + frame = MiniApp(None) + frame.Show() + frame.show_img([np.zeros((512, 512), dtype=np.uint8)], 'zeros') + frame.show_table(pd.DataFrame(np.arange(100).reshape((10,10))), 'title') + + plgs = ('root', [('file', [('IO', [('Open', OpenFile), ('Save', SaveImage)]), ('Gaussian', Gaussian)])]) + + frame.load_menu(plgs) + + ''' + frame.show_md('abcdefg', 'md') + frame.show_md('ddddddd', 'md') + frame.show_txt('abcdefg', 'txt') + frame.show_txt('ddddddd', 'txt') + ''' + + app.MainLoop() \ No newline at end of file diff --git a/sciwx/app/sciapp_.py b/sciwx/app/sciapp_.py new file mode 100644 index 00000000..11e38219 --- /dev/null +++ b/sciwx/app/sciapp_.py @@ -0,0 +1,379 @@ +import wx, os, sys +import time, threading +sys.path.append('../../') +import wx.lib.agw.aui as aui +from sciwx.widgets import MenuBar, ToolBar, RibbonBar, ParaDialog +from sciwx.canvas import CanvasNoteBook +from sciwx.grid import GridNoteBook +from sciwx.widgets import ProgressBar +from sciwx.mesh import Canvas3DFrame +from sciwx.text import MDFrame, TextFrame +from sciwx.plot import PlotFrame +from sciapp.object import Image, Table +from sciapp import App, Source + +from sciapp.action.plugin.filters import Gaussian +from sciapp.action.plugin.generalio import OpenFile, SaveImage + +class SciApp(wx.Frame, App): + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = 'ImagePy', + size = wx.Size(800,600), pos = wx.DefaultPosition, + style = wx.RESIZE_BORDER|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + App.__init__(self) + self.auimgr = aui.AuiManager() + self.auimgr.SetManagedWindow( self ) + self.SetSizeHints( wx.Size(800, 600) ) + + self.init_menu() + self.init_canvas() + self.init_table() + self.init_status() + + self.Layout() + self.auimgr.Update() + self.Fit() + self.Centre( wx.BOTH ) + + self.Bind(wx.EVT_CLOSE, self.on_close) + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_pan_close) + + def init_status(self): + self.stapanel = stapanel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizersta = wx.BoxSizer( wx.HORIZONTAL ) + self.txt_info = wx.StaticText( stapanel, wx.ID_ANY, "ImagePy v0.2", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.txt_info.Wrap( -1 ) + sizersta.Add( self.txt_info, 1, wx.ALIGN_BOTTOM|wx.BOTTOM|wx.LEFT|wx.RIGHT, 2 ) + #self.pro_bar = wx.Gauge( stapanel, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size( 100,15 ), wx.GA_HORIZONTAL ) + self.pro_bar = ProgressBar(stapanel) + sizersta.Add( self.pro_bar, 0, wx.ALL, 2 ) + stapanel.SetSizer(sizersta) + class OpenDrop(wx.FileDropTarget): + def __init__(self, app): + wx.FileDropTarget.__init__(self) + self.app = app + def OnDropFiles(self, x, y, path): + self.app.run_macros(["Open>{'path':'%s'}"%i.replace('\\', '/') for i in path]) + stapanel.SetDropTarget(OpenDrop(self)) + self.auimgr.AddPane( stapanel, aui.AuiPaneInfo() .Bottom() .CaptionVisible( False ).PinButton( True ) + .PaneBorder( False ).Dock().Resizable().FloatingSize( wx.DefaultSize ).DockFixed( True ) + . MinSize(wx.Size(-1, 20)). MaxSize(wx.Size(-1, 20)).Layer( 10 ) ) + + def init_menu(self): + self.menubar = MenuBar(self) + + def init_menu(self): + self.menubar = RibbonBar(self) + self.auimgr.AddPane( self.menubar, aui.AuiPaneInfo() .CaptionVisible(False) .Top() .PinButton( True ).Dock().Resizable().FloatingSize( wx.DefaultSize ).Layer(5) ) + + + def load_menu(self, data): + self.menubar.load(data, {}) + + def init_canvas(self): + self.canvasnbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.canvasnb = CanvasNoteBook(self.canvasnbwrap) + + sizer.Add( self.canvasnb, 1, wx.EXPAND |wx.ALL, 0 ) + self.canvasnbwrap.SetSizer( sizer ) + self.canvasnbwrap.Layout() + self.auimgr.AddPane( self.canvasnbwrap, aui.AuiPaneInfo() .Center() .CaptionVisible( False ).PinButton( True ).Dock() + .PaneBorder( False ).Resizable().FloatingSize( wx.DefaultSize ). BottomDockable( True ).TopDockable( False ) + .LeftDockable( True ).RightDockable( True ) ) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_img) + self.canvasnb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_img) + + def init_table(self): + self.tablenbwrap = wx.Panel(self) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.tablenb = GridNoteBook( self.tablenbwrap) + sizer.Add( self.tablenb, 1, wx.EXPAND |wx.ALL, 0 ) + self.tablenbwrap.SetSizer( sizer ) + self.tablenbwrap.Layout() + + self.auimgr.AddPane( self.tablenbwrap, aui.AuiPaneInfo() .Bottom() .CaptionVisible( True ).PinButton( True ).Dock().Hide() + .MaximizeButton( True ).Resizable().FloatingSize((800, 600)).BestSize(( 120,120 )). Caption('Table') . + BottomDockable( True ).TopDockable( False ).LeftDockable( True ).RightDockable( True ) ) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_active_table) + self.tablenb.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close_table) + + def init_tool(self): + sizer = wx.BoxSizer(wx.VERTICAL) + self.toolbar = ToolBar(self, False) + self.toolbar.Fit() + + self.auimgr.AddPane(self.toolbar, aui.AuiPaneInfo() .Top() .PinButton( True ).PaneBorder( False ) + .CaptionVisible( False ).Dock().FloatingSize( wx.DefaultSize ).MinSize(wx.Size( -1,34 )).DockFixed( True ) + . BottomDockable( False ).TopDockable( False ).Layer( 10 ) ) + + def add_task(self, task): + self.task_manager.add(task.title, task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def remove_task(self, task): + self.task_manager.remove(obj=task) + tasks = self.task_manager.gets() + tasks = [(p.title, lambda t=p:p.prgs) for n,p,t in tasks] + self.pro_bar.SetValue(tasks) + + def init_text(self): return + #self.mdframe = MDNoteFrame(self, 'Sci Document') + #self.txtframe = TextNoteFrame(self, 'Sci Text') + + def on_pan_close(self, event): + if event.GetPane().window in [self.toolbar, self.widgets]: + event.Veto() + if hasattr(event.GetPane().window, 'close'): + event.GetPane().window.close() + + def on_new_img(self, event): + self.add_img(self.canvasnb.canvas().image) + self.add_img_win(self.canvasnb.canvas()) + + def on_active_img(self, event): + self.active_img(self.canvasnb.canvas().image.name) + #self.add_img_win(self.canvasnb.canvas()) + + def on_close_img(self, event): + canvas = event.GetEventObject().GetPage(event.GetSelection()) + self.remove_img_win(canvas) + self.remove_img(canvas.image) + + def on_new_tab(self, event): + self.add_tab(event.GetEventObject().grid.table) + self.add_tab_win(event.GetEventObject().grid) + + def on_active_table(self, event): + self.active_table(self.tablenb.grid().table.title) + + def on_close_table(self, event): + grid = event.GetEventObject().GetPage(event.GetSelection()) + App.close_table(self, grid.table.title) + + def on_new_mesh(self, event): + self.add_mesh(event.GetEventObject().canvas.mesh) + self.add_mesh_win(event.GetEventObject().canvas) + + def on_close_mesh(self, event): + self.remove_mesh(event.GetEventObject().canvas.mesh) + self.remove_mesh_win(event.GetEventObject().canvas) + event.Skip() + + def info(self, value): + wx.CallAfter(self.txt_info.SetLabel, value) + + def set_progress(self, value): + v = max(min(value, 100), 0) + self.pro_bar.SetValue(v) + if value==-1: + self.pro_bar.Hide() + elif not self.pro_bar.IsShown(): + self.pro_bar.Show() + self.stapanel.GetSizer().Layout() + self.pro_bar.Update() + + def on_close(self, event): + self.Destroy() + sys.exit() + + def _show_img(self, img, title=None): + print(img) + canvas = self.canvasnb.add_canvas() + if not isinstance(img, Image): + img = Image(img, title) + App.show_img(self, img, img.title) + canvas.set_img(img) + + def show_img(self, img, title=None): + wx.CallAfter(self._show_img, img, title) + + def _show_table(self, tab, title): + grid = self.tablenb.add_grid() + if not isinstance(tab, Table): + tab = Table(tab, title) + App.show_table(self, tab, tab.title) + grid.set_data(tab) + info = self.auimgr.GetPane(self.tablenbwrap) + info.Show(True) + self.auimgr.Update() + + def show_table(self, tab, title=None): + wx.CallAfter(self._show_table, tab, title) + + def show_plot(self, title): + fig = PlotFrame(self) + fig.figure.title = title + return fig + + def _show_md(self, cont, title='ImagePy'): + mdframe = MDFrame(self) + mdframe.set_cont(cont) + mdframe.mdpad.title = title + mdframe.Show(True) + + def show_md(self, cont, title='ImagePy'): + wx.CallAfter(self._show_md, cont, title) + + def _show_workflow(self, cont, title='ImagePy'): + pan = WorkFlowPanel(self) + pan.SetValue(cont) + info = aui.AuiPaneInfo(). DestroyOnClose(True). Left(). Caption(title) .PinButton( True ) \ + .Resizable().FloatingSize( wx.DefaultSize ).Dockable(False).Float().Top().Layer( 5 ) + pan.Bind(None, lambda x:self.run_macros(['%s>None'%x])) + self.auimgr.AddPane(pan, info) + self.auimgr.Update() + + def show_workflow(self, cont, title='ImagePy'): + wx.CallAfter(self._show_workflow, cont, title) + + def _show_txt(self, cont, title='ImagePy'): + TextFrame(self, title, cont).Show() + + def show_txt(self, cont, title='ImagePy'): + wx.CallAfter(self._show_txt, cont, title) + + def _show_mesh(self, mesh=None, title=None): + if mesh is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + + elif hasattr(mesh, 'vts'): + canvas = self.get_mesh_win() + if canvas is None: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.mesh.name = 'Surface' + canvas.add_surf(title, mesh) + else: + cframe = Canvas3DFrame(self) + canvas = cframe.canvas + canvas.set_mesh(mesh) + canvas.GetParent().Show() + canvas.GetParent().Bind(wx.EVT_ACTIVATE, self.on_new_mesh) + canvas.GetParent().Bind(wx.EVT_CLOSE, self.on_close_mesh) + self.add_mesh(canvas.mesh) + self.add_mesh_win(canvas) + + def show_mesh(self, mesh=None, title=None): + wx.CallAfter(self._show_mesh, mesh, title) + + def show_widget(self, panel, title='Widgets'): + obj = self.manager('widget').get(panel.title) + if obj is None: + pan = panel(self) + self.manager('widget').add(obj=pan, name=panel.title) + self.auimgr.AddPane(pan, aui.AuiPaneInfo().Caption(panel.title).Left().Layer( 15 ).PinButton( True ) + .Float().Resizable().FloatingSize( wx.DefaultSize ).Dockable(True)) #.DestroyOnClose()) + else: + info = self.auimgr.GetPane(obj) + info.Show(True) + self.Layout() + self.auimgr.Update() + + def close_img(self, name=None): + names = self.img_names() if name is None else [name] + for name in names: + idx = self.canvasnb.GetPageIndex(self.get_img_win(name)) + self.remove_img(self.get_img_win(name).image) + self.remove_img_win(self.get_img_win(name)) + self.canvasnb.DeletePage(idx) + + def close_table(self, name=None): + names = self.get_tab_name() if name is None else [name] + for name in names: + idx = self.tablenb.GetPageIndex(self.get_tab_win(name)) + self.remove_tab(self.get_tab_win(name).table) + self.remove_tab_win(self.get_tab_win(name)) + self.tablenb.DeletePage(idx) + + def run_macros(self, cmd, callafter=None): + cmds = [i for i in cmd] + def one(cmds, after): + cmd = cmds.pop(0) + title, para = cmd.split('>') + plg = self.app.plugin_manager.get(name=title)() + after = lambda cmds=cmds: one(cmds, one) + if len(cmds)==0: after = callafter + wx.CallAfter(plg.start, self, eval(para), after) + one(cmds, None) + + def show(self, tag, cont, title): + tag = tag or 'img' + if tag=='img': + self.show_img([cont], title) + elif tag=='imgs': + self.show_img(cont, title) + elif tag=='tab': + self.show_table(cont, title) + elif tag=='mc': + self.run_macros(cont) + elif tag=='md': + self.show_md(cont, title) + elif tag=='wf': + self.show_workflow(cont, title) + else: self.alert('no view for %s!'%tag) + + def info(self, cont): + wx.CallAfter(self.txt_info.SetLabel, cont) + + def _alert(self, info, title='ImagePy'): + dialog=wx.MessageDialog(self, info, title, wx.OK) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + + def alert(self, info, title='ImagePy'): + wx.CallAfter(self._alert, info, title) + + def yes_no(self, info, title='ImagePy'): + dialog = wx.MessageDialog(self, info, title, wx.YES_NO | wx.CANCEL) + rst = dialog.ShowModal() + dialog.Destroy() + dic = {wx.ID_YES:'yes', wx.ID_NO:'no', wx.ID_CANCEL:'cancel'} + return dic[rst] + + def get_path(self, title, filt, io, name=''): + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + dialog = wx.FileDialog(self, title, '', name, filt, dic[io]) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + + def show_para(self, title, para, view, on_handle=None, on_ok=None, + on_cancel=None, on_help=None, preview=False, modal=True): + on_help = lambda x=None:self.show_md(x or 'No Document!', title) + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + dialog.Bind('help', on_help) + return dialog.show() + +if __name__ == '__main__': + import numpy as np + import pandas as pd + + app = wx.App(False) + frame = SciApp(None) + frame.Show() + frame.show_img([np.zeros((512, 512), dtype=np.uint8)], 'zeros') + frame.show_table(pd.DataFrame(np.arange(100).reshape((10,10))), 'title') + + plgs = ('root', [('file', [('IO', [('Open', OpenFile), ('Save', SaveImage)]), ('Gaussian', Gaussian)])]) + + frame.load_menu(plgs) + + ''' + frame.show_md('abcdefg', 'md') + frame.show_md('ddddddd', 'md') + frame.show_txt('abcdefg', 'txt') + frame.show_txt('ddddddd', 'txt') + ''' + + app.MainLoop() \ No newline at end of file diff --git a/sciwx/canvas/__init__.py b/sciwx/canvas/__init__.py new file mode 100644 index 00000000..800fe320 --- /dev/null +++ b/sciwx/canvas/__init__.py @@ -0,0 +1,4 @@ +#from .canvas import Canvas +from .mcanvas import MCanvas, ICanvas, VCanvas, Canvas +from .widget import CanvasFrame, CanvasNoteBook, CanvasNoteFrame +from .vwidget import VectorFrame, VectorNoteBook, VectorNoteFrame \ No newline at end of file diff --git a/sciwx/canvas/canvas.py b/sciwx/canvas/canvas.py new file mode 100644 index 00000000..787683cf --- /dev/null +++ b/sciwx/canvas/canvas.py @@ -0,0 +1,357 @@ +import wx, numpy as np +from sciapp.util.imgutil import mix_img, cross, multiply, merge, lay, mat, like +from .mark import drawmark +from sciapp.object import Image, Shape, mark2shp, Layer, json2shp +from sciapp.action import ImageTool, ShapeTool +from time import time + +class Canvas (wx.Panel): + scales = [0.03125, 0.0625, 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 8, 10, 15, 20, 30, 50] + + def __init__(self, parent, autofit=False, within=False, ingrade=False, up=False): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, + pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TAB_TRAVERSAL ) + + self.winbox = None + self.within = within + self.conbox = [0,0,1,1] + self.oribox = [0,0,1,1] + + self.outbak = None + self.outimg = None + self.outrgb = None + self.outbmp = None + self.outint = None + self.buffer = None + + self.first = True + + self.images = [] + self.marks = {} + self.tool = None + + self.scaidx = 6 + self.autofit = autofit + self.ingrade = ingrade + self.up = up + self.scrbox = wx.DisplaySize() + self.bindEvents() + + def get_obj_tol(self): return self, Tool.default + + def bindEvents(self): + for event, handler in [ \ + (wx.EVT_SIZE, self.on_size), + (wx.EVT_MOUSE_EVENTS, self.on_mouse), + (wx.EVT_IDLE, self.on_idle), + (wx.EVT_PAINT, self.on_paint)]: + self.Bind(event, handler) + + def on_mouse(self, me): + px, py = me.GetX(), me.GetY() + x, y = self.to_data_coor(px, py) + obj, tol = self.get_obj_tol() + btn, tool = me.GetButton(), self.tool or tol + ld, rd, md = me.LeftIsDown(), me.RightIsDown(), me.MiddleIsDown() + sta = [me.AltDown(), me.ControlDown(), me.ShiftDown()] + others = {'alt':sta[0], 'ctrl':sta[1], + 'shift':sta[2], 'px':px, 'py':py, 'canvas':self} + if me.Moving() and not (ld or md or rd): + for i in (ImageTool, ShapeTool): + if isinstance(tool, i): i.mouse_move(tool, obj, x, y, btn, **others) + if me.ButtonDown(): + self.SetFocus() + tool.mouse_down(obj, x, y, btn, **others) + if me.ButtonUp(): + tool.mouse_up(obj, x, y, btn, **others) + if me.Moving(): + tool.mouse_move(obj, x, y, None, **others) + btn = [ld, md, rd,True] + btn = (btn.index(True) +1) %4 + wheel = np.sign(me.GetWheelRotation()) + if me.Dragging(): + tool.mouse_move(obj, x, y, btn, **others) + if wheel!=0: + tool.mouse_wheel(obj, x, y, wheel, **others) + ckey = {'arrow':1,'cross':5,'hand':6} + cursor = ckey[tool.cursor] if tool.cursor in ckey else 1 + self.SetCursor(wx.Cursor(cursor)) + + def initBuffer(self): + box = self.GetClientSize() + self.buffer = wx.Bitmap(*box) + self.winbox = [0, 0, *box] + lay(self.winbox, self.conbox) + + def fit(self): + self.update_box() + oriw = self.oribox[2]-self.oribox[0] + orih = self.oribox[3]-self.oribox[1] + if not self.autofit: a,b,c,d = self.winbox + else: + (a,b),(c,d) = (0,0), self.scrbox + c, d = c*0.9, d*0.9 + if not self.ingrade: + for i in self.scales[6::-1]: + if oriw*i1: self.update_box() + + if None in [self.winbox, self.conbox]: return + if self.within :lay(self.winbox, self.conbox) + + if self.first and self.conbox[2] - self.conbox[0]>1: + self.first = False + return self.fit() + + counter[0] += 1 + start = time() + # lay(self.winbox, self.conbox) + dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) + #dc.SetBackground(wx.Brush((255,255,255))) + dc.Clear() + for i in self.images: + if i.img is None: continue + self.draw_image(dc, i, i.back, 0) + + for i in self.marks.values(): + if i is None: continue + if callable(i): + i(dc, self.to_panel_coor, k=self.scale, cur=0, + winbox=self.winbox, oribox=self.oribox, conbox=self.conbox) + else: + drawmark(dc, self.to_panel_coor, i, k=self.scale, cur=0, + winbox=self.winbox, oribox=self.oribox, conbox=self.conbox) + dc.UnMask() + + counter[1] += time()-start + if counter[0] == 50: + print('frame rate:',int(50/max(0.001,counter[1]))) + counter[0] = counter[1] = 0 + + def set_tool(self, tool): self.tool = tool + + @property + def scale(self): + conw = self.conbox[2]-self.conbox[0] + oriw = self.oribox[2]-self.oribox[0] + conh = self.conbox[3]-self.conbox[1] + orih = self.oribox[3]-self.oribox[1] + l1, l2 = conw**2+conh**2, oriw**2+orih**2 + return l1**0.5 / l2**0.5 + + def move(self, dx, dy, coord='win'): + if coord=='data': + dx,dy = dx*self.scale, dy*self.scale + arr = np.array(self.conbox) + arr = arr.reshape((2,2))+(dx, dy) + self.conbox = arr.ravel().tolist() + self.update() + + def on_size(self, event): + if max(self.GetClientSize())>20: # and self.images[0].img is not None: + self.initBuffer() + return self.update() + # if len(self.images)+len(self.marks)==0: return + # if self.conbox[2] - self.conbox[0] > 1: self.update() + + def on_idle(self, event): + need = sum([i.dirty for i in self.images]) + shapes = [i for i in self.marks.values() if isinstance(i, Shape)] + need += sum([i.dirty for i in shapes]) + if need==0: return + else: + for i in self.images: i.dirty = False + for i in shapes: i.dirty = False + return self.update() + + def on_paint(self, event): + if self.buffer is None: return + wx.BufferedPaintDC(self, self.buffer) + + def center(self, x, y, coord='win'): + if coord=='data': + x,y = self.to_panel_coor(x, y) + dx = (self.winbox[2]-self.winbox[0])/2 - x + dy = (self.winbox[3]-self.winbox[1])/2 - y + for i,j in zip((0,1,2,3),(dx,dy,dx,dy)): + self.conbox[i] += j + lay(self.winbox, self.conbox) + + def zoom(self, k, x, y, coord='win'): + if coord=='data': + x,y = self.to_panel_coor(x, y) + if self.up: y = (self.winbox[3]-self.winbox[1]) - y + box = np.array(self.conbox).reshape((2,2)) + box = (box - (x,y)) / self.scale * k + (x, y) + self.conbox = box.ravel().tolist() + if not self.autofit: return + a,b,c,d = self.conbox + if c-awin[2] or cell[2]win[3] or cell[3]sap: + idx = np.linspace(0, len(i), min(len(i), sap), False, dtype=np.uint16) + i = i[idx] + x, y = i.T[:2] + xy = np.vstack(f(x, y)).T + lst.append(np.hstack((xy-r, np.ones((len(x),2))*(r*2)))) + plst.append(xy) + isline = pts.lstyle and '-' in pts.lstyle + ispoint = pts.lstyle and 'o' in pts.lstyle + if pts.dtype in {'polygon', 'polygons'}: + dc.DrawPolygonList(plst) + + if isline or pts.dtype == 'lines': + for line in plst: + dc.DrawLines(line) + + if ispoint: + pen.SetWidth(1) + brush.SetStyle(100) + brush.SetColour(pen.GetColour()) + dc.SetPen(pen) + dc.SetBrush(brush) + for i in lst: dc.DrawEllipseList(i.round()) + pen.SetWidth(pts.lw or width) + brush.SetStyle((106,100)[pts.fill] if pts.fill != None else style) + brush.SetColour(pts.fcolor or fcolor) + dc.SetPen(pen) + dc.SetBrush(brush) + + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +def draw_circle(pts, dc, f, **key): + pen, brush = dc.GetPen(), dc.GetBrush() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + + if not pts.color is None: + pen.SetColour(pts.color) + if not pts.fcolor is None: + brush.SetColour(pts.fcolor) + if not pts.lw is None: + pen.SetWidth(pts.lw) + if not pts.fill is None: + brush.SetStyle((106,100)[pts.fill]) + + dc.SetPen(pen) + dc.SetBrush(brush) + + if pts.dtype == 'circle': + x, y ,r = pts.body + x, y = f(x, y) + dc.DrawCircle(x, y, int(r*key['k']+0.5)) + if pts.dtype == 'circles': + lst = [] + x, y, r = pts.body.T + x, y = f(x, y) + r = (r * key['k'] + 0.5).astype(np.int32) + lst = np.vstack([x-r, y-r, r*2, r*2]).T + dc.DrawEllipseList(lst) + + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +def make_ellipse(l1, l2, ang): + m = np.array([[l1*cos(-ang),-l2*sin(-ang)], + [l1*sin(-ang),l2*cos(-ang)]]) + a = np.linspace(0, np.pi*2, 36) + xys = np.array((np.cos(a), np.sin(a))) + return np.dot(m, xys).T + +def draw_ellipse(pts, dc, f, **key): + pen, brush = dc.GetPen(), dc.GetBrush() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + + if not pts.color is None: + pen.SetColour(pts.color) + if not pts.fcolor is None: + brush.SetColour(pts.fcolor) + if not pts.lw is None: + pen.SetWidth(pts.lw) + if not pts.fill is None: + brush.SetStyle((106,100)[pts.fill]) + + dc.SetPen(pen) + dc.SetBrush(brush) + + if pts.dtype == 'ellipse': + x, y ,l1, l2, a = pts.body + elp = make_ellipse(l1,l2,a) + (x,y) + elp = np.vstack(f(*elp.T[:2])).T + dc.DrawPolygon(elp) + if pts.dtype == 'ellipses': + lst = [] + for x, y, l1, l2, a in pts.body: + elp = make_ellipse(l1,l2,a) + (x,y) + lst.append(np.vstack(f(*elp.T[:2])).T) + dc.DrawPolygonList(lst) + + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +def draw_rectangle(pts, dc, f, **key): + pen, brush = dc.GetPen(), dc.GetBrush() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + + if not pts.color is None: + pen.SetColour(pts.color) + if not pts.fcolor is None: + brush.SetColour(pts.fcolor) + if not pts.lw is None: + pen.SetWidth(pts.lw) + if not pts.fill is None: + brush.SetStyle((106,100)[pts.fill]) + + dc.SetPen(pen) + dc.SetBrush(brush) + + if pts.dtype == 'rectangle': + x, y, w, h = pts.body + w, h = f(x+w, y+h) + x, y = f(x, y) + dc.DrawRectangle(x.round(), (y).round(), + (w-x).round(), (h-y).round()) + if pts.dtype == 'rectangles': + x, y, w, h = pts.body.T + w, h = f(x+w, y+h) + x, y = f(x, y) + lst = np.vstack((x,y,w-x,h-y)).T + dc.DrawRectangleList(lst.round()) + + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +def draw_text(pts, dc, f, **key): + pen, brush, font = dc.GetPen(), dc.GetBrush(), dc.GetFont() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + size = font.GetPointSize() + tcolor = dc.GetTextForeground() + bcolor = dc.GetTextBackground() + dc.SetTextForeground(color) + dc.SetTextBackground(fcolor) + if not pts.color is None: + pen.SetColour(pts.color) + dc.SetTextForeground(pts.color) + brush.SetColour(pen.GetColour()) + brush.SetStyle(100) + if not pts.fill is None: + dc.SetBackgroundMode((106, 100)[pts.fill]) + if not pts.fcolor is None: + dc.SetTextBackground(pts.fcolor) + if not pts.lw is None: + font.SetPointSize(pts.lw) + + dc.SetPen(pen) + dc.SetBrush(brush) + dc.SetFont(font) + + if pts.dtype == 'text': + (x, y), text = pts.body, pts.txt + ox, oy = pts.offset + x, y = f(x, y) + dc.DrawText(text, x+1+ox, y+1+oy) + if not pts.lstyle is None: + dc.DrawEllipse(x+ox-2,y+oy-2,4,4) + if pts.dtype == 'texts': + tlst, clst, elst = [], [], [] + x, y = pts.body.T + ox, oy = pts.offset + tlst = pts.txt + + x, y = f(x, y) + x += ox; y += oy; + r = x * 0 + 4 + dc.DrawTextList(tlst, np.vstack((x+1,y+1)).T) + if pts.fill: + dc.DrawEllipseList(np.vstack((x,y,r,r)).T) + + font.SetPointSize(size) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + dc.SetFont(font) + dc.SetTextForeground(tcolor) + dc.SetTextBackground(bcolor) + +draw_dic = {'points':plot, 'point':plot, 'line':plot, + 'polygon':plot, 'lines':plot, 'polygons':plot, + 'circle':draw_circle, 'circles':draw_circle, + 'ellipse':draw_ellipse, 'ellipses':draw_ellipse, + 'rectangle':draw_rectangle, 'rectangles':draw_rectangle, + 'text':draw_text, 'texts':draw_text} + +def draw(obj, dc, f, **key): + if len(obj.body)==0: return + draw_dic[obj.dtype](obj, dc, f, **key) + +def draw_layer(pts, dc, f, **key): + pen, brush = dc.GetPen(), dc.GetBrush() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + tcolor = dc.GetTextForeground() + if pts.color: + pen.SetColour(pts.color) + if pts.tcolor: + dc.SetTextForeground(pts.tcolor) + if pts.fcolor: + brush.SetColour(pts.fcolor) + if pts.lw != None: + pen.SetWidth(pts.lw) + if not pts.fill is None: + brush.SetStyle((106,100)[pts.fill]) + + dc.SetPen(pen) + dc.SetBrush(brush) + + for i in pts.body:draw(i, dc, f, **key) + + dc.SetTextForeground(tcolor) + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +draw_dic['layer'] = draw_layer + +def draw_layers(pts, dc, f, **key): + pen, brush = dc.GetPen(), dc.GetBrush() + width, color = pen.GetWidth(), pen.GetColour() + fcolor, style = brush.GetColour(), brush.GetStyle() + + if pts.color: + pen.SetColour(pts.color) + if pts.fcolor: + brush.SetColour(pts.fcolor) + if pts.lw != None: + pen.SetWidth(pts.lw) + if pts.fill: + brush.SetStyle((106,100)[pts.fill]) + + dc.SetPen(pen) + dc.SetBrush(brush) + print(pts.body.keys(), key['cur']) + if key['cur'] in pts.body: + draw(pts.body[key['cur']], dc, f, **key) + + pen.SetWidth(width) + pen.SetColour(color) + brush.SetColour(fcolor) + brush.SetStyle(style) + dc.SetPen(pen) + dc.SetBrush(brush) + +draw_dic['layers'] = draw_layers + +def drawmark(dc, f, body, **key): + default_style = body.default + pen, brush, font = dc.GetPen(), dc.GetBrush(), dc.GetFont() + pen.SetColour(default_style['color']) + brush.SetColour(default_style['fcolor']) + brush.SetStyle((106,100)[default_style['fill']]) + pen.SetWidth(default_style['lw']) + dc.SetTextForeground(default_style['tcolor']) + font.SetPointSize(default_style['size']) + dc.SetPen(pen); dc.SetBrush(brush); dc.SetFont(font); + draw(body, dc, f, **key) + +if __name__ == '__main__': + pass + # print(make_ellipse(0,0,2,1,0)) \ No newline at end of file diff --git a/sciwx/canvas/mcanvas.py b/sciwx/canvas/mcanvas.py new file mode 100644 index 00000000..93662997 --- /dev/null +++ b/sciwx/canvas/mcanvas.py @@ -0,0 +1,280 @@ +import wx, numpy as np +from numpy import ndarray +import wx.lib.agw.aui as aui +from .canvas import Canvas +from sciapp.object.image import Image +from sciapp.action import Tool, ImageTool, ShapeTool + +class ICanvas(Canvas): + def __init__(self, parent, autofit=False, within=False, ingrade=False, up=False): + Canvas.__init__(self, parent, autofit, within, ingrade, up) + self.images.append(Image()) + #self.images[0].back = Image() + self.Bind(wx.EVT_IDLE, self.on_idle) + + def get_obj_tol(self): + return self.image, ImageTool.default + + def set_img(self, img, b=False): + isarr = isinstance(img, ndarray) + if b and not isarr: self.images[0].back = img + if not b and not isarr: self.images[0] = img + if b and isarr: self.images[0].back = Image([img]) + if not b and isarr: self.images[0].img = img + if not b: self.images[0].reset() + if b and self.images[0].back: + self.images[0].back.reset() + self.update_box() + self.update() + + def set_log(self, log, b=False): + if b: self.back.log = log + else: self.image.log = log + + def set_rg(self, rg, b=False): + if b: self.back.rg = rg + else: self.image.rg = rg + + def set_lut(self, lut, b=False): + if b: self.back.lut = lut + else: self.image.lut = lut + + def set_cn(self, cn, b=False): + if b: self.back.cn = cn + else: self.image.cn = cn + + def set_mode(self, mode): + self.image.mode = mode + + def set_tool(self, tool): + self.tool = tool + + @property + def image(self): return self.images[0] + + @property + def name(self): return self.image.title + + @property + def back(self): return self.images[0].back + + def draw_ruler(self, dc, f, **key): + dc.SetPen(wx.Pen((255,255,255), width=2, style=wx.SOLID)) + conbox, winbox, oribox = key['conbox'], key['winbox'], key['oribox'] + x1 = max(conbox[0], winbox[0])+5 + x2 = min(conbox[2], winbox[2])-5 + pixs = (x2-x1+10)*(oribox[2]-oribox[0])/10.0/(conbox[2]-conbox[0]) + h = min(conbox[3], winbox[3])-5 + dc.DrawLineList([(x1,h,x2,h)]) + dc.DrawLineList([(i,h,i,h-5) for i in np.linspace(x1, x2, 11)]) + dc.SetTextForeground((255,255,255)) + k, unit = self.image.unit + text = 'Unit = %.1f %s'%(k*pixs, unit) + dw, dh = dc.GetTextExtent(text) + dc.DrawText(text, (x2-dw, h-10-dh)) + + def on_idle(self, event): + if self.image.unit == (1, 'pix'): + if 'unit' in self.marks: del self.marks['unit'] + else: self.marks['unit'] = self.draw_ruler + if self.image.roi is None: + if 'roi' in self.marks: del self.marks['roi'] + else: self.marks['roi'] = self.image.roi + if self.image.mark is None: + if 'mark' in self.marks: del self.marks['mark'] + elif self.image.mark.dtype=='layers': + if self.image.cur in self.image.mark.body: + self.marks['mark'] = self.image.mark.body[self.image.cur] + elif 'mark' in self.marks: del self.marks['mark'] + else: self.marks['mark'] = self.image.mark + self.tool = self.image.tool + Canvas.on_idle(self, event) + +class VCanvas(Canvas): + def __init__(self, parent, autofit=False, within=False, ingrade=True, up=True): + Canvas.__init__(self, parent, autofit, within, ingrade, up) + + def get_obj_tol(self): + return self.shape, ShapeTool.default + + def set_shp(self, shp): + self.marks['shape'] = shp + self.update() + + def set_tool(self, tool): self.tool = tool + + @property + def shape(self): + if not 'shape' in self.marks: return None + return self.marks['shape'] + +class SCanvas(wx.Panel): + def __init__(self, parent, autofit=False): + wx.Panel.__init__(self, parent) + self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) + self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) ) + + sizer = wx.BoxSizer( wx.VERTICAL ) + self.lab_info = wx.StaticText( self, wx.ID_ANY, + 'information', wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_info.Wrap( -1 ) + # self.lab_info.Hide() + sizer.Add( self.lab_info, 0, wx.ALL, 0 ) + + self.canvas = VCanvas(self, autofit = autofit) + sizer.Add( self.canvas, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.set_shp = self.canvas.set_shp + + self.Bind(wx.EVT_IDLE, self.on_idle) + + def on_idle(self, event): + if self.shape is None: return + if self.shape.dirty: self.update() + self.shape.dirty = False + + def update(self): + if self.shape is None: return + self.canvas.update() + if self.lab_info.GetLabel()!=self.shape.info: + self.lab_info.SetLabel(self.shape.info) + + def Fit(self): + wx.Panel.Fit(self) + self.GetParent().Fit() + + @property + def shape(self): return self.canvas.shape + + @property + def name(self): return self.canvas.shape.name + + +class MCanvas(wx.Panel): + def __init__(self, parent=None, autofit=False): + wx.Panel.__init__ ( self, parent) + + self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) + self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) ) + + sizer = wx.BoxSizer( wx.VERTICAL ) + self.lab_info = wx.StaticText( self, wx.ID_ANY, + 'information', wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_info.Wrap( -1 ) + # self.lab_info.Hide() + sizer.Add( self.lab_info, 0, wx.EXPAND, 0 ) + + self.canvas = ICanvas(self, autofit = autofit) + sizer.Add( self.canvas, 1, wx.EXPAND |wx.ALL, 0 ) + + self.sli_chan = wx.Slider( self, wx.ID_ANY, 0, 0, 100, wx.DefaultPosition, wx.DefaultSize, + wx.SL_HORIZONTAL| wx.SL_SELRANGE| wx.SL_TOP) + sizer.Add( self.sli_chan, 0, wx.ALL|wx.EXPAND, 0 ) + self.sli_chan.SetMaxSize( wx.Size( -1,18 ) ) + self.sli_chan.Hide() + + self.sli_page = wx.ScrollBar( self, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.SB_HORIZONTAL) + self.sli_page.SetScrollbar(0,0,0,0, refresh=True) + sizer.Add( self.sli_page, 0, wx.ALL|wx.EXPAND, 0 ) + self.sli_page.Hide() + + self.SetSizer(sizer) + self.Layout() + self.sli_page.Bind(wx.EVT_SCROLL, self.on_scroll) + self.sli_chan.Bind(wx.EVT_SCROLL, self.on_scroll) + self.Bind(wx.EVT_IDLE, self.on_idle) + #self.Fit() + self.set_rg = self.canvas.set_rg + self.set_lut = self.canvas.set_rg + self.set_log = self.canvas.set_log + self.set_mode = self.canvas.set_mode + self.set_tool = self.canvas.set_tool + + self.chans, self.pages, self.cn, self.cur = -1, -1, -1, -1 + + def set_img(self, img, b=False): + self.canvas.set_img(img, b) + self.canvas.update_box() + self.update() + + def set_cn(self, cn, b=False): + self.canvas.set_cn(cn, b) + self.update() + + @property + def image(self): return self.canvas.image + + @property + def back(self): return self.canvas.back + + @property + def name(self): return self.canvas.image.name + + def set_imgs(self, imgs, b=False): + if b: self.canvas.back.set_imgs(imgs) + else: self.canvas.image.set_imgs(imgs) + self.canvas.update_box() + self.update() + + def Fit(self): + wx.Panel.Fit(self) + self.GetParent().Fit() + + def slider(self): + slices = self.image.slices + channels = self.image.channels + if slices != self.pages or self.image.cur != self.cur: + print('set slices') + if slices==1 and self.sli_page.Shown: + self.sli_page.Hide() + if slices>1 and not self.sli_page.Shown: + self.sli_page.Show() + self.sli_page.SetScrollbar(self.image.cur, 0, slices-1, 0, True) + self.pages, self.cur = slices, self.image.cur + if channels != self.chans or self.cn != self.image.cn: + print('set channels') + if not isinstance(self.image.cn, int) and self.sli_chan.Shown: + self.sli_chan.Hide() + if isinstance(self.image.cn, int) and channels>1: + if not self.sli_chan.Shown: self.sli_chan.Show() + self.sli_chan.SetValue(self.image.cn) + self.sli_chan.SetMax(channels-1) + self.chans, self.cn = channels, self.image.cn + + def update(self): + if self.image.img is None: return + self.slider() + if self.lab_info.GetLabel()!=self.image.info: + self.lab_info.SetLabel(self.image.info) + # self.canvas.update() + self.Layout() + + def on_scroll(self, event): + self.image.cur = self.sli_page.GetThumbPosition() + self.image.dirty = True + if isinstance(self.image.cn, int): + self.image.cn = self.sli_chan.GetValue() + self.canvas.on_idle(event) + + def on_idle(self, event): + if self.image.img is None: return + image, info = self.image, self.lab_info.GetLabel() + imgs = image.slices, image.channels, image.cn, image.cur + selfs = self.pages ,self.chans, self.cn, self.cur + if imgs != selfs or info!=self.image.info: self.update() + + def __del__(self): + print('canvas panel del') + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + app = wx.App() + frame = wx.Frame(None, title='MCanvas') + mc = MCanvas(frame, autofit=False) + mc.set_img(astronaut()) + mc.set_cn(0) + frame.Show() + app.MainLoop() diff --git a/sciwx/canvas/vwidget.py b/sciwx/canvas/vwidget.py new file mode 100644 index 00000000..d6981cd1 --- /dev/null +++ b/sciwx/canvas/vwidget.py @@ -0,0 +1,133 @@ +import wx, wx.lib.agw.aui as aui +from .mcanvas import SCanvas as Canvas +from ..widgets import ToolBar, MenuBar +from sciapp import App + +class VectorFrame(wx.Frame, App): + def __init__(self, parent=None, autofit=False): + App.__init__(self) + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'VectorFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.canvas =Canvas(self, autofit=autofit) + sizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0) + self.SetSizer(sizer) + self.set_shp = self.canvas.set_shp + self.Bind(wx.EVT_IDLE, self.on_idle) + self.Bind(wx.EVT_ACTIVATE, self.on_valid) + self.Bind(wx.EVT_CLOSE, self.on_close) + + def on_idle(self, event): + if self.GetTitle()!=self.canvas.shape.name: + self.SetTitle(self.canvas.shape.name) + + def set_title(self, title): self.SetTitle(title) + + def on_valid(self, event): pass + + def on_close(self, event): + event.Skip() + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def add_menubar(self): + menubar = MenuBar() + self.SetMenuBar(menubar) + return menubar + +class VectorNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).shape.name + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def canvas(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_canvas(self, vcanvas=None): + if vcanvas is None: vcanvas = Canvas(self) + self.AddPage(vcanvas, 'Vector', True, wx.NullBitmap ) + return vcanvas + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + +class VectorNoteFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'VectorNoteFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = VectorNoteBook(self) + self.canvas = self.notebook.canvas + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_canvas = self.notebook.add_canvas + self.Layout() + self.Bind(wx.EVT_CLOSE, self.on_close) + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def add_menubar(self): + menubar = MenuBar() + self.SetMenuBar(menubar) + return menubar + + def on_close(self, event): + while self.notebook.GetPageCount()>0: + self.notebook.DeletePage(0) + event.Skip() + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + + app = wx.App() + cf = CanvasFrame(None, autofit=False) + cf.set_imgs([astronaut(), 255-astronaut()]) + cf.set_cn(0) + cf.Show() + app.MainLoop() + + ''' + app = wx.App() + cnf = CanvasNoteFrame(None) + canvas = cnf.add_img() + canvas.set_img(camera()) + + canvas = cnf.add_img() + canvas.set_img(camera()) + canvas.set_cn(0) + + cnf.Show() + app.MainLoop() + ''' diff --git a/sciwx/canvas/widget.py b/sciwx/canvas/widget.py new file mode 100644 index 00000000..d5dba719 --- /dev/null +++ b/sciwx/canvas/widget.py @@ -0,0 +1,151 @@ +import wx, wx.lib.agw.aui as aui +from .mcanvas import MCanvas +from ..widgets import ToolBar, MenuBar, ParaDialog +from sciapp import App + +class CanvasFrame(wx.Frame, App): + def __init__(self, parent=None, autofit=False): + App.__init__(self) + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'CanvasFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.canvas = MCanvas(self, autofit=autofit) + sizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0) + self.SetSizer(sizer) + + self.set_rg = self.canvas.set_rg + self.set_lut = self.canvas.set_rg + self.set_log = self.canvas.set_log + self.set_mode = self.canvas.set_mode + self.set_tool = self.canvas.set_tool + self.set_imgs = self.canvas.set_imgs + self.set_img = self.canvas.set_img + self.set_cn = self.canvas.set_cn + + self.Bind(wx.EVT_IDLE, self.on_idle) + self.Bind(wx.EVT_ACTIVATE, self.on_valid) + self.Bind(wx.EVT_CLOSE, self.on_close) + + def on_idle(self, event): + if self.GetTitle()!=self.canvas.image.title: + self.SetTitle(self.canvas.image.title) + + def set_title(self, ips): self.SetTitle(ips.title) + + def on_valid(self, event): pass + + def on_close(self, event): + event.Skip() + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def add_menubar(self): + menubar = MenuBar(self) + self.SetMenuBar(menubar) + return menubar + + def show_para(self, title, view, para, on_handle=None, on_ok=None, on_cancel=None, preview=False, modal=True): + dialog = ParaDialog(self, title) + dialog.init_view(view, para, preview, modal=modal, app=self) + dialog.Bind('cancel', on_cancel) + dialog.Bind('parameter', on_handle) + dialog.Bind('commit', on_ok) + return dialog.show() + +class CanvasNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).image.title + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def canvas(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_canvas(self, mcanvas=None): + if mcanvas is None: mcanvas = MCanvas(self) + self.AddPage(mcanvas, 'Image', True, wx.NullBitmap ) + return mcanvas + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + +class CanvasNoteFrame(wx.Frame, App): + def __init__(self, parent, title = 'CanvasNoteFrame'): + App.__init__(self) + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = title, + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = CanvasNoteBook(self) + self.canvas = self.notebook.canvas + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_canvas = self.notebook.add_canvas + self.Layout() + self.Bind(wx.EVT_CLOSE, self.on_close) + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def add_menubar(self): + menubar = MenuBar(self) + self.SetMenuBar(menubar) + return menubar + + def on_close(self, event): + while self.notebook.GetPageCount()>0: + self.notebook.DeletePage(0) + event.Skip() + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + + app = wx.App() + cf = CanvasFrame(None, autofit=False) + cf.set_imgs([astronaut(), 255-astronaut()]) + cf.set_cn(0) + cf.Show() + app.MainLoop() + + ''' + app = wx.App() + cnf = CanvasNoteFrame(None) + canvas = cnf.add_img() + canvas.set_img(camera()) + + canvas = cnf.add_img() + canvas.set_img(camera()) + canvas.set_cn(0) + + cnf.Show() + app.MainLoop() + ''' diff --git a/sciwx/demo/aipen_demo.py b/sciwx/demo/aipen_demo.py new file mode 100644 index 00000000..9cfa9a5d --- /dev/null +++ b/sciwx/demo/aipen_demo.py @@ -0,0 +1,60 @@ +import sys, wx +sys.path.append('../../') +from skimage.draw import line +from sciwx.canvas import CanvasFrame +from sciapp.action import Tool, DefaultTool + +from skimage.morphology import flood_fill, flood +from skimage.draw import line, circle +from skimage.segmentation import felzenszwalb +import numpy as np + +class AIPen(Tool): + title = 'AI Pen' + para = {'win':48, 'ms':30} + view = [(int, 'win', (28, 64), 0, 'window', 'size'), + (float, 'ms', (10, 50), 0, 'stickiness', 'pix')] + + def __init__(self): self.status = None + + def mouse_down(self, ips, x, y, btn, **key): + self.status = 'down' + self.oldp = (y, x) + ips.snapshot() + + def mouse_up(self, ips, x, y, btn, **key): self.status = None + + def mouse_move(self, ips, x, y, btn, **key): + if self.status == None: return + img, color = ips.img, (255,255,0) + rs, cs = line(*[int(round(i)) for i in self.oldp + (y, x)]) + np.clip(rs, 0, img.shape[0]-1, out=rs) + np.clip(cs, 0, img.shape[1]-1, out=cs) + color = (np.mean(color), color)[img.ndim==3] + w = self.para['win'] + + for r,c in zip(rs, cs): + sr = (max(0,r-w), min(img.shape[0], r+w)) + sc = (max(0,c-w), min(img.shape[1], c+w)) + r, c = min(r, w), min(c, w) + clip = img[slice(*sr), slice(*sc)] + if (clip[r,c] - color).sum()==0: continue + lab = felzenszwalb(clip, 1, 0, self.para['ms']) + clip[flood(lab, (r, c), connectivity=2)] = color + + self.oldp = (y, x) + ips.update() + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + app = wx.App() + cf = CanvasFrame(None, autofit=False) + cf.set_imgs([astronaut(), 255-astronaut()]) + cf.set_cn((0,1,2)) + bar = cf.add_toolbar() + bar.add_tool(DefaultTool, 'M') + bar.add_tool(AIPen, 'A') + cf.Show() + app.MainLoop() diff --git a/sciwx/demo/app_demo.py b/sciwx/demo/app_demo.py new file mode 100644 index 00000000..f0720d98 --- /dev/null +++ b/sciwx/demo/app_demo.py @@ -0,0 +1,38 @@ +import wx, sys +sys.path.append('../../') +from sciwx.app import SciApp +from sciwx.canvas import CanvasFrame +from sciapp.action import ImgAction, Tool, DefaultTool +from sciwx.plugins.curve import Curve +from sciwx.plugins.channels import Channels +from sciwx.plugins.histogram import Histogram +from sciwx.plugins.viewport import ViewPort + +from sciwx.plugins.filters import Gaussian, Undo +from sciwx.plugins.pencil import Pencil +from sciwx.plugins.io import Open, Save + +if __name__ == '__main__': + from skimage.data import camera + + app = wx.App(False) + frame = SciApp(None) + + logo = 'C:/Users/54631/Documents/projects/imagepy/imagepy/tools/Standard/magic.gif' + frame.load_menu(('menu',[('File',[('Open', Open), + ('Save', Save)]), + ('Filters', [('Gaussian', Gaussian), + ('Undo', Undo)])])) + frame.load_tool(('tools',[('standard', [('P', Pencil), + ('D', DefaultTool)]), + ('draw', [('X', Pencil), + ('X', Pencil)])]), 'draw') + frame.load_widget(('widgets', [('Histogram', [('Histogram', Histogram), + ('Curve', Curve), + ('Channels', Channels)]), + ('Navigator', [('Viewport', ViewPort)])])) + + frame.show_img(camera()) + frame.show_img(camera()) + frame.Show() + app.MainLoop() diff --git a/sciwx/demo/canvas1_demo.py b/sciwx/demo/canvas1_demo.py new file mode 100644 index 00000000..10b4bc53 --- /dev/null +++ b/sciwx/demo/canvas1_demo.py @@ -0,0 +1,47 @@ +import sys +sys.path.append('../../') +from skimage.data import astronaut, camera +from numpy.fft import fft2, fftshift +from sciwx.canvas import ICanvas as Canvas +from sciapp.object import Image +import wx + +def gray_test(): + frame = wx.Frame(None, title='gray test') + canvas = Canvas(frame, autofit=True) + canvas.set_img(camera()) + frame.Show() + +def rgb_test(): + frame = wx.Frame(None, title='gray test') + canvas = Canvas(frame, autofit=True) + canvas.set_img(astronaut()) + canvas.set_cn((0,1,2)) + frame.Show() + +def rgb_gray_blend(): + frame = wx.Frame(None, title='blend') + canvas = Canvas(frame, autofit=True) + canvas.set_img(astronaut()) + canvas.set_cn((2,-1,-1)) + canvas.set_img(camera(), True) + canvas.set_cn(0, True) + canvas.set_mode(0.5) + frame.Show() + +def complex_test(): + frame = wx.Frame(None, title='fft') + canvas = Canvas(frame, autofit=True) + canvas.set_img(fftshift(fft2(camera()))) + canvas.set_rg((0,31015306)) + canvas.set_log(True) + frame.Show() + +if __name__ == '__main__': + app = wx.App() + gray_test() + rgb_test() + rgb_gray_blend() + complex_test() + app.MainLoop() + diff --git a/sciwx/demo/canvas2_m_demo.py b/sciwx/demo/canvas2_m_demo.py new file mode 100644 index 00000000..4a7dd548 --- /dev/null +++ b/sciwx/demo/canvas2_m_demo.py @@ -0,0 +1,33 @@ +import sys +sys.path.append('../../') +from skimage.data import astronaut, camera +from sciwx.canvas import MCanvas +import wx + +def mcanvas_test(): + frame = wx.Frame(None, title='gray test1') + canvas = MCanvas(frame, autofit=True) + canvas.set_img(astronaut()) + canvas.set_cn((0,1,2)) + frame.Show() + +def channels_test(): + frame = wx.Frame(None, title='gray test2') + canvas = MCanvas(frame, autofit=True) + canvas.set_img(astronaut()) + canvas.set_cn(0) + frame.Show() + +def sequence_test(): + frame = wx.Frame(None, title='gray test3') + canvas = MCanvas(frame, autofit=True) + canvas.set_imgs([astronaut(), 255-astronaut()]) + canvas.set_cn(0) + frame.Show() + +if __name__ == '__main__': + app = wx.App() + mcanvas_test() + sequence_test() + channels_test() + app.MainLoop() diff --git a/sciwx/demo/canvas3_image_obj.py b/sciwx/demo/canvas3_image_obj.py new file mode 100644 index 00000000..3c8cd9c6 --- /dev/null +++ b/sciwx/demo/canvas3_image_obj.py @@ -0,0 +1,32 @@ +import sys +sys.path.append('../../') +from skimage.data import astronaut, camera +from sciapp.object import Image +from sciwx.canvas import ICanvas, MCanvas +import wx + +def image_canvas_test(): + obj = Image() + obj.img = camera() + obj.cn = 0 + + frame = wx.Frame(None, title='gray test') + canvas = ICanvas(frame, autofit=True) + canvas.set_img(obj) + frame.Show() + +def image_mcanvas_test(): + obj = Image() + obj.imgs = [astronaut(), 255-astronaut()] + obj.cn = 0 + + frame = wx.Frame(None, title='gray test') + canvas = MCanvas(frame, autofit=True) + canvas.set_img(obj) + frame.Show() + +if __name__ == '__main__': + app = wx.App() + image_canvas_test() + image_mcanvas_test() + app.MainLoop() diff --git a/sciwx/demo/canvas4_tool.py b/sciwx/demo/canvas4_tool.py new file mode 100644 index 00000000..ef8e171a --- /dev/null +++ b/sciwx/demo/canvas4_tool.py @@ -0,0 +1,33 @@ +import sys +sys.path.append('../../') +from skimage.data import astronaut, camera +from sciwx.canvas import ICanvas +from sciapp.action import Tool +import wx + +class TestTool(Tool): + def __init__(self): Tool.__init__(self) + + def mouse_down(self, image, x, y, btn, **key): + print('x:%d y:%d btn:%d ctrl:%s alt:%s shift:%s'% + (x, y, btn, key['ctrl'], key['alt'], key['shift'])) + + def mouse_up(self, image, x, y, btn, **key): + pass + + def mouse_move(self, image, x, y, btn, **key): + pass + + def mouse_wheel(self, image, x, y, d, **key): + image.img[:] = image.img + d + key['canvas'].update() + + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + canvas = ICanvas(frame, autofit=True) + canvas.set_img(camera()) + canvas.set_tool(TestTool()) + frame.Show() + app.MainLoop() diff --git a/sciwx/demo/canvas5_frame_demo.py b/sciwx/demo/canvas5_frame_demo.py new file mode 100644 index 00000000..5d41b812 --- /dev/null +++ b/sciwx/demo/canvas5_frame_demo.py @@ -0,0 +1,26 @@ +import sys +sys.path.append('../../') + +from skimage.data import astronaut, camera +from sciwx.canvas import CanvasFrame, CanvasNoteFrame +import wx + +def canvas_frame_test(): + cf = CanvasFrame(None, autofit=True) + cf.set_imgs([camera(), 255-camera()]) + cf.Show() + +def canvas_note_test(): + cnf = CanvasNoteFrame(None) + cv1 = cnf.add_canvas() + cv1.set_img(camera()) + cv2 = cnf.add_canvas() + cv2.set_img(astronaut()) + cv2.set_cn((2,1,0)) + cnf.Show() + +if __name__ == '__main__': + app = wx.App() + canvas_frame_test() + canvas_note_test() + app.MainLoop() diff --git a/sciwx/demo/canvas6_frame_toolbar.py b/sciwx/demo/canvas6_frame_toolbar.py new file mode 100644 index 00000000..a6b841f0 --- /dev/null +++ b/sciwx/demo/canvas6_frame_toolbar.py @@ -0,0 +1,46 @@ +import sys, wx +sys.path.append('../../') +from skimage.draw import line +from sciwx.app.canvasapp import CanvasApp +from sciapp.action import Tool, ImageTool + +class Pencil(ImageTool): + title = 'Pencil' + para = {'pc':(255,0,0)} + view = [('color', 'pc','pen', 'color')] + + def __init__(self): + self.status = False + self.oldp = (0,0) + + def mouse_down(self, ips, x, y, btn, **key): + self.status = True + self.oldp = (y, x) + + def mouse_up(self, ips, x, y, btn, **key): + self.status = False + + def mouse_move(self, ips, x, y, btn, **key): + if not self.status:return + se = self.oldp + (y,x) + rs,cs = line(*[int(i) for i in se]) + rs.clip(0, ips.shape[1], out=rs) + cs.clip(0, ips.shape[0], out=cs) + ips.img[rs,cs] = self.para['pc'] + self.oldp = (y, x) + key['canvas'].update() + + def mouse_wheel(self, ips, x, y, d, **key):pass + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + app = wx.App() + cf = CanvasApp(None, autofit=False) + cf.set_img(astronaut()) + bar = cf.add_toolbar() + bar.add_tool('M', ImageTool) + bar.add_tool('P', Pencil) + cf.Show() + app.MainLoop() diff --git a/sciwx/demo/canvas7_frame_menu.py b/sciwx/demo/canvas7_frame_menu.py new file mode 100644 index 00000000..d618e989 --- /dev/null +++ b/sciwx/demo/canvas7_frame_menu.py @@ -0,0 +1,34 @@ +import sys, wx +sys.path.append('../../') +from scipy.ndimage import gaussian_filter +from sciwx.app.canvasapp import CanvasApp +from sciapp.action import ImgAction + +class Gaussian(ImgAction): + title = 'Gaussian' + note = ['auto_snap', 'preview'] + para = {'sigma':2} + view = [(float, 'sigma', (0, 30), 1, 'sigma', 'pix')] + + def run(self, ips, img, snap, para): + gaussian_filter(snap, para['sigma'], output=img) + +class Undo(ImgAction): + title = 'Undo' + def run(self, ips, img, snap, para): + print(ips.img.mean(), ips.snap.mean()) + ips.swap() + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + app = wx.App() + ca = CanvasApp(None, autofit=False) + ca.set_img(camera()) + bar = ca.add_menubar() + bar.load(('menu',[('Filter',[('Gaussian', Gaussian), + ('Unto', Undo)]), + ])) + ca.Show() + app.MainLoop() diff --git a/sciwx/demo/canvas8_frame_menu_tool.py b/sciwx/demo/canvas8_frame_menu_tool.py new file mode 100644 index 00000000..c9d031f9 --- /dev/null +++ b/sciwx/demo/canvas8_frame_menu_tool.py @@ -0,0 +1,62 @@ +import sys, wx +sys.path.append('../../') +from scipy.ndimage import gaussian_filter +from skimage.draw import line +from sciwx.app.canvasapp import CanvasApp +from sciapp.action import ImgAction, ImageTool + +class Gaussian(ImgAction): + title = 'Gaussian' + note = ['auto_snap', 'preview'] + para = {'sigma':2} + view = [(float, 'sigma', (0, 30), 1, 'sigma', 'pix')] + + def run(self, ips, img, snap, para): + gaussian_filter(snap, para['sigma'], output=img) + +class Undo(ImgAction): + title = 'Undo' + def run(self, ips, img, snap, para): ips.swap() + +class Pencil(ImageTool): + title = 'Pencil' + def __init__(self): + self.status = False + self.oldp = (0,0) + + def mouse_down(self, ips, x, y, btn, **key): + self.status = True + self.oldp = (y, x) + ips.snapshot() + + def mouse_up(self, ips, x, y, btn, **key): + self.status = False + + def mouse_move(self, ips, x, y, btn, **key): + if not self.status:return + se = self.oldp + (y,x) + rs,cs = line(*[int(i) for i in se]) + rs.clip(0, ips.shape[1], out=rs) + cs.clip(0, ips.shape[0], out=cs) + ips.img[rs,cs] = 255 + self.oldp = (y, x) + key['canvas'].update() + + def mouse_wheel(self, ips, x, y, d, **key):pass + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + app = wx.App() + cf = CanvasApp(None, autofit=False) + cf.set_imgs([camera(), 255-camera()]) + cf.set_cn(0) + bar = cf.add_menubar() + bar.load(('menu',[('Filter',[('Gaussian', Gaussian), + ('Unto', Undo)]),])) + + bar = cf.add_toolbar() + bar.add_tool('P', Pencil) + cf.Show() + app.MainLoop() diff --git a/sciwx/demo/canvas9_image_roi.py b/sciwx/demo/canvas9_image_roi.py new file mode 100644 index 00000000..f3094295 --- /dev/null +++ b/sciwx/demo/canvas9_image_roi.py @@ -0,0 +1,17 @@ +import sys +sys.path.append('../../') +from skimage.data import astronaut, camera +from sciwx.canvas import ICanvas +from sciapp.action import Tool +from sciapp.object import ROI, Line +import wx + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + canvas = ICanvas(frame, autofit=True) + canvas.set_img(camera()) + roi = ROI([Line([(0,0),(100,100),(300,500)])]) + canvas.image.roi = roi + frame.Show() + app.MainLoop() \ No newline at end of file diff --git a/sciwx/demo/dialog_demo.py b/sciwx/demo/dialog_demo.py new file mode 100644 index 00000000..d241bfdb --- /dev/null +++ b/sciwx/demo/dialog_demo.py @@ -0,0 +1,25 @@ +import sys, wx +sys.path.append('../../') +from sciwx.widgets import ParaDialog + +if __name__ == '__main__': + para = {'name':'yxdragon', 'age':10, 'h':1.72, 'w':70, 'sport':True, 'sys':'Mac', 'lan':['C/C++', 'Python'], 'c':(255,0,0)} + + view = [('lab', 'lab', 'This is a questionnaire'), + (str, 'name', 'name', 'please'), + (int, 'age', (0,150), 0, 'age', 'years old'), + (float, 'h', (0.3, 2.5), 2, 'height', 'm'), + ('slide', 'w', (1, 150), 0, 'weight','kg'), + (bool, 'sport', 'do you like sport'), + (list, 'sys', ['Windows','Mac','Linux'], str, 'favourite', 'system'), + ('chos', 'lan', ['C/C++','Java','Python'], 'lanuage you like(multi)'), + ('color', 'c', 'which', 'you like')] + + app = wx.App() + dic = {'height':'高度', 'm':'米'} + pd = ParaDialog(None, 'Test', dic) + pd.init_view(view, para, preview=True, modal=False) + pd.pack() + pd.ShowModal() + print(para) + app.MainLoop() diff --git a/sciwx/demo/grid_demo.py b/sciwx/demo/grid_demo.py new file mode 100644 index 00000000..4f13c6bc --- /dev/null +++ b/sciwx/demo/grid_demo.py @@ -0,0 +1,70 @@ +import sys, wx +sys.path.append('../../') +import pandas as pd +import numpy as np + +from sciwx.grid import Grid +from sciapp.object import Table +from sciwx.grid import GridFrame, GridNoteBook, GridNoteFrame + +def grid_test(): + df = pd.DataFrame(np.random.randn(100,5)) + frame = wx.Frame(None, title='Grid') + grid = Grid(frame) + grid.set_data(df) + frame.Show() + +def grid_style_test(): + df = pd.DataFrame(np.random.randn(100,5)) + frame = wx.Frame(None, title='Grid') + grid = Grid(frame) + grid.set_data(df) + grid.set_style(1, tc=(255,0,0), round=5) + grid.set_style(2, lc=(255,0,255), ln='both') + frame.Show() + +def grid_frame_test(): + df = pd.DataFrame(np.random.randn(100,5)) + cf = GridFrame(None) + cf.set_data(df) + cf.Show() + +def grid_note_book(): + df = pd.DataFrame(np.random.randn(100,5)) + frame = wx.Frame(None, title='GridNoteBook') + gnb = GridNoteBook(frame) + grid1 = gnb.add_grid() + grid1.set_data(df) + grid2 = gnb.add_grid() + grid2.set_data(df) + frame.Show() + +def grid_note_frame(): + df = pd.DataFrame(np.random.randn(6,4)) + gnf = GridNoteFrame(None) + grid1 = gnf.add_grid() + grid1.set_data(df) + grid2 = gnf.add_grid() + grid2.set_data(df) + gnf.Show() + +def table_obj_test(): + df = pd.DataFrame(np.random.randn(100,5)) + table = Table() + table.data = df + table.name = 'Table Object' + table.set_style(1, tc=(255,0,0), round=5) + table.set_style(2, lc=(255,0,255), ln='both') + cf = GridFrame(None) + cf.set_data(table) + cf.Show() + +if __name__=='__main__': + app = wx.App() + grid_test() + grid_style_test() + grid_frame_test() + grid_note_book() + grid_note_frame() + table_obj_test() + app.MainLoop() diff --git a/sciwx/demo/markdown_demo.py b/sciwx/demo/markdown_demo.py new file mode 100644 index 00000000..501b748b --- /dev/null +++ b/sciwx/demo/markdown_demo.py @@ -0,0 +1,46 @@ +import sys, wx +sys.path.append('../../') + +from sciwx.text import MDPad, MDNoteBook, MDFrame, MDNoteFrame + +mdstr ='''## Markdown Test +### Title +1. paragraph 1 +2. paragraph 2 +''' + +def md_pad_test(): + frame = wx.Frame(None, title='Text Pad') + mdpad = MDPad(frame) + mdpad.set_cont(mdstr) + frame.Show() + +def md_frame_test(): + mdframe = MDFrame(None) + mdframe.set_cont(mdstr) + mdframe.Show() + +def md_note_book(): + frame = wx.Frame(None, title='Text Note Book') + mnb = MDNoteBook(frame) + md1 = mnb.add_page() + md1.set_cont(mdstr) + md2 = mnb.add_page() + md2.set_cont(mdstr) + frame.Show() + +def md_note_frame(): + mnf = MDNoteFrame(None) + md1 = mnf.add_page() + md1.set_cont(mdstr) + md2 = mnf.add_page() + md2.set_cont(mdstr) + mnf.Show() + +if __name__ == '__main__': + app = wx.App() + md_pad_test() + md_frame_test() + md_note_book() + md_note_frame() + app.MainLoop() diff --git a/sciwx/demo/mesh1_demo.py b/sciwx/demo/mesh1_demo.py new file mode 100644 index 00000000..28303164 --- /dev/null +++ b/sciwx/demo/mesh1_demo.py @@ -0,0 +1,57 @@ +import sys, wx +import numpy as np +sys.path.append('../../') + +from sciapp.object import Mesh, Scene +from sciwx.mesh import Canvas3D, MCanvas3D, Canvas3DFrame, Canvas3DNoteBook, Canvas3DNoteFrame +from sciapp.util import meshutil + +# vts, fs, ns, cs = surfutil.build_ball((100,100,100), 50, (1,0,0)) +verts, faces = meshutil.create_ball((0,0,0), 1) +ball = Mesh(verts=verts, faces=faces, colors=verts[:,2], cmap='jet') +ball2 = Mesh(verts=verts, faces=faces, colors=verts[:,2], mode='grid') +ball3 = Mesh(verts=verts, faces=faces, colors=verts[:,2], mode='grid') + +def canvas3d_test(): + frame = wx.Frame(None, title='Canvas3D') + canvas3d = Canvas3D(parent=frame) + canvas3d.SetBackgroundColour((255,0,0)) + canvas3d.add_obj('ball', ball) + frame.Show() + +def mcanvas3d_test(): + frame = wx.Frame(None, title='MCanvas3D') + canvas3d = MCanvas3D(frame) + canvas3d.add_obj('ball', ball) + frame.Show() + +def canvas3d_frame_test(): + cnf = Canvas3DFrame(None) + cnf.add_obj('ball', ball) + cnf.Show() + +def canvas3d_note_book(): + frame = wx.Frame(None, title='Canvas3D NoteBook') + cnb = Canvas3DNoteBook(frame) + canvas1 = cnb.add_canvas() + canvas1.add_obj('ball', ball) + canvas2 = cnb.add_canvas() + canvas2.add_obj('ball2', ball2) + canvas3 = cnb.add_canvas() + canvas3.add_obj('ball3', ball3) + frame.Show() + +def canvas3d_note_frame(): + cnf = Canvas3DNoteFrame(None) + canvas1 = cnf.add_canvas() + canvas1.add_obj('ball1', ball) + canvas2 = cnf.add_canvas() + canvas2.add_obj('ball2', ball2) + canvas2 = cnf.add_canvas() + canvas2.add_obj('ball3', ball3) + cnf.Show() + +if __name__ == '__main__': + app = wx.App() + canvas3d_note_book() + app.MainLoop() diff --git a/sciwx/demo/mesh2_mesh_demo.py b/sciwx/demo/mesh2_mesh_demo.py new file mode 100644 index 00000000..bac13352 --- /dev/null +++ b/sciwx/demo/mesh2_mesh_demo.py @@ -0,0 +1,35 @@ +import sys, wx +sys.path.append('../../') + +from sciwx.mesh import Canvas3D, MCanvas3D +from sciapp.util import meshutil +from sciapp.object import Scene, Mesh +from sciwx.mesh import Canvas3DFrame, Canvas3DNoteBook, Canvas3DNoteFrame + +verts, faces = meshutil.create_sphere(16, 16, 16) +ball = Mesh(verts=verts, faces=faces, colors=(1,0,0), mode='grid') + +def add_with_para(): + cnf = Canvas3DFrame(None) + cnf.add_obj('gridball', ball) + cnf.Show() + +def mesh_obj_test(): + cnf = Canvas3DFrame(None) + scene = cnf.canvas.scene3d + verts, faces = meshutil.create_sphere(16, 16, 16) + redball = Mesh(verts=verts*50+(100,100,100), faces=faces, colors=(1,0,0)) + scene.add_obj('redball', redball) + verts, faces = meshutil.create_sphere(16, 16, 16) + yellowball = Mesh(verts=verts*50+(300,100,100), faces=faces, colors=(1,1,0)) + scene.add_obj('yellowball', yellowball) + verts, faces = meshutil.create_sphere(16, 16, 16) + hideball = Mesh(verts=verts*50+(300,-300,100), faces=faces, colors=(0,1,0), visible=False) + scene.add_obj('hideball', hideball) + cnf.Show() + +if __name__ == '__main__': + app = wx.App() + # add_with_para() + mesh_obj_test() + app.MainLoop() diff --git a/sciwx/demo/mesh3_geoutil.py b/sciwx/demo/mesh3_geoutil.py new file mode 100644 index 00000000..94f7427c --- /dev/null +++ b/sciwx/demo/mesh3_geoutil.py @@ -0,0 +1,171 @@ +import sys, wx +sys.path.append('../../') + +from sciwx.mesh import Canvas3D, MCanvas3D +from sciapp.util import surfutil, meshutil +from sciapp.object import Scene, Mesh, Surface2d, Surface3d, TextSet, Volume3d +from sciwx.mesh import Canvas3DFrame, Canvas3DNoteBook, Canvas3DNoteFrame +import sys, wx +import scipy.ndimage as ndimg +from skimage.data import moon, camera +import numpy as np + +def dem_test(): + cnf = Canvas3DFrame(None) + cnf.add_obj('dem', Surface2d(img=moon(), sample=1, sigma=1, k=0.3, cmap='jet')) + cnf.Show() + +def ball_test(): + cnf = Canvas3DFrame(None) + vts, fs = meshutil.create_ball((100,100,100), 1) + cnf.add_obj('ball', Mesh(vts, fs, colors=(1,0,0))) + cnf.add_obj('line', TextSet(texts=['TEXT'], verts=[(101,100,100)], size=256)) + cnf.Show() + +def random_ball_test(): + cnf = Canvas3DFrame(None) + os = np.random.rand(30).reshape((-1,3)) + rs = np.random.rand(10)/7+0.05 + cs = np.random.rand(10) + vts_b, fs_b, cs_b = meshutil.create_balls(os, rs, cs) + cnf.add_obj('balls', Mesh(verts=vts_b, faces=fs_b, colors=cs_b, cmap='jet')) + cnf.Show() + +def line_test(): + cnf = Canvas3DFrame(None) + vts = np.array([(0,0,0),(1,1,0),(2,1,0),(1,0,0)], dtype=np.float32) + fs = np.array([(0,1,2),(1,2,3)], dtype=np.uint32) + ns = np.ones((4,3), dtype=np.float32) + + n_mer, n_long = 6, 11 + pi = np.pi + dphi = pi / 1000.0 + phi = np.arange(0.0, 2 * pi + 0.5 * dphi, dphi) + mu = phi * n_mer + x = np.cos(mu) * (1 + np.cos(n_long * mu / n_mer) * 0.5) + y = np.sin(mu) * (1 + np.cos(n_long * mu / n_mer) * 0.5) + z = np.sin(n_long * mu / n_mer) * 0.5 + + vts = np.array([x, y, z]).T.astype(np.float32) + + fs = np.arange(len(x), dtype=np.uint32) + fs = np.array([fs[:-1], fs[1:]]).T + + # cs[:] = geoutil.auto_lookup(vts[:,2], geoutil.linear_color('jet'))/255 + cnf.add_obj('ball', Mesh(vts, fs, colors=vts[:,2], mode='grid', cmap='jet')) + cnf.Show() + +def mesh_test(): + cnf = Canvas3DFrame(None) + dphi, dtheta = np.pi/16.0, np.pi/16.0 + [phi,theta] = np.mgrid[0:np.pi+dphi*1.5:dphi,0:2*np.pi+dtheta*1.5:dtheta] + m0 = 4; m1 = 3; m2 = 2; m3 = 3; m4 = 6; m5 = 2; m6 = 6; m7 = 4; + r = np.sin(m0*phi)**m1 + np.cos(m2*phi)**m3 + np.sin(m4*theta)**m5 + np.cos(m6*theta)**m7 + x = r*np.sin(phi)*np.cos(theta) + y = r*np.cos(phi) + z = r*np.sin(phi)*np.sin(theta) + vts, fs = meshutil.create_grid_mesh(x, y, z) + mesh = Mesh(vts, fs.astype(np.uint32), vts[:,2], mode='grid', cmap='jet') + cnf.add_obj('ball', mesh) + cnf.Show() + + +def ball_ring_test(): + cnf = Canvas3DFrame(None) + os = np.random.rand(30).reshape((-1,3)) + rs = np.random.rand(10)/7 + 0.05 + cs = np.random.rand(10) + vts_b, fs_b, cs_b = meshutil.create_balls(os, rs, cs) + cnf.add_obj('balls', Mesh(verts=vts_b, faces=fs_b, colors=cs_b, cmap='jet')) + + vts_l, fs_l = meshutil.create_line(*os.T) + cnf.add_obj('line', Mesh(verts=vts_l, faces=fs_l, colors=cs, cmap='jet', mode='grid')) + # vts_c, fs_c, ns_c, cs_c = geoutil.build_cube((0,0,0), (1,1,1)) + + vts_c, ls_c = meshutil.create_bound((0,0,0), (1,1,1), 3, 3, 3) + cnf.add_obj('box', Mesh(verts=vts_c, faces=ls_c, )) + cnf.Show() + +def balls_mark_rest(): + cnf = Canvas3DFrame(None) + os = np.random.rand(30).reshape((-1,3)) + rs = np.random.rand(10)/7+0.05 + cs = np.random.rand(10) + vts_b, fs_b, cs_b = meshutil.create_balls(os, rs, cs) + cont = ['ID:%s'%i for i in range(10)] + + # vtss, fss, pps, h, color = surfutil.build_marks(cont, os, rs, 0.05, (1,1,1)) + # cnf.add_obj('balls', Mesh(verts=vts_b.astype(np.float32), faces=fs_b.astype(np.uint32), colors=cs_b, cmap='jet')) + cnf.add_obj('line', TextSet(texts=a, verts=b, size=1600, colors=c)) + cnf.Show() + +def surface2d_test(): + cnf = Canvas3DFrame(None) + x, y = np.ogrid[-2:2:20j, -2:2:20j] + z = x * np.exp( - x**2 - y**2) + + vts, fs = meshutil.create_surface2d(z, sample=1, k=10) + dem = Mesh(verts=vts, faces=fs.astype(np.uint32), colors=z.ravel(), cmap='jet') + cnf.add_obj('dem', dem) + cnf.Show() + +def arrow_test(): + cnf = Canvas3DFrame(None) + v1, v2 = np.array([[[0,0,0],[5,5,5]],[[0,15,5],[2,8,3]]], dtype=np.float32) + vts, fs, ns, cs = meshutil.build_arrows(v1, v2, 1, 1, 1, 1, (1,0,0)) + # vts, fs = meshutil.create_arrow(15, 15) + cnf.add_obj('arrow', Mesh(vts, fs, colors=(1,0,0))) + cnf.Show() + +def cube_test(): + cnf = Canvas3DFrame(None) + vts, fs, ls = meshutil.create_cube() + cnf.add_obj('box', Mesh(vts, ls, colors=(1,0,0), mode='grid')) + cnf.Show() + +def cube_surf_test(): + cnf = Canvas3DFrame(None) + lut = np.zeros((256,3), dtype=np.uint8) + lut[:,0] = np.arange(256) + imgs = np.array([camera()[:300,::]]*256) + vts, fs, ns, cs = geoutil.build_img_cube(imgs) + obj = cnf.add_surf('cube', vts, fs, ns, cs) + vts, fs, ns, cs = geoutil.build_img_box(imgs) + cnf.add_surf('box', vts, fs, ns, cs, mode='grid') + cnf.Show() + +def isosurface_test(): + cnf = Canvas3DFrame(None) + cube = np.zeros((100,100,100), dtype=np.float32) + x,y,z = np.random.randint(10,90,900).reshape(3,-1) + cube[x,y,z] = 1000 + surf3d = Surface3d(cube, level=1.5, sigma=3, step=2, colors=(1,0,0)) + cnf.add_obj('volume', surf3d) + cnf.Show() + +def volume_test(): + cnf = Canvas3DFrame(None) + cube = np.zeros((100,100,100), dtype=np.float32) + x,y,z = np.random.randint(10,90,900).reshape(3,-1) + cube[x,y,z] = 1000 + surf3d = Volume3d(cube, level=1.5, step=2, cmap='gray') + cnf.add_obj('volume', surf3d) + cnf.Show() + +if __name__ == '__main__': + app = wx.App() + # balls_mark_rest() + # dem_test() + # ball_test() + # random_ball_test() + # line_test() + # mesh_test() + # ball_ring_test() + # balls_mark_rest() + # surface2d_test() + # arrow_test() # bad + # cube_test() + # cube_surf_test() # bad + # isosurface_test() + volume_test() + app.MainLoop() diff --git a/sciwx/demo/plot_demo.py b/sciwx/demo/plot_demo.py new file mode 100644 index 00000000..71452a2e --- /dev/null +++ b/sciwx/demo/plot_demo.py @@ -0,0 +1,52 @@ +import wx, sys +sys.path.append('../../') + +import numpy as np +from sciwx.plot import PlotCanvas, PlotFrame, PlotNoteBook, PlotNoteFrame + +x = np.linspace(0, 10, 100) +y = np.sin(x) + +def plot_canvas_test(): + frame = wx.Frame(None, title='PlotCanvas') + pcanvas = PlotCanvas(frame) + ax = pcanvas.add_subplot() + ax.plot(x, y) + frame.Show() + +def plot_frame_test(): + pframe = PlotFrame(None) + ax = pframe.add_subplot() + ax.plot(x, y) + pframe.Show() + +def plot_note_book(): + frame = wx.Frame(None) + pnb = PlotNoteBook(frame) + figure1 = pnb.add_figure() + ax = figure1.add_subplot() + ax.plot(x, y) + figure2 = pnb.add_figure() + ax = figure2.add_subplot() + ax.plot(x, y, 'r-.') + frame.Show() + +def plot_note_frame(): + pnf = PlotNoteFrame(None) + figure1 = pnf.add_figure() + ax = figure1.add_subplot() + ax.plot(x, y) + ax.grid() + ax.set_title('abc') + figure2 = pnf.add_figure() + ax = figure2.add_subplot() + ax.plot(x, y) + pnf.Show() + +if __name__ == '__main__': + app = wx.App() + plot_canvas_test() + plot_frame_test() + plot_note_book() + plot_note_frame() + app.MainLoop() diff --git a/sciwx/demo/plt_demo.py b/sciwx/demo/plt_demo.py new file mode 100644 index 00000000..ebbb5039 --- /dev/null +++ b/sciwx/demo/plt_demo.py @@ -0,0 +1,31 @@ +import sys +sys.path.append('../../') + +from skimage.data import astronaut, camera +from sciwx import plt +print(plt, '=========') + +import numpy as np +import pandas as pd + +if __name__ == '__main__': + plt.imshow(camera(), cn=0) + + plt.imstackshow([astronaut(), 255-astronaut()], cn=(0,1,2)) + + plt.tabshow(pd.DataFrame(np.zeros((100,5)))) + + plt.txtshow('abcdefg') + + plt.mdshow('#Markdown\n## paragraph') + + fg = plt.figure() + ax = fg.add_subplot() + ax.plot(np.random.rand(100)) + + #mesh = plt.meshshow() + #vts, fs, ns, cs = plt.build_ball((100,100,100),50, (1,0,0)) + #mesh.add_surf('ball',vts, fs, ns, cs) + + plt.parashow({'c':(255,0,0)}, [('color', 'c', 'select', 'color')], False) + plt.show() diff --git a/sciwx/demo/shape_demo.py b/sciwx/demo/shape_demo.py new file mode 100644 index 00000000..1d5c5538 --- /dev/null +++ b/sciwx/demo/shape_demo.py @@ -0,0 +1,57 @@ +import sys +sys.path.append('../../') +from sciapp.object import mark2shp, Layer, json2shp +from sciapp.action import PointEditor, LineEditor, PolygonEditor, \ +RectangleEditor, EllipseEditor, FreeLineEditor, FreePolygonEditor, BaseEditor +from sciwx.canvas import VCanvas as Canvas +import wx + +point = {'type':'point', 'color':(255,0,0), 'lw':1, 'body':(10,10)} +points = {'type':'points', 'color':(255,0,0), 'lw':1, 'body':[(10,10),(100,200)]} +line = {'type':'line', 'color':(255,0,0), 'lw':1, 'lstyle':'-', 'body':[(10,10),(100,200),(200,200)]} +lines = {'type':'lines', 'color':(255,0,0), 'lw':1, 'lstyle':'-o', 'body':[[(10,10),(100,200),(200,200)],[(150,10),(50,250)]]} +polygon = {'type':'polygon', 'color':(255,0,0), 'fcolor':(255,255,0), 'lw':1, 'fill':False, 'lstyle':'o', 'body':[[(10,10),(100,200),(200,200)]]} +polygons = {'type':'polygons', 'color':(255,0,0), 'fcolor':(255,255,0,30), 'fill':True, 'lw':1, 'lstyle':'o', 'body':[[(10,10),(100,200),(200,200)],[(150,10),(50,250),(288,0)]]} +circle = {'type':'circle', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':(100,100,50)} +circles = {'type':'circles', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':True, 'lw':2, 'body':[(100,100,50),(300,300,100)]} +ellipse = {'type':'ellipse', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':(100,100,100,-50,1)} +ellipses = {'type':'ellipses', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':[(100,100,100,50,1),(200,250,50,100,3.14)]} +rectangle = {'type':'rectangle', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':True, 'body':(100,100,80,50)} +rectangles = {'type':'rectangles', 'fcolor':(255,255,0), 'fill':False, 'body':[(100,100,80,50),(200,200,80,100)]} +text = {'type':'text', 'color':(255,0,0), 'fcolor':(0,0,0), 'lw':8, 'fill':True, 'body':(100,200,'id=0')} +texts = {'type':'texts', 'color':(255,0,0), 'fcolor':(0,0,0), 'lw':8, 'fill':True, 'body':[(100,200,'id=0'),(180,250,'id=1')]} + +layer = {'type':'layer', 'num':-1, 'color':(255,255,0), 'fcolor':(255,255,255), 'fill':False, + 'body':[point, points, line, lines, polygon, polygons, circle, circles, ellipse, ellipses, rectangle, rectangles, text, texts]} + +layers = {'type':'layers', 'num':-1, 'color':(255,255,0), 'fcolor':(255,255,255), 'fill':False, 'body':{2:points, 1:line, 0:layer}} + +layer = {'type':'layer', 'num':-1, 'color':(0,0,255), 'fcolor':(255,255,255), 'fill':False, 'body':[ellipses, rectangles, ellipse]} + +def mark_test(mark): + frame = wx.Frame(None, title='gray test') + canvas = Canvas(frame, autofit=False, up=True) + canvas.set_shp(mark2shp(mark)) + frame.Show() + +if __name__ == '__main__': + app = wx.App() + #ShapeEditor(dtype={'layer', 'rectangles'}).start(None) + FreePolygonEditor().start(None) + #mark_test(point) + #mark_test(points) + #mark_test(line) + #mark_test(lines) + #mark_test(polygon) + #mark_test(polygons) + #mark_test(circle) + #mark_test(circles) + #mark_test(ellipse) + #mark_test(ellipses) + #mark_test(rectangle) + #mark_test(rectangles) + #mark_test(text) + #mark_test(texts) + mark_test(layer) + #mark_test(layers) + app.MainLoop() diff --git a/sciwx/demo/shape_edite_demo.py b/sciwx/demo/shape_edite_demo.py new file mode 100644 index 00000000..db45a13c --- /dev/null +++ b/sciwx/demo/shape_edite_demo.py @@ -0,0 +1,30 @@ +import sys +sys.path.append('../../') +from sciapp.object import mark2shp, Layer, json2shp +from sciapp.action import PointEditor, LineEditor, PolygonEditor, \ +RectangleEditor, EllipseEditor, FreeLineEditor, FreePolygonEditor, BaseEditor +from sciwx.canvas import VectorFrame +from sciwx.plugins.filters import Gaussian +import wx + +ellipse = {'type':'ellipse', 'body':(100,100,100,-50,1)} +rectangles = {'type':'rectangles', 'body':[(100,100,80,50),(200,200,80,100)]} +layer = {'type':'layer', 'num':-1, 'color':(0,0,255), 'fill':False, 'body':[rectangles, ellipse]} + +if __name__ == '__main__': + app = wx.App() + frame = VectorFrame(None) + frame.set_shp(mark2shp(layer)) + bar = frame.add_toolbar() + + bar.add_tool('E', BaseEditor) + bar.add_tool('P', PointEditor) + bar.add_tool('L', LineEditor) + bar.add_tool('M', PolygonEditor) + bar.add_tool('R', RectangleEditor) + bar.add_tool('O', EllipseEditor) + bar.add_tool('S', FreeLineEditor) + bar.add_tool('&', FreePolygonEditor) + + frame.Show() + app.MainLoop() diff --git a/sciwx/demo/shape_frame_demo.py b/sciwx/demo/shape_frame_demo.py new file mode 100644 index 00000000..055daf2c --- /dev/null +++ b/sciwx/demo/shape_frame_demo.py @@ -0,0 +1,56 @@ +import sys +sys.path.append('../../') +from sciapp.object import mark2shp, Layer, json2shp +from sciwx.canvas import VectorFrame, VectorNoteBook, VectorNoteFrame +import wx + +point = {'type':'point', 'color':(255,0,0), 'lw':1, 'body':(10,10)} +points = {'type':'points', 'color':(255,0,0), 'lw':1, 'body':[(10,10),(100,200)]} +line = {'type':'line', 'color':(255,0,0), 'lw':1, 'lstyle':'-', 'body':[(10,10),(100,200),(200,200)]} +lines = {'type':'lines', 'color':(255,0,0), 'lw':1, 'lstyle':'-o', 'body':[[(10,10),(100,200),(200,200)],[(150,10),(50,250)]]} +polygon = {'type':'polygon', 'color':(255,0,0), 'fcolor':(255,255,0), 'lw':1, 'fill':False, 'lstyle':'o', 'body':[(10,10),(100,200),(200,200)]} +polygons = {'type':'polygons', 'color':(255,0,0), 'fcolor':(255,255,0,30), 'fill':True, 'lw':1, 'lstyle':'o', 'body':[[(10,10),(100,200),(200,200)],[(150,10),(50,250),(288,0)]]} +circle = {'type':'circle', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':(100,100,50)} +circles = {'type':'circles', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':True, 'lw':2, 'body':[(100,100,50),(300,300,100)]} +ellipse = {'type':'ellipse', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':(100,100,100,50,1)} +ellipses = {'type':'ellipses', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':[(100,100,100,50,1),(200,250,50,100,3.14)]} +rectangle = {'type':'rectangle', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':True, 'body':(100,100,80,50)} +rectangles = {'type':'rectangles', 'color':(255,0,0), 'fcolor':(255,255,0), 'fill':False, 'body':[(100,100,80,50),(200,200,80,100)]} +text = {'type':'text', 'color':(255,0,0), 'fcolor':(0,0,0), 'lw':8, 'fill':True, 'body':(100,200,'id=0')} +texts = {'type':'texts', 'color':(255,0,0), 'fcolor':(0,0,0), 'lw':8, 'fill':True, 'body':[(100,200,'id=0'),(180,250,'id=1')]} + +layer = {'type':'layer', 'num':-1, 'clolor':(255,255,0), 'fcolor':(255,255,255), 'fill':False, + 'body':[point, points, line, lines, polygon, polygons, circle, circles, ellipse, ellipses, rectangle, rectangles, text, texts]} + +layers = {'type':'layers', 'num':-1, 'clolor':(255,255,0), 'fcolor':(255,255,255), 'fill':False, + 'body':{2:points, 1:line, 0:layer}} + +def frame_test(): + frame = VectorFrame(None) + frame.set_shp(mark2shp(layer)) + frame.Show() + +def note_test(): + frame = wx.Frame(None) + notebook = VectorNoteBook(frame) + canvas = notebook.add_canvas() + canvas.set_shp(mark2shp(layer)) + canvas = notebook.add_canvas() + canvas.set_shp(mark2shp(ellipses)) + frame.Show() + +def note_frame_test(): + frame = VectorNoteFrame(None) + canvas = frame.add_canvas() + canvas.set_shp(mark2shp(layer)) + canvas = frame.add_canvas() + canvas.set_shp(mark2shp(ellipses)) + frame.Show() + +if __name__ == '__main__': + from sciapp.action import ShapeTool + app = wx.App() + frame_test() + # note_test() + # note_frame_test() + app.MainLoop() diff --git a/sciwx/demo/shape_measure_demo.py b/sciwx/demo/shape_measure_demo.py new file mode 100644 index 00000000..87b0814a --- /dev/null +++ b/sciwx/demo/shape_measure_demo.py @@ -0,0 +1,26 @@ +import sys +sys.path.append('../../') +from sciapp.action import DistanceTool, AngleTool, SlopTool, AreaTool, CoordinateTool +from sciwx.canvas import CanvasFrame +from skimage.data import astronaut, camera +from sciapp.object import ROI, Line +import wx + +ellipse = {'type':'ellipse', 'body':(100,100,100,-50,1)} +rectangles = {'type':'rectangles', 'body':[(100,100,80,50),(200,200,80,100)]} +layer = {'type':'layer', 'num':-1, 'color':(0,0,255), 'fill':False, 'body':[rectangles, ellipse]} + +if __name__ == '__main__': + app = wx.App() + frame = CanvasFrame(None) + bar = frame.add_toolbar() + + bar.add_tool('C', CoordinateTool) + bar.add_tool('D', DistanceTool) + bar.add_tool('A', AngleTool) + bar.add_tool('T', SlopTool) + bar.add_tool('S', AreaTool) + + frame.Show() + frame.canvas.set_img(camera()) + app.MainLoop() \ No newline at end of file diff --git a/sciwx/demo/shape_roi_demo.py b/sciwx/demo/shape_roi_demo.py new file mode 100644 index 00000000..ad428c7d --- /dev/null +++ b/sciwx/demo/shape_roi_demo.py @@ -0,0 +1,31 @@ +import sys +sys.path.append('../../') +from sciapp.object import mark2shp, Layer, json2shp +from sciapp.action import PolygonROI, LineROI, PointROI, EllipseROI, RectangleROI, FreeLineROI, FreePolygonROI +from sciwx.canvas import CanvasFrame +from skimage.data import astronaut, camera +from sciapp.object import ROI, Line +import wx + +ellipse = {'type':'ellipse', 'body':(100,100,100,-50,1)} +rectangles = {'type':'rectangles', 'body':[(100,100,80,50),(200,200,80,100)]} +layer = {'type':'layer', 'num':-1, 'color':(0,0,255), 'fill':False, 'body':[rectangles, ellipse]} + +if __name__ == '__main__': + app = wx.App() + frame = CanvasFrame(None) + frame.canvas.set_img(camera()) + roi = ROI([Line([(0,0),(100,100),(300,500)])]) + frame.canvas.image.roi = roi + bar = frame.add_toolbar() + bar = frame.add_toolbar() + + bar.add_tool('P', PointROI) + bar.add_tool('L', LineROI) + bar.add_tool('M', PolygonROI) + bar.add_tool('R', RectangleROI) + bar.add_tool('O', EllipseROI) + bar.add_tool('S', FreeLineROI) + bar.add_tool('&', FreePolygonROI) + frame.Show() + app.MainLoop() \ No newline at end of file diff --git a/sciwx/demo/text_demo.py b/sciwx/demo/text_demo.py new file mode 100644 index 00000000..afa23112 --- /dev/null +++ b/sciwx/demo/text_demo.py @@ -0,0 +1,40 @@ +import sys, wx +sys.path.append('../../') + +from sciwx.text import TextPad, TextNoteBook, TextFrame, TextNoteFrame + +def text_pad_test(): + frame = wx.Frame(None, title='Text Pad') + textpad = TextPad(frame) + textpad.append('abcdefg') + frame.Show() + +def text_frame_test(): + textframe = TextFrame(None) + textframe.append('abcdefg') + textframe.Show() + +def text_note_book(): + frame = wx.Frame(None, title='Text Note Book') + tnb = TextNoteBook(frame) + note1 = tnb.add_notepad() + note1.append('abc') + note1 = tnb.add_notepad() + note1.append('def') + frame.Show() + +def text_note_frame(): + npbf = TextNoteFrame(None) + note1 = npbf.add_notepad() + note1.append('abc') + note1 = npbf.add_notepad() + note1.append('def') + npbf.Show() + +if __name__ == '__main__': + app = wx.App() + text_pad_test() + text_frame_test() + text_note_book() + text_note_frame() + app.MainLoop() diff --git a/sciwx/demo/widget_demo.py b/sciwx/demo/widget_demo.py new file mode 100644 index 00000000..78967f5b --- /dev/null +++ b/sciwx/demo/widget_demo.py @@ -0,0 +1,30 @@ +import sys, wx, numpy as np +sys.path.append('../../') +from sciwx.widgets import CMapPanel, CMapSelPanel, CurvePanel, HistPanel + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + sizer = wx.BoxSizer(wx.VERTICAL) + + cmap = CMapPanel(frame) + cmap.set_hist(np.random.rand(256)+2) + sizer.Add(cmap, 0, 0, 0) + + cmapsel = CMapSelPanel(frame, 'color map') + cmap = np.arange(256*3, dtype=np.uint8).reshape((3,-1)).T + cmapsel.SetItems({'gray':cmap}) + sizer.Add(cmapsel, 0, 0, 0) + + hist = HistPanel(frame) + hist.SetValue(np.random.rand(256)) + sizer.Add(hist, 0, 0, 0) + + curve = CurvePanel(frame, l=230) + curve.set_hist(np.random.rand(256)+2) + sizer.Add(curve, 0, 0, 0) + + frame.SetSizer(sizer) + frame.Fit() + frame.Show() + app.MainLoop() diff --git a/sciwx/doc/document.md b/sciwx/doc/document.md new file mode 100644 index 00000000..e69de29b diff --git a/sciwx/grid/__init__.py b/sciwx/grid/__init__.py new file mode 100644 index 00000000..697596a3 --- /dev/null +++ b/sciwx/grid/__init__.py @@ -0,0 +1,2 @@ +from .grid import Grid +from .widget import GridFrame, GridNoteBook, GridNoteFrame, MGrid \ No newline at end of file diff --git a/imagepy/ui/tablewindow.py b/sciwx/grid/grid.py similarity index 53% rename from imagepy/ui/tablewindow.py rename to sciwx/grid/grid.py index e63a946d..864b79b7 100644 --- a/imagepy/ui/tablewindow.py +++ b/sciwx/grid/grid.py @@ -1,65 +1,63 @@ -#!/usr/bin/env python - +from sciapp.object import Table +from sciapp.action import TableTool import wx, os -import wx.grid as Grid +import wx.grid import sys -from imagepy import IPy, root_dir import numpy as np import pandas as pd -from imagepy.core.manager import TableManager, WTableManager +from ..widgets import get_para -class TableBase(Grid.GridTableBase): - """ - A custom wx.Grid Table using user supplied data - """ +class TableBase(wx.grid.GridTableBase): def __init__(self): - Grid.GridTableBase.__init__(self) - self.tps = None + wx.grid.GridTableBase.__init__(self) + self.table = None - def set_tps(self, tps): - self.tps = tps - self._rows = tps.data.shape[0] - self._cols = tps.data.shape[1] - print('haha') + def set_data(self, table): + self.table = table + self._rows, self._cols = table.shape def GetNumberCols(self): - if self.tps is None: return 0 - return self.tps.data.shape[1] + if self.table is None: return 0 + return self.table.shape[1] def GetNumberRows(self): - if self.tps is None: return 0 - return self.tps.data.shape[0] + if self.table is None: return 0 + return self.table.shape[0] def GetColLabelValue(self, col): - return str(self.tps.data.columns[col]) + return str(self.table.columns[col]) def GetRowLabelValue(self, row): - return str(self.tps.data.index[row]) + return str(self.table.index[row]) def GetValue(self, row, col): - return self.tps.data.iat[row, col] + return self.table.data.iat[row, col] def GetRawValue(self, row, col): return 'x' def SetValue(self, row, col, value): - col = self.tps.data.columns[col] - self.tps.data[col][row] = value + col = self.table.columns[col] + self.table.data[col,row] = value def ResetView(self, grid): grid.BeginBatch() for current, new, delmsg, addmsg in [ - (self._rows, self.GetNumberRows(), Grid.GRIDTABLE_NOTIFY_ROWS_DELETED, Grid.GRIDTABLE_NOTIFY_ROWS_APPENDED), - (self._cols, self.GetNumberCols(), Grid.GRIDTABLE_NOTIFY_COLS_DELETED, Grid.GRIDTABLE_NOTIFY_COLS_APPENDED), + (self._rows, self.GetNumberRows(), + wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, + wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED), + (self._cols, self.GetNumberCols(), + wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, + wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED) ]: if new < current: - msg = Grid.GridTableMessage(self,delmsg,new,current-new) + msg = wx.grid.GridTableMessage(self,delmsg,new,current-new) grid.ProcessTableMessage(msg) elif new > current: - msg = Grid.GridTableMessage(self,addmsg,new-current) + msg = wx.grid.GridTableMessage(self,addmsg,new-current) grid.ProcessTableMessage(msg) self.UpdateValues(grid) @@ -78,7 +76,7 @@ def ResetView(self, grid): def UpdateValues(self, grid): """Update all displayed values""" # This sends an event to the grid table to update all of the values - msg = Grid.GridTableMessage(self, Grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) + msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) def _updateColAttrs(self, grid): @@ -91,27 +89,16 @@ def _updateColAttrs(self, grid): """ col = 0 - for colname in self.tps.data.columns: - attr = Grid.GridCellAttr() - renderer = MegaFontRenderer(self.tps) + for colname in self.table.columns: + attr = wx.grid.GridCellAttr() + renderer = MegaFontRenderer(self.table) attr.SetRenderer(renderer) grid.SetColAttr(col, attr) col += 1 - - - # end table manipulation code - # ---------------------------------------------------------- - - -# -------------------------------------------------------------------- -# Sample wx.Grid renderers - - - -class MegaFontRenderer(Grid.GridCellRenderer): - def __init__(self, tps): - Grid.GridCellRenderer.__init__(self) +class MegaFontRenderer(wx.grid.GridCellRenderer): + def __init__(self, table): + wx.grid.GridCellRenderer.__init__(self) n_back = wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) l_back = wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) n_text = wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOWTEXT ) @@ -121,12 +108,12 @@ def __init__(self, tps): self.selectedBrush = wx.Brush("blue", wx.BRUSHSTYLE_SOLID) self.normalBrush = wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID) self.font = wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, "ARIAL") - self.tps = tps + self.table = table def Draw(self, grid, attr, dc, rect, row, col, isSelected): dc.SetClippingRegion(rect) - cn = self.tps.data.columns[col] - rd, tc, lc, ln = self.tps.get_props()[cn] + cn = self.table.columns[col] + rd, tc, lc, ln = self.table.style[cn] bcolor, tcolor = self.colors[isSelected] if isSelected:tc = tcolor @@ -137,11 +124,11 @@ def Draw(self, grid, attr, dc, rect, row, col, isSelected): dc.SetPen(wx.Pen(bcolor, 1, wx.PENSTYLE_SOLID)) dc.DrawRectangle(rect) - isnum = np.issubdtype(self.tps.data[cn].dtype, np.number) + isnum = np.issubdtype(self.table.data[cn].dtype, np.number) if ln!='Line': if isnum: - text = str(round(self.tps.data.iat[row, col], rd)) - else: text = str(self.tps.data.iat[row, col]) + text = str(round(self.table.data.iat[row, col], rd)) + else: text = str(self.table.data.iat[row, col]) dc.SetBackgroundMode(wx.SOLID) # change the text background based on whether the grid is selected @@ -162,10 +149,10 @@ def Draw(self, grid, attr, dc, rect, row, col, isSelected): dc.DrawText("...", x, rect.y+1) if isnum and ln!='Text': - data = self.tps.data + data = self.table.data cn = data.columns[col] rn = data.index[row] - minv, maxv = self.tps.range[cn] + minv, maxv = self.table.rg[cn] dc.SetPen(wx.Pen(wx.Colour(lc), 1, wx.PENSTYLE_SOLID)) dc.SetBrush(wx.Brush(wx.Colour(lc), wx.BRUSHSTYLE_SOLID)) v = rect.x + (data[cn][rn]-minv)/(maxv-minv)*rect.width @@ -183,113 +170,107 @@ def Draw(self, grid, attr, dc, rect, row, col, isSelected): # Sample Grid using a specialized table and renderers that can # be plugged in based on column names -class GridBase(Grid.Grid): +class Grid(wx.grid.Grid): def __init__(self, parent): - Grid.Grid.__init__(self, parent, -1) - self.table = TableBase() - self.Bind(Grid.EVT_GRID_RANGE_SELECT, self.on_select) + wx.grid.Grid.__init__(self, parent, -1) + self.tablebase = TableBase() + self.table = Table() + self.tool = None + + self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.on_select) + self.Bind(wx.grid.EVT_GRID_LABEL_RIGHT_CLICK, self.on_label) + self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.on_cell) + self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.on_cell) self.Bind(wx.EVT_IDLE, self.on_idle) - self.tps = None - self.handle = None - self.Bind(Grid.EVT_GRID_LABEL_RIGHT_CLICK, self.on_label) def on_label(self, evt): - # Did we click on a row or a column? row, col = evt.GetRow(), evt.GetCol() if row==-1: - props = self.tps.props + props = self.table.props - cur = props.iloc[:,col] + cur = props[self.table.columns[col]] + print(cur) para = {'accu':cur[0], 'tc':cur[1], 'lc':cur[2], 'ln':cur[3]} view = [(int, 'accu', (0, 10), 0, 'accuracy', ''), ('color', 'tc', 'text color', ''), ('color', 'lc', 'line color', ''), (list, 'ln', ['Text', 'Line', 'Both'], str, 'draw', '')] - rst = IPy.get_para('Table Properties', view, para) - if rst!=wx.ID_OK:return + rst = get_para(para, view, 'Table Properties', self) + if not rst :return + print(para) if col!=-1: - props.iloc[:,col] = [para[i] for i in ['accu', 'tc', 'lc', 'ln']] + props[self.table.columns[col]] = [para[i] for i in ['accu', 'tc', 'lc', 'ln']] + #print(props[self.table.columns[col]]) + #print('===========') if col==-1: - for c in range(props.shape[1]): - props.iloc[:,c] = [para[i] for i in ['accu', 'tc', 'lc', 'ln']] - ''' - if row==-1 and col>-1: - props.ix['ln',col] = (props.ix['ln',col]+1)%3 - if row==-1 and col==-1: - cn = self.tps.data.columns[col] - props.ix['ln'] = (props.ix['ln'].min()+1)%3 - ''' - self.tps.update = True - - - def set_handler(self, handle=None): - self.handle = handle - - def set_tps(self, tps): - self.tps = tps - self.table.set_tps(tps) - self._rows, self._cols = tps.data.shape - self.SetTable(self.table) - self.Reset() + for c in self.table.columns: + props[c] = [para[i] for i in ['accu', 'tc', 'lc', 'ln']] + self.update() + + def on_cell(self, me): + x, y = me.GetCol(), me.GetRow() + obj, tol = self.table, TableTool.default + tool = self.tool or tol + #ld, rd, md = me.LeftIsDown(), me.RightIsDown(), me.MiddleIsDown() + sta = [me.AltDown(), me.ControlDown(), me.ShiftDown()] + others = {'alt':sta[0], 'ctrl':sta[1], + 'shift':sta[2], 'grid':self} + tool.mouse_down(self.table, x, y, 0, **others) + me.Skip() + + def set_data(self, tab): + if isinstance(tab, Table): + self.table = tab + else: self.table.data = tab + self.tablebase.set_data(self.table) + self._rows, self._cols = tab.shape + self.SetTable(self.tablebase) + self.update() + + def set_style(self, name, **key): + self.table.set_style(name, **key) + self.update() def on_select(self, event): rs, cs = self.GetSelectedRows(), self.GetSelectedCols() lt, rb = self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight() if len(lt)==1 and len(rb)==1: - return self.tps.select(range(lt[0][0],rb[0][0]+1), range(lt[0][1],rb[0][1]+1)) - else: self.tps.select(rs, cs, True) - #self.tps.select() + return self.table.select(range(lt[0][0],rb[0][0]+1), range(lt[0][1],rb[0][1]+1)) + else: self.table.select(list(rs), list(cs), True) - def Reset(self): - """reset the view based on the data in the table. Call - this when rows are added or destroyed""" - self.table.ResetView(self) - if not self.handle is None: - self.handle(self.tps) + def update(self): + self.tablebase.ResetView(self) def select(self): - self.Bind(Grid.EVT_GRID_RANGE_SELECT, None) + self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, None) self.ClearSelection() - print('select grid') - print(self.tps.rowmsk, self.tps.colmsk) - for i in self.tps.data.index.get_indexer(self.tps.rowmsk): - print(i) + for i in self.table.index.get_indexer(self.table.rowmsk): self.SelectRow(i, True) - for i in self.tps.data.columns.get_indexer(self.tps.colmsk): - print(i) + for i in self.table.columns.get_indexer(self.table.colmsk): self.SelectCol(i, True) - self.Bind(Grid.EVT_GRID_RANGE_SELECT, self.on_select) - - def __del__(self): - print('grid deleted!!!') + self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.on_select) def on_idle(self, event): - if not self.IsShown() or self.tps is None\ - or self.tps.update == False: return - if self.tps.update == 'shp': - self.select() - self.Reset() - if self.tps.update == True: - self.select() - self.ForceRefresh() - self.tps.update = False + if not self.IsShown() or self.table is None\ + or self.table.dirty == False: return + + self.tablebase.ResetView(self) + self.table.dirty = False + # self.select() print('update') - -#--------------------------------------------------------------------------- - + def __del__(self): + print('grid deleted!!!') if __name__ == '__main__': dates = pd.date_range('20170220',periods=6) - data = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD')) + df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD')) app = wx.App(False) - tf = TableFrame() - from imagepy import TablePlus - tps = TablePlus('table1', data) - tf.set_tps(tps) - tf.Show() - + frame = wx.Frame(None) + grid = Grid(frame) + grid.set_data(df) + frame.Show() app.MainLoop() diff --git a/sciwx/grid/widget.py b/sciwx/grid/widget.py new file mode 100644 index 00000000..e2ca1623 --- /dev/null +++ b/sciwx/grid/widget.py @@ -0,0 +1,145 @@ +import wx, wx.lib.agw.aui as aui +from .grid import Grid + +class MGrid(wx.Panel): + def __init__(self, parent=None, autofit=False): + wx.Frame.__init__ ( self, parent) + self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) + + self.SetBackgroundColour( wx.Colour( 255, 255, 255 ) ) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.lab_info = wx.StaticText( self, wx.ID_ANY, + 'information', wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_info.Wrap( -1 ) + self.Bind(wx.EVT_IDLE, self.on_idle) + sizer.Add( self.lab_info, 0, wx.EXPAND, 0 ) + + self.grid = Grid(self) + sizer.Add( self.grid, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.select = self.grid.select + self.set_data = self.grid.set_data + + @property + def table(self): return self.grid.table + + @property + def name(self): return self.grid.table.name + + def on_idle(self, event): + if self.table.data is None: return + if self.lab_info.GetLabel() != self.table.info: + self.lab_info.SetLabel(self.table.info) + +class GridFrame(wx.Frame): + def __init__(self, parent=None): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'GridFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.grid = MGrid(self) + sizer.Add(self.grid, 1, wx.EXPAND|wx.ALL, 0) + self.SetSizer(sizer) + self.set_data = self.grid.set_data + self.Bind(wx.EVT_IDLE, self.on_idle) + + def on_idle(self, event): + if self.GetTitle()!=self.grid.table.name: + self.SetTitle(self.grid.table.name) + + def set_title(self, tab): self.SetTitle(tab.name) + + def on_valid(self, event): event.Skip() + + def on_close(self, event): event.Skip() + + def Show(self): + self.Fit() + wx.Frame.Show(self) + +class GridNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).table.title + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def grid(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_grid(self, grid=None): + if grid is None: grid = MGrid(self) + self.AddPage(grid, 'Image', True, wx.NullBitmap ) + return grid + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + +class GridNoteFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'CanvasNoteFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = GridNoteBook(self) + self.grid = self.notebook.grid + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_grid = self.notebook.add_grid + self.Layout() + self.Bind(wx.EVT_CLOSE, self.on_close) + + def add_toolbar(self): + toolbar = ToolBar(self) + self.GetSizer().Insert(0, toolbar, 0, wx.EXPAND | wx.ALL, 0) + return toolbar + + def on_close(self, event): + while self.notebook.GetPageCount()>0: + self.notebook.DeletePage(0) + event.Skip() + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + df = pd.DataFrame(np.arange(100).reshape((20,5))) + app = wx.App() + cf = GridFrame(None) + cf.set_data(df) + cf.Show() + app.MainLoop() + + ''' + app = wx.App() + cnf = CanvasNoteFrame(None) + canvas = cnf.add_img() + canvas.set_img(camera()) + + canvas = cnf.add_img() + canvas.set_img(camera()) + canvas.set_cn(0) + + cnf.Show() + app.MainLoop() + ''' diff --git a/sciwx/mesh/__init__.py b/sciwx/mesh/__init__.py new file mode 100644 index 00000000..440e18bd --- /dev/null +++ b/sciwx/mesh/__init__.py @@ -0,0 +1,4 @@ +from .canvas import Canvas3D +from .mcanvas import MCanvas3D +from .widget import Canvas3DFrame, Canvas3DNoteBook, Canvas3DNoteFrame +# from .scene import Surface, MarkText, MeshSet \ No newline at end of file diff --git a/sciwx/mesh/canvas.py b/sciwx/mesh/canvas.py new file mode 100644 index 00000000..98201c60 --- /dev/null +++ b/sciwx/mesh/canvas.py @@ -0,0 +1,256 @@ +import wx, weakref, sys +import numpy as np +from vispy import app, scene, gloo +import platform, os.path as osp +from vispy.visuals.transforms import STTransform +from vispy.color import Colormap +from sciapp.object import Mesh, TextSet, Volume3d, Scene +from sciapp.action import MeshTool + +# verts, faces, colors, mode, alpha, visible +# light_dir, light, ambiass, background + +from vispy.geometry import MeshData + +def get_vertex_normals(self, indexed=None): + if self._vertex_normals is None: + faceNorms = self.get_face_normals() + self._vertex_normals = np.zeros(self._vertices.shape, dtype=np.float32) + np.add.at(self._vertex_normals, self._faces.T, faceNorms[None,:,:]) + v = np.linalg.norm(self._vertex_normals, axis=1)[:,None] + self._vertex_normals /= np.clip(v, 1e-5, 1e5, out=v) + if indexed is None: return self._vertex_normals + elif indexed == 'faces': return self._vertex_normals[self.get_faces()] + +MeshData.get_vertex_normals = get_vertex_normals + +class MeshVisual(scene.visuals.Mesh): + def __init__(self, *p, **key): + scene.visuals.Mesh.__init__(self, *p, **key) + self.unfreeze() + self._light_color = (0.7,0.7,0.7, 1.0) + self.freeze() + + @property + def light_color(self): + return self._light_color + + @light_color.setter + def light_color(self, light): + self._light_color = light + self.mesh_data_changed() + + def _update_data(self): + rst = scene.visuals.Mesh._update_data(self) + #if self.shading is not None: + # self.shared_program.vert['light_color'] = self._light_color + return rst + +class VolumeVisual(scene.visuals.Volume): + def set_data(self, vol, clim=None, copy=True): + scene.visuals.Volume.set_data(self, vol.transpose(2,1,0), clim, copy) + + def _compute_bounds(self, axis, view): + return 0, self._vol_shape[::-1][axis] + +def viewmesh(mesh, view): + if mesh.dirty == 'geom': + faces = mesh.faces + if mesh.mode == 'grid': faces = mesh.get_edges() + if mesh.mode == 'points': faces = np.arange(len(mesh.verts)) + shading = 'smooth' if mesh.mode == 'mesh' else None + if view is None or view.shading!=shading: + if not view is None: view.parent = None + view = MeshVisual(shading=shading) + dic = {'mesh':'triangles', 'grid':'lines', 'points':'points'} + view._draw_mode = dic[mesh.mode] + key = {'vertices':mesh.verts, 'faces':faces} + if isinstance(mesh.colors, tuple): colorkey = 'color' + elif mesh.colors.ndim == 2: colorkey = 'vertex_colors' + elif mesh.colors.ndim == 1: colorkey = 'vertex_values' + key[colorkey] = mesh.colors + view.set_data(**key) + view.interactive = True + if view.shading_filter: + view.shading_filter.shininess = mesh.shiness + if isinstance(mesh.cmap, str): cmap = mesh.cmap + elif mesh.cmap.max()>1+1e-5: cmap = Colormap(mesh.cmap/255) + else: cmap = Colormap(mesh.cmap) + if isinstance(mesh.colors, tuple): view.color = mesh.colors + view.cmap = cmap + view.clim = [-1, 1] + view.visible = mesh.visible + view.opacity = mesh.alpha + ''' + if mesh.high_light is False: + view._picking_filter.enabled = False + view._picking_filter.id = view._id + else: + view._picking_filter.enabled = True + view._picking_filter.id = mesh.high_light + ''' + # view.shading = 'flat' + mesh.dirty = False + return view + +def viewtext(text, view): + if text.dirty=='geom': + if view is None: + view = scene.visuals.Text() + + view.text = text.texts + view.pos = text.verts + view.color = text.colors + view.font_size = text.size + + view.interactive = True + view.visible = text.visible + view.opacity = text.alpha + if text.high_light is False: + view._picking_filter.enabled = False + view._picking_filter.id = view._id + else: + view._picking_filter.enabled = True + view._picking_filter.id = text.high_light + # view.shading = 'flat' + text.dirty = False + return view + +def viewvolume(vol, view): + if isinstance(vol.cmap, str): cmap = vol.cmap + elif vol.cmap.max()>1+1e-5: cmap = Colormap(vol.cmap/255) + else: cmap = Colormap(vol.cmap) + if vol.dirty=='geom': + if view is None: + view = VolumeVisual(vol.imgs, cmap=cmap) + view.relative_step_size = vol.step + view.threshold = vol.level + + view.interactive = True + view.visible = vol.visible + view.opacity = vol.alpha + if vol.high_light is False: + view._picking_filter.enabled = False + view._picking_filter.id = view._id + else: + view._picking_filter.enabled = True + view._picking_filter.id = vol.high_light + # view.shading = 'flat' + vol.dirty = False + return view + + +def viewobj(obj, view): + if isinstance(obj, Mesh): return viewmesh(obj, view) + if isinstance(obj, TextSet): return viewtext(obj, view) + if isinstance(obj, Volume3d): return viewvolume(obj, view) + +class Canvas3D(wx.Panel): + def __init__(self, parent, scene3d=None): + wx.Panel.__init__(self, parent) + self.canvas = scene.SceneCanvas(app='wx', parent=self, keys='interactive', show=True, dpi=150) + box = wx.BoxSizer( wx.VERTICAL ) + box.Add(self.GetChildren()[-1], 1, wx.ALL|wx.EXPAND, 0 ) + self.SetSizer(box) + self.set_scene(scene3d or Scene()) + self.Bind(wx.EVT_IDLE, self.on_idle) + self.view = self.canvas.central_widget.add_view() + self.visuals = {} + self.curobj = None + self.tool = None + self.camera = scene.cameras.TurntableCamera(parent=self.view.scene, fov=45, name='Turntable') + self.view.camera = self.camera + + self.canvas._process_mouse_event = self._process_mouse_event + + def set_scene(self, scene): + self.scene3d = scene + + def add_obj(self, name, obj): + self.scene3d.add_obj(name, obj) + + def on_idle(self, event): + need = 'ignore' + if self.scene3d.dirty: + need = 'update' + self.canvas.bgcolor = self.scene3d.bg_color + for i in self.visuals: + if not isinstance(self.visuals[i], MeshVisual): continue + if self.visuals[i].shading_filter is None: continue + # self.visuals[i].shading_filter.shininess = 0 + self.visuals[i].shading_filter.ambient_light = self.scene3d.ambient_color + self.visuals[i].shading_filter.diffuse_light = self.scene3d.light_color + self.visuals[i].shading_filter.light_dir = self.scene3d.light_dir + self.scene3d.dirty = False + for i in self.scene3d.names: + obj = self.scene3d.objects[i] + if not i in self.visuals: + self.visuals[i] = viewobj(obj, None) + need = 'add' + vis = self.visuals[i] + if obj.dirty != False: + self.visuals[i] = viewobj(obj, vis) + if need=='ignore': need = 'update' + self.visuals[i].parent = self.view.scene + if need == 'add': self.fit() + if need != 'ignore': + self.canvas.update() + + def fit(self): self.set_camera(auto=True) + + def set_camera(self, azimuth=None, elevation=None, dist=None, fov=None, auto=False): + if not azimuth is None: self.camera.azimuth = azimuth + if not elevation is None: self.camera.elevation = elevation + if not fov is None: self.camera.fov = fov + if auto: self.camera.set_range() + + def at(self, x, y): + self.view.interactive = False + vis = self.visual_at((x, y)) + self.view.interactive = True + for k in self.visuals: + if self.visuals[k] is vis: + return self.scene3d.objects[k] + return None + + def _process_mouse_event(self, event): + # self.measure_fps() + # return scene.SceneCanvas._process_mouse_event(self, event) + px, py = x, y = tuple(event.pos) + canvas, tool, btn = self, self.tool or MeshTool.default, event._button + btn = {2:3, 3:2}.get(btn, btn) + ld, rd, md = [i in event.buttons for i in (1,2,3)] + sta = [i in [j.name for j in event.modifiers] for i in ('Alt', 'Control', 'Shift')] + others = {'alt':sta[0], 'ctrl':sta[1], + 'shift':sta[2], 'px':px, 'py':py, 'canvas':canvas} + + if event.type == 'mouse_press': + canvas.SetFocus() + tool.mouse_down(canvas.scene3d, x, y, btn, **others) + if event.type == 'mouse_release': + tool.mouse_up(canvas.scene3d, x, y, btn, **others) + if event.type == 'mouse_move': + tool.mouse_move(canvas.scene3d, x, y, None, **others) + + wheel = np.sign(event.delta[1]) + #if me.Dragging(): + # tool.mouse_move(canvas.scene3d, x, y, btn, **others) + if wheel!=0: + tool.mouse_wheel(canvas.scene3d, x, y, wheel, **others) + ckey = {'arrow':1,'cross':5,'hand':6} + cursor = ckey[tool.cursor] if tool.cursor in ckey else 1 + canvas.SetCursor(wx.Cursor(cursor)) + event.handled = True + + def close(self): + self.canvas._process_mouse_event = None + self.canvas = None + + def __del__(self): + # self.img = self.back = None + print('========== canvas del') + +def make_bitmap(bmp): + img = bmp.ConvertToImage() + img.Resize((20, 20), (2, 2)) + return img.ConvertToBitmap() diff --git a/sciwx/mesh/imgs/__init__.py b/sciwx/mesh/imgs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imagepy/core/myvi/imgs/configure.png b/sciwx/mesh/imgs/configure.png similarity index 100% rename from imagepy/core/myvi/imgs/configure.png rename to sciwx/mesh/imgs/configure.png diff --git a/imagepy/core/myvi/imgs/isometric.png b/sciwx/mesh/imgs/isometric.png similarity index 100% rename from imagepy/core/myvi/imgs/isometric.png rename to sciwx/mesh/imgs/isometric.png diff --git a/imagepy/core/myvi/imgs/logo.ico b/sciwx/mesh/imgs/logo.ico similarity index 100% rename from imagepy/core/myvi/imgs/logo.ico rename to sciwx/mesh/imgs/logo.ico diff --git a/sciwx/mesh/imgs/open.png b/sciwx/mesh/imgs/open.png new file mode 100644 index 00000000..165409ab Binary files /dev/null and b/sciwx/mesh/imgs/open.png differ diff --git a/imagepy/core/myvi/imgs/parallel.png b/sciwx/mesh/imgs/parallel.png similarity index 100% rename from imagepy/core/myvi/imgs/parallel.png rename to sciwx/mesh/imgs/parallel.png diff --git a/imagepy/core/myvi/imgs/save.png b/sciwx/mesh/imgs/save.png similarity index 100% rename from imagepy/core/myvi/imgs/save.png rename to sciwx/mesh/imgs/save.png diff --git a/sciwx/mesh/imgs/stl.png b/sciwx/mesh/imgs/stl.png new file mode 100644 index 00000000..c97c2787 Binary files /dev/null and b/sciwx/mesh/imgs/stl.png differ diff --git a/imagepy/core/myvi/imgs/x-axis.png b/sciwx/mesh/imgs/x-axis.png similarity index 100% rename from imagepy/core/myvi/imgs/x-axis.png rename to sciwx/mesh/imgs/x-axis.png diff --git a/imagepy/core/myvi/imgs/y-axis.png b/sciwx/mesh/imgs/y-axis.png similarity index 100% rename from imagepy/core/myvi/imgs/y-axis.png rename to sciwx/mesh/imgs/y-axis.png diff --git a/imagepy/core/myvi/imgs/z-axis.png b/sciwx/mesh/imgs/z-axis.png similarity index 100% rename from imagepy/core/myvi/imgs/z-axis.png rename to sciwx/mesh/imgs/z-axis.png diff --git a/sciwx/mesh/mcanvas.py b/sciwx/mesh/mcanvas.py new file mode 100644 index 00000000..93a7f857 --- /dev/null +++ b/sciwx/mesh/mcanvas.py @@ -0,0 +1,280 @@ +from .canvas import Canvas3D +import wx, os.path as osp, platform +import math +import numpy as np + +def make_bitmap(bmp): + img = bmp.ConvertToImage() + img.Resize((20, 20), (2, 2)) + return img.ConvertToBitmap() + +class MCanvas3D(wx.Panel): + def __init__( self, parent, scene=None): + wx.Panel.__init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL ) + #self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) + sizer = wx.BoxSizer( wx.VERTICAL ) + self.canvas = Canvas3D(self, scene) + self.toolbar = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize) + tsizer = wx.BoxSizer( wx.HORIZONTAL ) + + root = osp.abspath(osp.dirname(__file__)) + + #self.SetIcon(wx.Icon('data/logo.ico', wx.BITMAP_TYPE_ICO)) + + self.btn_x = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap( osp.join(root, 'imgs/x-axis.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_x, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + self.btn_y = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap( osp.join(root, 'imgs/y-axis.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_y, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + self.btn_z = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap( osp.join(root, 'imgs/z-axis.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_z, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + tsizer.Add(wx.StaticLine( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL), 0, wx.ALL|wx.EXPAND, 2 ) + self.btn_pers = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap( osp.join(root, 'imgs/isometric.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_pers, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + self.btn_orth = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap( osp.join(root, 'imgs/parallel.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_orth, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + tsizer.Add(wx.StaticLine( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL), 0, wx.ALL|wx.EXPAND, 2 ) + self.btn_open = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap(osp.join(root, 'imgs/open.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_open, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + self.btn_stl = wx.BitmapButton( self.toolbar, wx.ID_ANY, make_bitmap(wx.Bitmap(osp.join(root, 'imgs/stl.png'), wx.BITMAP_TYPE_ANY )), wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW ) + tsizer.Add( self.btn_stl, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + #pan = wx.Panel(self.toolbar, size=(50, 50)) + self.btn_color = wx.ColourPickerCtrl( self.toolbar, wx.ID_ANY, wx.Colour( 128, 128, 128 ), wx.DefaultPosition, [(33, 38), (-1, -1)][platform.system() in ['Windows', 'Linux']], wx.CLRP_DEFAULT_STYLE ) + tsizer.Add( self.btn_color, 0, wx.ALL|(0, wx.EXPAND)[platform.system() in ['Windows', 'Linux']], 0 ) + tsizer.Add(wx.StaticLine( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL), 0, wx.ALL|wx.EXPAND, 2 ) + self.cho_light = wx.Choice( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, ['force light', 'normal light', 'weak light', 'off light'], 0 ) + self.cho_light.SetSelection( 1 ) + tsizer.Add( self.cho_light, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + self.cho_bg = wx.Choice( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, ['force scatter', 'normal scatter', 'weak scatter', 'off scatter'], 0 ) + self.cho_bg.SetSelection( 1 ) + tsizer.Add( self.cho_bg, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + self.spn_dirv = wx.SpinButton( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + tsizer.Add( self.spn_dirv, 0, wx.ALL|wx.EXPAND, 1 ) + self.spn_dirh = wx.SpinButton( self.toolbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_HORIZONTAL ) + tsizer.Add( self.spn_dirh, 0, wx.ALL|wx.EXPAND, 1 ) + + self.spn_dirv.SetRange(-1e4, 1e4) + self.spn_dirh.SetRange(-1e4, 1e4) + + self.toolbar.SetSizer( tsizer ) + tsizer.Layout() + + self.settingbar = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + ssizer = wx.BoxSizer( wx.HORIZONTAL ) + + + cho_objChoices = [''] + self.cho_obj = wx.Choice( self.settingbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_objChoices, 0 ) + self.cho_obj.SetSelection( 0 ) + ssizer.Add( self.cho_obj, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + + self.chk_visible = wx.CheckBox( self.settingbar, wx.ID_ANY, u"visible", wx.DefaultPosition, wx.DefaultSize, 0 ) + ssizer.Add( self.chk_visible, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) + + self.cho_hlight = wx.Choice( self.settingbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, ['force specular', 'normal specular', 'weak specular', 'off specular'], 0 ) + self.cho_hlight.SetSelection( 3 ) + ssizer.Add( self.cho_hlight, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + + self.col_color = wx.ColourPickerCtrl( self.settingbar, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize, wx.CLRP_DEFAULT_STYLE ) + ssizer.Add( self.col_color, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + + self.m_staticText2 = wx.StaticText( self.settingbar, wx.ID_ANY, u"Alpha:", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText2.Wrap( -1 ) + ssizer.Add( self.m_staticText2, 0, wx.ALIGN_CENTER|wx.LEFT, 10 ) + + self.sli_alpha = wx.Slider( self.settingbar, wx.ID_ANY, 10, 0, 10, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL ) + ssizer.Add( self.sli_alpha, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + self.settingbar.SetSizer(ssizer) + + cho_objChoices = ['mesh', 'grid', 'points'] + self.cho_mode = wx.Choice( self.settingbar, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_objChoices, 0 ) + self.cho_mode.SetSelection( 0 ) + ssizer.Add( self.cho_mode, 0, wx.ALIGN_CENTER|wx.ALL, 1 ) + + sizer.Add( self.toolbar, 0, wx.EXPAND |wx.ALL, 0 ) + sizer.Add( self.canvas, 1, wx.EXPAND |wx.ALL, 0) + sizer.Add( self.settingbar, 0, wx.EXPAND |wx.ALL, 0 ) + + self.SetSizer( sizer ) + self.Layout() + self.Centre( wx.BOTH ) + + self.view_x = lambda e: self.canvas.set_camera(azimuth=0, elevation=0) + self.view_y = lambda e: self.canvas.set_camera(azimuth=90, elevation=0) + self.view_z = lambda e: self.canvas.set_camera(azimuth=0, elevation=90) + self.set_pers = lambda s: self.canvas.set_camera(fov=[0, 45][s]) + #self.set_background = self.canvas.set_background + #self.set_scatter = self.canvas.set_scatter + #self.set_bright = self.canvas.set_bright + + self.on_bgcolor = lambda e: self.canvas.scene3d.set_style(bg_color=tuple(np.array(e.GetColour()[:3])/255)) + self.on_bg = lambda e: self.canvas.scene3d.set_style(ambient_color=((3-e.GetSelection())/3,)*3+(1,)) + self.on_light = lambda e: self.canvas.scene3d.set_style(light_color=((3-e.GetSelection())/3,)*3+(1,)) + self.on_shiness = lambda e: self.curobj.set_data(shiness=(3-e.GetSelection())*20) + + self.btn_x.Bind( wx.EVT_BUTTON, self.view_x) + self.btn_y.Bind( wx.EVT_BUTTON, self.view_y) + self.btn_z.Bind( wx.EVT_BUTTON, self.view_z) + #self.btn_open.Bind( wx.EVT_BUTTON, self.on_open) + #self.btn_stl.Bind( wx.EVT_BUTTON, self.on_stl) + self.btn_pers.Bind( wx.EVT_BUTTON, lambda evt, f=self.set_pers:f(True)) + self.btn_orth.Bind( wx.EVT_BUTTON, lambda evt, f=self.set_pers:f(False)) + self.btn_color.Bind( wx.EVT_COLOURPICKER_CHANGED, self.on_bgcolor ) + + self.cho_obj.Bind( wx.EVT_CHOICE, self.on_select ) + self.cho_mode.Bind( wx.EVT_CHOICE, self.on_mode ) + self.cho_light.Bind( wx.EVT_CHOICE, self.on_light ) + self.cho_hlight.Bind( wx.EVT_CHOICE, self.on_shiness ) + self.cho_bg.Bind( wx.EVT_CHOICE, self.on_bg ) + self.chk_visible.Bind( wx.EVT_CHECKBOX, self.on_visible) + self.sli_alpha.Bind( wx.EVT_SCROLL, self.on_alpha ) + self.col_color.Bind( wx.EVT_COLOURPICKER_CHANGED, self.on_color ) + + self.spn_dirv.Bind( wx.EVT_SPIN, self.on_dirv ) + self.spn_dirh.Bind( wx.EVT_SPIN, self.on_dirh ) + + self.Bind(wx.EVT_IDLE, self.on_idle) + + self.cho_obj.Set(list(self.canvas.scene3d.names)) + + def on_idle(self, event): + if set(self.canvas.scene3d.names) != set(self.cho_obj.Items): + self.cho_obj.Set(list(self.canvas.scene3d.names)) + self.cho_obj.SetSelection(0) + self.on_select(0) + + @property + def name(self): return self.canvas.scene3d.name + + def set_mesh(self, mesh): + self.canvas.set_mesh(mesh) + self.cho_obj.Set(list(mesh.objs.keys())) + + @property + def scene3d(self): return self.canvas.scene3d + + def light_dir_move(self, dx, dy): + from math import sin, cos, asin, sqrt, pi + lx, ly, lz = self.scene3d.light_dir + ay = asin(lz/sqrt(lx**2+ly**2+lz**2))-dy + xx = cos(dx)*lx - sin(dx)*ly + yy = sin(dx)*lx + cos(dx)*ly + ay = max(min(pi/2-1e-4, ay), -pi/2+1e-4) + zz, k = sin(ay), cos(ay)/sqrt(lx**2+ly**2) + self.scene3d.set_style(light_dir = (xx*k, yy*k, zz)) + + def on_dirv(self, evt): + self.light_dir_move(0, 5/180*math.pi*evt.GetInt()) + self.spn_dirv.SetValue(0) + + def on_dirh(self, evt): + self.light_dir_move(5/180*math.pi*evt.GetInt(), 0) + self.spn_dirh.SetValue(0) + + def on_save(self, evt): + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + filt = 'PNG files (*.png)|*.png' + dialog = wx.FileDialog(self, 'Save Picture', '', '', filt, wx.FD_SAVE) + if dialog.ShowModal() == wx.ID_OK: + path = dialog.GetPath() + self.canvas.save_bitmap(path) + dialog.Destroy() + + def on_stl(self, evt): + filt = 'STL files (*.stl)|*.stl' + dialog = wx.FileDialog(self, 'Save STL', '', '', filt, wx.FD_SAVE) + rst = dialog.ShowModal() + if rst == wx.ID_OK: + path = dialog.GetPath() + self.canvas.save_stl(path) + dialog.Destroy() + + def on_open(self, evt): + from stl import mesh + filt = 'STL files (*.stl)|*.stl' + dialog = wx.FileDialog(self, 'Open STL', '', '', filt, wx.FD_OPEN) + rst = dialog.ShowModal() + if rst == wx.ID_OK: + path = dialog.GetPath() + cube = mesh.Mesh.from_file(path) + verts = cube.vectors.reshape((-1,3)).astype(np.float32) + ids = np.arange(len(verts), dtype=np.uint32).reshape((-1,3)) + norms = count_ns(verts, ids) + fp, fn = osp.split(path) + fn, fe = osp.splitext(fn) + self.add_surf_asyn(fn, verts, ids, norms, (1,1,1)) + dialog.Destroy() + + def get_obj(self, name): + return self.canvas.scene3d.get_obj(name) + + def set_style(self, name, **key): + self.get_obj(name).set_style(**key) + self.canvas.Refresh() + + def on_visible(self, evt): + self.curobj.set_data(visible=evt.IsChecked()) + # self.canvas.Refresh(False) + + def on_alpha(self, evt): + self.curobj.set_data(alpha=evt.GetInt()/10.0) + # self.canvas.Refresh(False) + + def on_mode(self, evt): + self.curobj.set_data(mode=evt.GetString()) + # self.canvas.Refresh(False) + + def on_color(self, evt): + c = tuple(np.array(evt.GetColour()[:3])/255) + self.curobj.set_data(colors = c) + # self.canvas.Refresh(False) + + def on_select(self, evt): + n = self.cho_obj.GetSelection() + self.curobj = self.get_obj(self.cho_obj.GetString(n)) + + ''' + self.chk_visible.SetValue(self.curobj.visible) + color = (np.array(self.curobj.color)*255).astype(np.uint8) + self.col_color.SetColour((tuple(color))) + self.sli_alpha.SetValue(int(self.curobj.alpha*10)) + self.cho_mode.SetSelection(['mesh', 'grid'].index(self.curobj.mode)) + ''' + + def add_surf_asyn(self, name, obj): + self.canvas.add_surf_asyn(name, obj) + + def add_obj(self, name, obj): + self.canvas.scene3d.add_obj(name, obj) + + def close(self): + self.canvas.close() + self.canvas = None +''' +from mesh import Mesh +import numpy as np + +colors = np.random.rand(4,4); colors[:,3] = 1 + +slz = Mesh(verts=np.array([(0,0,0),(1,1,0),(1,0,1),(0,1,1)], dtype='float32'), + faces=np.array([(0,1,2),(0,2,3),(0,1,3),(1,2,3)], dtype='int32'), + colors = colors, mode='mesh') + +from geom import create_sphere +verts, faces = create_sphere(16, 16, 16) + +ball1 = Mesh(verts=verts, faces=faces, colors=verts, mode='mesh', alpha=1) +ball2 = Mesh(verts=verts+(1,0,0), faces=faces, colors=(1,1,1), mode='mesh', alpha=1) + +class MdCanvas(wx.Frame): + def __init__(self, size=(800, 600), title='wx MdCanvas'): + wx.Frame.__init__(self, None, -1, title, wx.DefaultPosition, size=size) + canvas = MCanvas3D(self) + canvas.add_obj('ball1', ball1) + canvas.add_obj('ball2', ball2) +''' + +if __name__ == '__main__': + myapp = wx.App(0) + frame = MdCanvas() + frame.Show(True) + myapp.MainLoop() \ No newline at end of file diff --git a/sciwx/mesh/widget.py b/sciwx/mesh/widget.py new file mode 100644 index 00000000..62cfa5a2 --- /dev/null +++ b/sciwx/mesh/widget.py @@ -0,0 +1,115 @@ +import wx, wx.lib.agw.aui as aui +from .mcanvas import MCanvas3D + +class Canvas3DFrame(wx.Frame): + def __init__(self, parent=None, scene=None): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'Canvas3DFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.canvas = MCanvas3D(self, scene) + sizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0) + self.SetSizer(sizer) + self.Bind(wx.EVT_IDLE, self.on_idle) + self.add_obj = self.canvas.add_obj + ''' + self.view_x = self.canvas.view_x + self.view_y = self.canvas.view_y + self.view_z = self.canvas.view_z + self.on_pers = self.canvas.set_pers + self.set_background = self.canvas.set_background + self.set_scatter = self.canvas.set_scatter + self.set_bright = self.canvas.set_bright + self.add_surf_asyn = self.canvas.add_surf_asyn + self.add_surf = self.canvas.add_surf + self.set_mesh = self.canvas.set_mesh + ''' + + def on_idle(self, event): + if self.GetTitle()!=self.canvas.scene3d.name: + self.SetTitle(self.canvas.scene3d.name) + + def set_title(self, tab): self.SetTitle(tab.title) + + def on_valid(self, event): event.Skip() + + def on_close(self, event): event.Skip() + +class Canvas3DNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).scene3d.name + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def canvas(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtabtProvider(img)) + + def add_canvas(self, scene=None): + canvas = MCanvas3D(self, scene) + self.AddPage(canvas, 'Mesh', True, wx.NullBitmap ) + return canvas + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): + self.GetCurrentPage().close() + import gc; gc.collect() + +class Canvas3DNoteFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'CanvasNoteFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = Canvas3DNoteBook(self) + self.canvas = self.notebook.canvas + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_canvas = self.notebook.add_canvas + self.Layout() + + +if __name__=='__main__': + from skimage.data import camera, astronaut + from skimage.io import imread + + df = pd.DataFrame(np.arange(100).reshape((20,5))) + app = wx.App() + cf = GridFrame(None) + cf.set_data(df) + cf.Show() + app.MainLoop() + + ''' + app = wx.App() + cnf = CanvasNoteFrame(None) + canvas = cnf.add_img() + canvas.set_img(camera()) + + canvas = cnf.add_img() + canvas.set_img(camera()) + canvas.set_cn(0) + + cnf.Show() + app.MainLoop() + ''' diff --git a/sciwx/plot/__init__.py b/sciwx/plot/__init__.py new file mode 100644 index 00000000..ac75c242 --- /dev/null +++ b/sciwx/plot/__init__.py @@ -0,0 +1 @@ +from .plot import PlotCanvas, PlotFrame, PlotNoteBook, PlotNoteFrame diff --git a/sciwx/plot/plot.py b/sciwx/plot/plot.py new file mode 100644 index 00000000..b8745ec5 --- /dev/null +++ b/sciwx/plot/plot.py @@ -0,0 +1,112 @@ +from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas +from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar +from matplotlib.figure import Figure + +import wx, wx.lib.agw.aui as aui +import numpy as np + +class PlotCanvas(FigureCanvas): + def __init__(self, parent, id=-1, fig=None, title='Plot'): + self.figure = fig or Figure() + self.title = title + FigureCanvas.__init__(self, parent, id, self.figure) + + def add_subplot(self, n=111, **key): + return self.figure.add_subplot(n, **key) + +class PlotFrame(wx.Frame): + def __init__(self, parent, toolbar=True): + wx.Frame.__init__(self, parent, -1, + 'CanvasFrame', size=(550, 350)) + self.figure = PlotCanvas(self) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.sizer.Add(self.figure, 1, wx.LEFT | wx.TOP | wx.EXPAND) + self.SetSizer(self.sizer) + self.Fit() + self.add_subplot = self.figure.add_subplot + if toolbar: self.add_toolbar() + self.Bind(wx.EVT_IDLE, self.on_idle) + + def on_idle(self, event): + if self.GetTitle()!=self.figure.title: + self.SetTitle(self.figure.title) + + def add_toolbar(self): + self.toolbar = NavigationToolbar(self.figure) + self.toolbar.Realize() + self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND) + self.toolbar.update() + +class PlotNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).title + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def figure(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_figure(self, figure=None): + if figure is None: figure = PlotCanvas(self) + self.AddPage(figure, 'Figure', True, wx.NullBitmap ) + return figure + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + + def mpl_connect(self, evt, method): + if self.figure() is None: return + self.figure().mpl_connect( + 'motion_notify_event', self.mouse_move) + +class PlotNoteFrame(wx.Frame): + def __init__(self, parent, toolbar=True): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = 'CanvasNoteFrame', + pos = wx.DefaultPosition, + size = wx.Size( 800, 600 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = PlotNoteBook(self) + self.figure = self.notebook.figure + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_figure = self.notebook.add_figure + #if toolbar: self.add_toolbar() + self.Layout() + + def add_toolbar(self): + self.toolbar = NavigationToolbar(self.notebook) + self.toolbar.Realize() + self.GetSizer().Add(self.toolbar, 0, wx.LEFT | wx.EXPAND) + self.toolbar.update() + +if __name__ == '__main__': + app = wx.App() + pframe = PlotFrame(None) + ax = pframe.add_subplot() + x = np.linspace(0,10,100) + y = np.sin(x) + ax.plot(x, y) + ax.grid() + ax.set_title('abc') + pframe.Show() + app.MainLoop() + diff --git a/sciwx/plt.py b/sciwx/plt.py new file mode 100644 index 00000000..627ec0d2 --- /dev/null +++ b/sciwx/plt.py @@ -0,0 +1,77 @@ +import wx +from .canvas import CanvasFrame +from .grid import GridFrame +from .text import MDFrame, TextFrame +from .plot import PlotFrame +from .mesh import Canvas3DFrame +from .widgets import ParaDialog +from sciapp.util.surfutil import * + +app = None + +def new_app(): + global app + if app is None: app = wx.App() + +def imshow(img, cn=0, log=False, autofit=True): + new_app() + cf = CanvasFrame(None, autofit) + cf.set_img(img) + cf.set_cn(cn) + cf.set_log(log) + cf.Show() + return cf + +def imstackshow(imgs, cn=0, log=False, autofit=True): + new_app() + cf = CanvasFrame(None, autofit) + cf.set_imgs(imgs) + cf.set_cn(cn) + cf.set_log(log) + cf.Show() + return cf + +def tabshow(tab): + new_app() + gf = GridFrame(None) + gf.set_data(tab) + gf.Show() + return gf + +def meshshow(): + new_app() + cf = Canvas3DFrame(None) + cf.Show() + return cf + +def txtshow(txt): + new_app() + tf = TextFrame(None) + tf.append(txt) + tf.Show() + return tf + +def mdshow(txt): + new_app() + new_app() + mf = MDFrame(None) + mf.set_cont(txt) + mf.Show() + return mf + +def figure(): + new_app() + pf = PlotFrame(None) + pf.Show() + return pf + +def parashow(para, view, modal=True): + new_app() + pd = ParaDialog(None, 'Test') + pd.init_view(view, para, preview=True, modal=modal) + pd.pack() + if modal: pd.ShowModal() + else: pd.Show() + return para + +def show(): app.MainLoop() diff --git a/sciwx/plugins/__init__.py b/sciwx/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sciwx/plugins/channels.py b/sciwx/plugins/channels.py new file mode 100644 index 00000000..3149265b --- /dev/null +++ b/sciwx/plugins/channels.py @@ -0,0 +1,304 @@ +from ..widgets import HistPanel, CMapPanel, FloatSlider +import numpy as np +import wx + +class Channels( wx.Panel ): + title = 'Channels RGB' + def __init__( self, parent , app): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 255,0 ), style = wx.TAB_TRAVERSAL ) + self.app = app + + bSizer1 = wx.BoxSizer( wx.VERTICAL ) + + sizer_chans = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_r = wx.Button( self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_r.SetBackgroundColour( wx.Colour( 255, 0, 0 ) ) + self.btn_r.SetMaxSize( wx.Size( -1,40 ) ) + sizer_chans.Add( self.btn_r, 0, wx.ALL|wx.CENTER, 0) + + com_rChoices = [ u"C:0" ] + self.com_r = wx.ComboBox( self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, com_rChoices, wx.CB_READONLY ) + self.com_r.SetSelection( 0 ) + self.com_r.SetInitialSize((50,-1)) + sizer_chans.Add( self.com_r, 1, wx.ALL|wx.EXPAND, 1) + + self.btn_g = wx.Button( self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_g.SetBackgroundColour( wx.Colour( 0, 255, 0 ) ) + self.btn_g.SetMaxSize( wx.Size( -1,40 ) ) + + sizer_chans.Add( self.btn_g, 0, wx.ALL|wx.CENTER, 0) + + com_gChoices = [ u"C:1" ] + self.com_g = wx.ComboBox( self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, com_gChoices, wx.CB_READONLY ) + self.com_g.SetSelection( 0 ) + self.com_g.SetInitialSize((50,-1)) + sizer_chans.Add( self.com_g, 1, wx.ALL|wx.EXPAND, 1) + + self.btn_b = wx.Button( self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_b.SetBackgroundColour( wx.Colour( 0, 0, 255 ) ) + self.btn_b.SetMaxSize( wx.Size( -1,40 ) ) + sizer_chans.Add( self.btn_b, 0, wx.ALL|wx.CENTER, 0) + + com_bChoices = [ u"C:2" ] + self.com_b = wx.ComboBox( self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, (-1,-1), com_bChoices, wx.CB_READONLY ) + self.com_b.SetSelection( 0 ) + self.com_b.SetInitialSize((50,-1)) + sizer_chans.Add( self.com_b, 1, wx.ALL|wx.EXPAND, 1 ) + + self.btn_gray = wx.Button( self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_gray.SetBackgroundColour( wx.Colour( 128, 128, 128 ) ) + self.btn_gray.SetMaxSize( wx.Size( -1,40 ) ) + sizer_chans.Add( self.btn_gray, 0, wx.ALL|wx.CENTER, 0) + + bSizer1.Add(sizer_chans, 0, wx.ALL|wx.EXPAND, 2) + + self.histpan = HistPanel(self) + bSizer1.Add(self.histpan, 0, wx.ALL|wx.EXPAND, 5 ) + + self.sli_high = FloatSlider(self, (0,255), 0, '') + self.sli_high.SetValue(255) + bSizer1.Add( self.sli_high, 0, wx.ALL|wx.EXPAND, 0 ) + + self.sli_low = FloatSlider(self, (0,255), 0, '') + self.sli_low.SetValue(0) + bSizer1.Add( self.sli_low, 0, wx.ALL|wx.EXPAND, 0 ) + + + bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) + self.btn_8bit = wx.Button( self, wx.ID_ANY, u"255", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) + self.btn_8bit.SetMaxSize( wx.Size( -1,40 ) ) + + bSizer2.Add( self.btn_8bit, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.btn_minmax = wx.Button( self, wx.ID_ANY, u"100", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) + self.btn_minmax.SetMaxSize( wx.Size( -1,40 ) ) + bSizer2.Add( self.btn_minmax, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.btn_90 = wx.Button( self, wx.ID_ANY, "90", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) + self.btn_90.SetMaxSize( wx.Size( -1,40 ) ) + bSizer2.Add( self.btn_90, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.btn_95 = wx.Button( self, wx.ID_ANY, "95", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) + self.btn_95.SetMaxSize( wx.Size( -1,40 ) ) + bSizer2.Add( self.btn_95, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.btn_99 = wx.Button( self, wx.ID_ANY, "99", wx.DefaultPosition, wx.Size( -1,-1 ), wx.BU_EXACTFIT ) + self.btn_99.SetMaxSize( wx.Size( -1,40 ) ) + bSizer2.Add( self.btn_99, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.chk_stack = wx.CheckBox( self, wx.ID_ANY, u"stack", wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer2.Add( self.chk_stack, 0, wx.ALIGN_CENTER|wx.ALL, 0 ) + + + bSizer1.Add( bSizer2, 0, wx.EXPAND |wx.ALL, 5 ) + + sizer_mode = wx.BoxSizer( wx.HORIZONTAL ) + + #self.btn_back = wx.Button( self, wx.ID_ANY, 'Bg', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + #self.btn_back.SetMaxSize( wx.Size( -1,40 ) ) + #self.btn_back = wx.StaticText( self, wx.ID_ANY, u" BG ", wx.DefaultPosition, wx.DefaultSize, 0|wx.SIMPLE_BORDER ) + #self.btn_back.Wrap( -1 ) + #sizer_mode.Add( self.btn_back, 0, wx.ALL|wx.EXPAND, 3) + + com_backChoices = [ u"No Background" ] + self.com_back = wx.ComboBox( self, wx.ID_ANY, u"No Background", wx.DefaultPosition, wx.DefaultSize, com_backChoices, wx.CB_READONLY) + self.com_back.SetSelection( 0 ) + sizer_mode.Add( self.com_back, 1, wx.ALL, 3 ) + + com_modeChoices = [ u"None", u"Max", u"Min", u"Mask", u"2-8mix", u"4-6mix", u"5-5mix", u"6-4mix", u"8-2mix" ] + self.com_mode = wx.ComboBox( self, wx.ID_ANY, u"Min", wx.DefaultPosition, wx.DefaultSize, com_modeChoices, wx.CB_READONLY ) + self.com_mode.SetSelection( 0 ) + sizer_mode.Add( self.com_mode, 0, wx.ALL, 3 ) + + bSizer1.Add( sizer_mode, 0, wx.EXPAND, 2 ) + #line = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + #txtlut = wx.StaticText( self, wx.ID_ANY, 'Look Up Table', wx.DefaultPosition, wx.DefaultSize) + #bSizer1.Add( line, 0, wx.EXPAND |wx.ALL, 5 ) + #bSizer1.Add( txtlut, 0, wx.EXPAND |wx.ALL, 5 ) + + self.SetSizer( bSizer1 ) + self.Layout() + + # Connect Events + self.sli_high.Bind( wx.EVT_SCROLL, self.on_low ) + self.sli_low.Bind( wx.EVT_SCROLL, self.on_high ) + self.btn_r.Bind( wx.EVT_BUTTON, lambda e: self.on_rgb(e, 'r') ) + self.btn_g.Bind( wx.EVT_BUTTON, lambda e: self.on_rgb(e, 'g') ) + self.btn_b.Bind( wx.EVT_BUTTON, lambda e: self.on_rgb(e, 'b') ) + self.btn_gray.Bind( wx.EVT_BUTTON, self.on_gray) + # self.btn_back.Bind( wx.EVT_LEFT_DOWN, self.on_back) + self.com_r.Bind( wx.EVT_COMBOBOX, lambda e: self.on_chan(e, 'r')) + self.com_g.Bind( wx.EVT_COMBOBOX, lambda e: self.on_chan(e, 'g')) + self.com_b.Bind( wx.EVT_COMBOBOX, lambda e: self.on_chan(e, 'b')) + self.com_back.Bind( wx.EVT_COMBOBOX, self.on_setback) + self.com_mode.Bind( wx.EVT_COMBOBOX, self.on_mode) + self.btn_8bit.Bind( wx.EVT_BUTTON, self.on_8bit ) + self.btn_minmax.Bind( wx.EVT_BUTTON, lambda e: self.on_p(e, 1)) + self.btn_90.Bind( wx.EVT_BUTTON, lambda e: self.on_p(e, 0.9)) + self.btn_95.Bind( wx.EVT_BUTTON, lambda e: self.on_p(e, 0.95)) + self.btn_99.Bind( wx.EVT_BUTTON, lambda e: self.on_p(e, 0.99)) + self.com_back.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.on_back) + self.active = 0 + + def on_back(self, event): + self.com_back.SetItems(['None']+self.app.img_names()) + cur = self.app.get_img_win() + if not cur is None: cur = cur.back + if not cur is None: cur = cur.title + self.com_back.SetValue(str(cur)) + modes = ['set', 'max', 'min', 'msk', 0.2, 0.4, 0.5, 0.6, 0.8] + ips = self.app.get_img() + if ips is None: self.com_mode.Select(0) + else: self.com_mode.Select(modes.index(ips.mode)) + + def on_setback(self, event): + name = self.com_back.GetValue() + if name is None: return + self.app.get_img().back = self.app.get_img(name) + self.app.get_img().update() + + def on_mode(self, event): + ips = self.app.get_img() + if ips is None: return + modes = ['set', 'max', 'min', 'msk', 0.2, 0.4, 0.5, 0.6, 0.8] + ips.mode = modes[self.com_mode.GetSelection()] + ips.update() + + # Virtual event handlers, overide them in your derived class + def on_low( self, event ): + ips = self.app.get_img() + if ips is None: return + if self.sli_low.GetValue()>self.sli_high.GetValue(): + self.sli_high.SetValue(self.sli_low.GetValue()) + rg = (self.sli_low.GetValue(), self.sli_high.GetValue()) + ips.rg[self.active] = rg + minv, maxv = self.sli_low.min, self.sli_high.max + lim1 = 1.0 * (self.sli_low.GetValue() - minv)/(maxv-minv) + lim2 = 1.0 * (self.sli_high.GetValue() - minv)/(maxv-minv) + self.histpan.set_lim(lim1*255, lim2*255) + ips.update() + + def on_high( self, event ): + ips = self.app.get_img() + if ips is None: return + if self.sli_low.GetValue()>self.sli_high.GetValue(): + self.sli_low.SetValue(self.sli_high.GetValue()) + rg = (self.sli_low.GetValue(), self.sli_high.GetValue()) + ips.rg[self.active] = rg + minv, maxv = self.sli_low.min, self.sli_high.max + lim1 = 1.0 * (self.sli_low.GetValue() - minv)/(maxv-minv) + lim2 = 1.0 * (self.sli_high.GetValue() - minv)/(maxv-minv) + self.histpan.set_lim(lim1*255, lim2*255) + ips.update() + + def on_rgb( self, event, color): + ips = self.app.get_img() + if ips is None: return + if isinstance(ips.cn, int): + ips.cn = [0, 1, 2] + chs = ['C:%d'%i for i in range(ips.channels)] + for i in (self.com_r, self.com_g, self.com_b): + chn = i.GetValue() + i.SetItems(chs) + idx = chs.index(chn) if chn in chs else 0 + i.Select(idx) + chanred = ips.cn['rgb'.index(color)] + chanrg = ips.rg[chanred] + rg = ips.get_updown('all', chanred, 512) + if (rg[0]==rg[1]): rg = (rg[0]-1e-4, rg[1]+1e-4) + slis = 'all' if self.chk_stack.GetValue() else ips.cur + hist = ips.histogram(rg, slis, chanred, 512) + self.histpan.SetValue(hist) + self.sli_low.set_para(rg, 10) + self.sli_high.set_para(rg, 10) + self.sli_low.SetValue(chanrg[0]) + self.sli_high.SetValue(chanrg[1]) + self.active = ips.cn['rgb'.index(color)] + self.range = chanrg + lim1 = (chanrg[0]-rg[0])/(rg[1]-rg[0]) + lim2 = (chanrg[1]-rg[0])/(rg[1]-rg[0]) + self.histpan.set_lim(lim1*255, lim2*255) + ips.update() + + def on_gray(self, event): + ips = self.app.get_img() + if ips is None: return + if not isinstance(ips.cn, int): ips.cn = 0 + chanrg = ips.rg[ips.cn] + rg = ips.get_updown('all', ips.cn, 512) + if (rg[0]==rg[1]): rg = (rg[0]-1e-4, rg[1]+1e-4) + slis = 'all' if self.chk_stack.GetValue() else ips.cur + hist = ips.histogram(rg, slis, ips.cn, 512) + self.histpan.SetValue(hist) + self.sli_low.set_para(rg, 10) + self.sli_high.set_para(rg, 10) + self.sli_low.SetValue(chanrg[0]) + self.sli_high.SetValue(chanrg[1]) + self.active = ips.cn + lim1 = (chanrg[0]-rg[0])/(rg[1]-rg[0]) + lim2 = (chanrg[1]-rg[0])/(rg[1]-rg[0]) + self.histpan.set_lim(lim1*255, lim2*255) + ips.update() + + def on_chan(self, event, color): + ips = self.app.get_img() + if ips is None: return + C = (self.com_r, self.com_g, self.com_b) + host = C['rgb'.index(color)] + chnidx = host.GetSelection() + ips.cn['rgb'.index(color)] = chnidx + self.on_rgb(event, color) + + def on_8bit(self, event): + ips = self.app.get_img() + if ips is None: return + rg = (0,255) + ips.rg[self.active] = rg + if (rg[0]==rg[1]): rg = (rg[0]-1e-4, rg[1]+1e-4) + slis = 'all' if self.chk_stack.GetValue() else ips.cur + hist = ips.histogram(rg, slis, self.active, 512) + self.histpan.SetValue(hist) + self.sli_low.set_para(rg, 10) + self.sli_high.set_para(rg, 10) + self.sli_low.SetValue(rg[0]) + self.sli_high.SetValue(rg[1]) + self.histpan.set_lim(0, 255) + ips.update() + + def on_p(self, event, k): + ips = self.app.get_img() + if ips is None: return + rg = ips.get_updown('all', self.active, 512) + if (rg[0]==rg[1]): rg = (rg[0]-1e-4, rg[1]+1e-4) + slis = 'all' if self.chk_stack.GetValue() else ips.cur + hist = ips.histogram(rg, slis, self.active, 512) + msk = np.abs(np.cumsum(hist)/hist.sum()-0.5)self.sli_high.GetValue(): + self.sli_low.SetValue(self.sli_high.GetValue()) + ips.range = (self.sli_low.GetValue(), self.sli_high.GetValue()) + ips.chan_rg = ips.range + lim1 = 1.0 * (self.sli_low.GetValue() - self.range[0])/(self.range[1]-self.range[0]) + lim2 = 1.0 * (self.sli_high.GetValue() - self.range[0])/(self.range[1]-self.range[0]) + self.histpan.set_lim(lim1*255, lim2*255) + ips.update() + + def on_8bit( self, event ): + ips = self.app.get_img() + if ips is None: return + self.range = ips.range = (0,255) + hist = ips.histogram(step=1024) + self.histpan.SetValue(hist) + self.sli_low.set_para((0,255), 0) + self.sli_high.set_para((0,255), 0) + self.sli_low.SetValue(0) + self.sli_high.SetValue(255) + self.histpan.set_lim(0,255) + ips.update() + + def on_minmax( self, event ): + ips = self.app.get_img() + if ips is None: return + minv, maxv = ips.get_updown()[0] + self.range = ips.range = (minv, maxv) + hist = ips.histogram(step=1024) + self.histpan.SetValue(hist) + self.sli_low.set_para(self.range, 10) + self.sli_high.set_para(self.range, 10) + self.sli_low.SetValue(minv) + self.sli_high.SetValue(maxv) + self.histpan.set_lim(0,255) + ips.update() + + def on_slice( self, event ): + ips = self.app.get_img() + if ips is None: return + hist = ips.histogram(step=1024) + self.histpan.SetValue(hist) + + def on_stack( self, event ): + ips = self.app.get_img() + if ips is None: return + hists = ips.histogram(slices='all', chans='all', step=512) + self.histpan.SetValue(hists) \ No newline at end of file diff --git a/sciwx/plugins/io.py b/sciwx/plugins/io.py new file mode 100644 index 00000000..1f0b3da6 --- /dev/null +++ b/sciwx/plugins/io.py @@ -0,0 +1,22 @@ +from sciapp.action import SciAction, ImgAction +from skimage.io import imread, imsave + +class Open(SciAction): + name = 'Open' + def start(self, app, para=None): + path = app.get_path('Open', ['png','bmp','jpg'], 'open') + if path is None: return + app.show_img(imread(path)) + +class Save(ImgAction): + name = 'Save' + para = {'path':''} + + def show(self): + path = self.app.get_path('Open', ['png','bmp','jpg'], 'save') + if path is None: return + self.para['path'] = path + return True + + def run(self, ips, img, snap, para): + imsave(para['path'], img) \ No newline at end of file diff --git a/sciwx/plugins/pencil.py b/sciwx/plugins/pencil.py new file mode 100644 index 00000000..f9134d50 --- /dev/null +++ b/sciwx/plugins/pencil.py @@ -0,0 +1,29 @@ +from sciapp.action import Tool +from skimage.draw import line + +class Pencil(Tool): + title = 'Pencil' + + def __init__(self): + self.status = False + self.oldp = (0,0) + + def mouse_down(self, ips, x, y, btn, **key): + self.status = True + self.oldp = (y, x) + ips.snapshot() + + def mouse_up(self, ips, x, y, btn, **key): + self.status = False + + def mouse_move(self, ips, x, y, btn, **key): + if not self.status:return + se = self.oldp + (y,x) + rs,cs = line(*[int(i) for i in se]) + rs.clip(0, ips.shape[1], out=rs) + cs.clip(0, ips.shape[0], out=cs) + ips.img[rs,cs] = 255 + self.oldp = (y, x) + key['canvas'].update() + + def mouse_wheel(self, ips, x, y, d, **key):pass \ No newline at end of file diff --git a/sciwx/plugins/viewport.py b/sciwx/plugins/viewport.py new file mode 100644 index 00000000..6d7c6b03 --- /dev/null +++ b/sciwx/plugins/viewport.py @@ -0,0 +1,108 @@ +from ..widgets import ViewPort as ViewPortCtrl +import wx, numpy as np + +class ViewPort ( wx.Panel ): + title = 'Navigator' + scales = [0.03125, 0.0625, 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 8, 10, 15, 20, 30, 50] + def __init__( self, parent , app): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 255,200 ), style = wx.TAB_TRAVERSAL ) + + self.app = app + bSizer1 = wx.BoxSizer( wx.VERTICAL ) + + bSizer3 = wx.BoxSizer( wx.HORIZONTAL ) + + self.viewport = ViewPortCtrl( self) + bSizer3.Add( self.viewport, 1, wx.EXPAND |wx.ALL, 5 ) + + self.slider = wx.Slider( self, wx.ID_ANY, 6, 0, len(self.scales), wx.DefaultPosition, wx.DefaultSize, wx.SL_LEFT|wx.SL_VERTICAL|wx.SL_SELRANGE|wx.SL_INVERSE ) + bSizer3.Add( self.slider, 0, wx.RIGHT|wx.EXPAND, 5 ) + + + bSizer1.Add( bSizer3, 1, wx.EXPAND, 5 ) + + bSizer2 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_apply = wx.Button( self, wx.ID_ANY, u"Apply", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_apply.SetMaxSize( wx.Size( -1,40 ) ) + + bSizer2.Add( self.btn_apply, 0, wx.ALL, 5 ) + + self.btn_fit = wx.Button( self, wx.ID_ANY, u"Fit", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_fit.SetMaxSize( wx.Size( -1,40 ) ) + + bSizer2.Add( self.btn_fit, 0, wx.ALL, 5 ) + + self.btn_one = wx.Button( self, wx.ID_ANY, u"Normal", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_one.SetMaxSize( wx.Size( -1,40 ) ) + + bSizer2.Add( self.btn_one, 0, wx.ALL, 5 ) + + + bSizer2.AddStretchSpacer(prop=1) + + self.label = wx.StaticText( self, wx.ID_ANY, u" 100.00%", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.label.Wrap( -1 ) + bSizer2.Add( self.label, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + bSizer1.Add( bSizer2, 0, wx.EXPAND, 5 ) + + self.SetSizer( bSizer1 ) + self.Layout() + + self.slider.Bind( wx.EVT_SCROLL, self.on_zoom ) + self.btn_apply.Bind( wx.EVT_BUTTON, self.on_apply ) + self.btn_fit.Bind( wx.EVT_BUTTON, self.on_fit ) + self.btn_one.Bind( wx.EVT_BUTTON, self.on_one ) + self.viewport.Bind('View_EVT', self.on_handle) + + def on_apply(self, event): + win = self.app.get_img_win() + if win is None: return + image = win.image + step = max(max(image.shape[:2])//300,1) + self.viewport.set_img(image.lookup(image.img[::step,::step]), image.shape) + self.viewport.set_box(win.canvas.conbox, win.canvas.winbox) + + def on_zoom(self, event): + self.on_apply(event) + k = self.scales[self.slider.GetValue()] + self.label.SetLabel('%.2f%%'%(k*100)) + win = self.app.get_img_win() + if win is None: return + a,b,c,d = win.canvas.winbox + win.canvas.scaidx = self.slider.GetValue() + win.canvas.zoom(k, (a+c)/2, (b+d)/2) + win.image.update() + self.viewport.set_box(win.canvas.conbox, win.canvas.winbox) + + def on_fit(self, event): + self.on_apply(event) + win = self.app.get_img_win() + if win is None: return + win.canvas.fit() + win.image.update() + self.slider.SetValue(win.canvas.scaidx) + k = self.scales[self.slider.GetValue()] + self.label.SetLabel('%.2f%%'%(k*100)) + self.viewport.set_box(win.canvas.conbox, win.canvas.winbox) + + def on_one(self, event): + self.on_apply(event) + win = self.app.get_img_win() + if win is None: return + a,b,c,d = win.canvas.winbox + win.canvas.scaidx = self.scales.index(1) + win.canvas.zoom(1, (a+c)/2, (b+d)/2) + win.image.update() + self.slider.SetValue(win.canvas.scaidx) + self.label.SetLabel('%.2f%%'%100) + self.viewport.set_box(win.canvas.conbox, win.canvas.winbox) + + def on_handle(self, loc, update=False): + if update: self.on_apply(update) + win = self.app.get_img_win() + if win is None: return + win.canvas.center(*loc, 'data') + win.image.update() + self.viewport.set_box(win.canvas.conbox, win.canvas.winbox) \ No newline at end of file diff --git a/sciwx/text/__init__.py b/sciwx/text/__init__.py new file mode 100644 index 00000000..90d079bd --- /dev/null +++ b/sciwx/text/__init__.py @@ -0,0 +1,3 @@ +from .mdutil import md2html +from .mdpad import MDPad, MDFrame, MDNoteBook, MDNoteFrame +from .textpad import TextPad, TextFrame, TextNoteBook, TextNoteFrame \ No newline at end of file diff --git a/imagepy/ui/mkdownwindow.py b/sciwx/text/markdown.css similarity index 59% rename from imagepy/ui/mkdownwindow.py rename to sciwx/text/markdown.css index 6642d6ec..ccf5c08f 100644 --- a/imagepy/ui/mkdownwindow.py +++ b/sciwx/text/markdown.css @@ -1,18 +1,3 @@ -import wx, wx.html2 as webview -from markdown import markdown -from imagepy import IPy, root_dir -import os - -def md2html(mdstr): - exts = ['markdown.extensions.extra', 'markdown.extensions.codehilite','markdown.extensions.tables','markdown.extensions.toc'] - - html = ''' - - - - - - - - - -%s - - - ''' - - ret = markdown(mdstr,extensions=exts) - f = open('yn.html', 'w', encoding='utf-8') - f.write(html % ret); - f.close() - return html % ret - -class HtmlPanel(wx.Panel): - def __init__(self, parent, cont, url): - wx.Panel.__init__(self, parent) - self.frame = self.GetTopLevelParent() - self.titleBase = self.frame.GetTitle() - - sizer = wx.BoxSizer(wx.VERTICAL) - self.wv = webview.WebView.New(self) - self.Bind(webview.EVT_WEBVIEW_TITLE_CHANGED, self.OnWebViewTitleChanged, self.wv) - - sizer.Add(self.wv, 1, wx.EXPAND) - self.SetSizer(sizer) - self.wv.SetPage(cont, url) - - def OnWebViewTitleChanged(self, evt): - if evt.GetString() == 'about:blank': return - if evt.GetString() == 'http:///': return - self.frame.SetTitle("%s -- %s" % (self.titleBase, evt.GetString())) - -class MkDownWindow(wx.Frame): - def __init__(self, parent, title, cont, url): - wx.Frame.__init__ (self, parent, id = wx.ID_ANY, title = title, size = wx.Size(500,500)) - logopath = os.path.join(root_dir, 'data/logo.ico') - self.SetIcon(wx.Icon(logopath, wx.BITMAP_TYPE_ICO)) - HtmlPanel(self, md2html(cont), url) \ No newline at end of file +} \ No newline at end of file diff --git a/sciwx/text/mdpad.py b/sciwx/text/mdpad.py new file mode 100644 index 00000000..b0045d52 --- /dev/null +++ b/sciwx/text/mdpad.py @@ -0,0 +1,125 @@ +import wx, os, time, wx.html2 as webview +import os.path as osp +import wx.lib.agw.aui as aui +from markdown import markdown +from .mdutil import md2html + +class MDPad(wx.Panel): + def __init__(self, parent, cont='', url='', home='.', title='markdown pad'): + wx.Panel.__init__(self, parent) + sizer = wx.BoxSizer(wx.VERTICAL) + self.wv = webview.WebView.New(self) + self.Bind(webview.EVT_WEBVIEW_TITLE_CHANGED, self.OnWebViewTitleChanged, self.wv) + sizer.Add(self.wv, 1, wx.EXPAND) + self.SetSizer(sizer) + self.num = 0 + self.home = home + if url != '': self.load_url(url) + elif cont!='': self.set_cont(cont, home) + self.title = title + + def set_cont(self, value): + value = value.replace('](./', '](%s/'%self.home) + # return self.wv.SetPage(md2html(value), '') + # I do not know why use SetPage the js would not run, So I write a file here + here = osp.split(osp.abspath(__file__)) + for n in range(1,10): + path = osp.join(here[0], 'index%s.htm'%n) + if not osp.exists(path): break + self.num = n + with open(path, 'w') as f: + f.write(md2html(value)) + self.load_url(path) + + def load_url(self, url): + self.wv.LoadURL(url) + + def OnWebViewTitleChanged(self, evt): + if evt.GetString() == 'about:blank': return + if evt.GetString() == 'http:///': return + here = osp.split(osp.abspath(__file__)) + path = osp.join(here[0], 'index%s.htm'%self.num) + if osp.exists(path): os.remove(path) + +class MDFrame(wx.Frame): + def __init__(self, parent, title='MarkDownFrame', cont='', url=''): + wx.Frame.__init__ (self, parent, id = wx.ID_ANY, title = title, size = wx.Size(500,500)) + cont = '\n'.join([i.strip() for i in cont.split('\n')]) + self.mdpad = MDPad(self, cont, url, title) + self.set_cont, self.load_url = self.mdpad.set_cont, self.mdpad.load_url + self.Bind( wx.EVT_IDLE, self.on_idle) + + def on_idle(self, event): + if self.GetTitle()!=self.mdpad.title: + self.SetTitle(self.mdpad.title) + +class MDNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.SetArtProvider(aui.AuiSimpleTabArt()) + self.Bind( wx.EVT_IDLE, self.on_idle) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).title + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def page(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_page(self, mdpanel=None): + if mdpanel is None: mdpanel = MDPad(self) + self.AddPage(mdpanel, 'markdown', True, wx.NullBitmap ) + return mdpanel + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + +class MDNoteFrame(wx.Frame): + def __init__(self, parent, title='MarkDownNoteFrame'): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = title, + pos = wx.DefaultPosition, + size = wx.Size( 500,500), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = MDNoteBook(self) + self.page = self.notebook.page + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_page = self.notebook.add_page + self.Layout() + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + + with open('./test.md', encoding='utf-8') as f: + cont = f.read() + mpd = MDPad(frame, home='E:/opensource/imagepy/sciwx/text') + mpd.set_cont(cont) + frame.Show() + app.MainLoop() + + ''' + app = wx.App() + mnf = MDNoteFrame(None) + mdpanel1 = mnf.add_page('markdown1') + mdpanel1.set_cont('abc') + mdpanel2 = mnf.add_page('markdown2') + mdpanel2.set_cont('def') + mnf.Show() + app.MainLoop() + ''' diff --git a/sciwx/text/mdutil.py b/sciwx/text/mdutil.py new file mode 100644 index 00000000..3ebe601a --- /dev/null +++ b/sciwx/text/mdutil.py @@ -0,0 +1,38 @@ +from markdown import markdown +import os.path as osp + +def md2html(mdstr, css=None): + exts = ['markdown.extensions.extra', 'markdown.extensions.codehilite', + 'markdown.extensions.tables','markdown.extensions.toc']#, 'mdx_math'] + html = ''' + + + + + + + + + + + + + %s + + + ''' + css = css or osp.join(osp.split(osp.abspath(__file__))[0], 'markdown.css') + return html % (css, markdown(mdstr, extensions=exts)) + +if __name__ == '__main__': + print(md2html('#abc')) diff --git a/sciwx/text/textpad.py b/sciwx/text/textpad.py new file mode 100644 index 00000000..25a89b7c --- /dev/null +++ b/sciwx/text/textpad.py @@ -0,0 +1,216 @@ +import wx, wx.lib.agw.aui as aui + +class TextPad(wx.Panel): + def __init__(self, parent, cont='', title='no name'): + wx.Panel.__init__(self, parent, size=(500,300)) + self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_3DLIGHT ) ) + self.title = title + + sizer = wx.BoxSizer( wx.VERTICAL ) + self.text= wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, + wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + self.text.SetValue(cont) + sizer.Add( self.text, 1, wx.ALL|wx.EXPAND, 1 ) + self.SetSizer( sizer ) + self.Bind(wx.EVT_RIGHT_DOWN,self.OnRClick) + self.set_cont = self.text.SetValue + + def OnOpen(self,event): + dialog=wx.FileDialog(self,'wxpython Notebook(o)',style=wx.FD_OPEN) + if dialog.ShowModal()==wx.ID_OK: + self.title=dialog.GetPath() + title=open(self.title) + self.text.write(title.read()) + title.close() + dialog.Destroy() + + def OnSave(self,event): + if self.title=='': + dialog=wx.FileDialog(self,'wxpython Notebook(s)',style=wx.FD_SAVE) + if dialog.ShowModal()==wx.ID_OK: + self.filtitlee=dialog.GetPath() + self.text.SaveFile(self.title) + dialog.Destroy() + else: + self.text.SaveFile(self.fititlele) + + def OnSaveAs(self,event): + dialog=wx.FileDialog(self,'wxpython notebook',style=wx.FD_SAVE) + if dialog.ShowModal()==wx.ID_OK: + self.title=dialog.GetPath() + self.text.SaveFile(self.title) + dialog.Destroy() + + def OnAbout(self,event): + wx.MessageBox('Text Log Window!','ImagePy',wx.OK) + + def OnRClick(self,event): + pos=(event.GetX(),event.GetY()) + self.panel.PopupMenu(self.menu.edit,pos) + + def OnUndo(self,event): self.text.Undo() + + def OnRedo(self,event): self.text.Redo() + + def OnCut(self,event): self.text.Cut() + + def OnCopy(self,event): self.text.Copy() + + def OnPaste(self,event): self.text.Paste() + + def OnSelectAll(self,event): self.text.SelectAll() + + def append(self, cont): + self.text.AppendText(cont+'\r\n') + +class TextFrame(wx.Frame): + def __init__(self, parent, title='no name', cont=''): + wx.Frame.__init__(self, parent, title=title, size=(500,300)) + self.title = title + self.textpad = TextPad(self, cont, title) + self.append = self.textpad.append + ### Create menus (name:event) k-v pairs + menus = [ + ## File + ('File(&F)',[('Open', self.textpad.OnOpen), + ('Save', self.textpad.OnSave), + ('Save as', self.textpad.OnSaveAs), + ('-'), + ('Exit', self.OnClose) + ]), + ## Edit + ('Edit(&E)', [ ('Undo', self.textpad.OnUndo), + ('Redo', self.textpad.OnRedo), + ('-'), + ('Cut', self.textpad.OnCut), + ('Copy', self.textpad.OnCopy), + ('Paste', self.textpad.OnPaste), + ('-'), + ('All', self.textpad.OnSelectAll) + ]), + ## Help + ('Help(&H)', [('About', self.textpad.OnAbout)]) + ] + + ### Bind menus with the corresponding events + self.menuBar=wx.MenuBar() + for menu in menus: + m = wx.Menu() + for item in menu[1]: + if item[0]=='-': + m.AppendSeparator() + else: + i = m.Append(-1, item[0]) + self.Bind(wx.EVT_MENU,item[1], i) + self.menuBar.Append(m,menu[0]) + self.SetMenuBar(self.menuBar) + self.Bind(wx.EVT_CLOSE, self.OnClosing) + + def OnClose(self,event): + self.Destroy() + + def OnClosing(self, event): + event.Skip() + +class TextNoteBook(wx.lib.agw.aui.AuiNotebook): + def __init__(self, parent): + wx.lib.agw.aui.AuiNotebook.__init__( self, parent, wx.ID_ANY, + wx.DefaultPosition, wx.DefaultSize, wx.lib.agw.aui.AUI_NB_DEFAULT_STYLE ) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_valid) + self.Bind( wx.lib.agw.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_close) + self.Bind( wx.EVT_IDLE, self.on_idle) + self.SetArtProvider(aui.AuiSimpleTabArt()) + + def on_idle(self, event): + for i in range(self.GetPageCount()): + title = self.GetPage(i).title + if self.GetPageText(i) != title: + self.SetPageText(i, title) + + def textpad(self, i=None): + if not i is None: return self.GetPage(i) + else: return self.GetCurrentPage() + + def set_background(self, img): + self.GetAuiManager().SetArtProvider(ImgArtProvider(img)) + + def add_page(self, textpad=None): + if textpad is None: textpad = TextPad(self) + self.AddPage(textpad, 'Text', True, wx.NullBitmap ) + return textpad + + def set_title(self, panel, title): + self.SetPageText(self.GetPageIndex(panel), title) + + def on_valid(self, event): pass + + def on_close(self, event): pass + +class TextNoteFrame(wx.Frame): + def __init__(self, parent, title='TextPadBookFrame'): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, + title = title, + pos = wx.DefaultPosition, + size = wx.Size( 500, 500 ), + style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer(wx.VERTICAL) + self.notebook = TextNoteBook(self) + self.textpad = self.notebook.textpad + sizer.Add( self.notebook, 1, wx.EXPAND |wx.ALL, 0 ) + self.SetSizer(sizer) + self.add_notepad = self.notebook.add_notepad + self.Layout() + + ### Create menus (name:event) k-v pairs + menus = [ + ## File + ('File(&F)',[('Open', lambda e: self.textpad().OnOpen(e)), + ('Save', lambda e: self.textpad().OnSave(e)), + ('Save as', lambda e: self.textpad().OnSaveAs(e)), + ('-'), + ('Exit', self.OnClose) + ]), + ## Edit + ('Edit(&E)', [ ('Undo', lambda e: self.textpad().OnUndo(e)), + ('Redo', lambda e: self.textpad().OnRedo(e)), + ('-'), + ('Cut', lambda e: self.textpad().OnCut(e)), + ('Copy', lambda e: self.textpad().OnCopy(e)), + ('Paste', lambda e: self.textpad().OnPaste(e)), + ('-'), + ('All', lambda e: self.textpad().OnSelectAll(e)) + ]), + ## Help + ('Help(&H)', [('About', lambda e: self.textpad().OnAbout(e))]) + ] + + ### Bind menus with the corresponding events + self.menuBar=wx.MenuBar() + for menu in menus: + m = wx.Menu() + for item in menu[1]: + if item[0]=='-': + m.AppendSeparator() + else: + i = m.Append(-1, item[0]) + self.Bind(wx.EVT_MENU,item[1], i) + self.menuBar.Append(m,menu[0]) + self.SetMenuBar(self.menuBar) + self.Bind(wx.EVT_CLOSE, self.OnClosing) + + def OnClose(self,event): + self.Destroy() + + def OnClosing(self, event): + event.Skip() + +if __name__ == '__main__': + app = wx.App() + npbf = TextNoteFrame(None) + note1 = npbf.add_notepad() + note1.append('abc') + note1 = npbf.add_notepad() + note1.append('def') + npbf.Show() + app.MainLoop() + diff --git a/sciwx/widgets/__init__.py b/sciwx/widgets/__init__.py new file mode 100644 index 00000000..3401e2d3 --- /dev/null +++ b/sciwx/widgets/__init__.py @@ -0,0 +1,14 @@ +from .paradialog import ParaDialog, get_para +from .cmappanel import CMapPanel +from .colormap import CMapSelPanel, CMapSelCtrl +from .curvepanel import CurvePanel +from .histpanel import HistPanel +from .progressbar import ProgressBar +from .normal import * +from .toolbar import ToolBar +from .menubar import MenuBar +from .ribbonbar import RibbonBar +from .viewport import ViewPort +from .choicebook import ChoiceBook +from .workflow import WorkFlowPanel +from . import util \ No newline at end of file diff --git a/sciwx/widgets/advanced.py b/sciwx/widgets/advanced.py new file mode 100644 index 00000000..10e08375 --- /dev/null +++ b/sciwx/widgets/advanced.py @@ -0,0 +1,28 @@ +from . normal import Choice, Choices +from . colormap import CMapSelPanel +# from ...core.manager import ImageManager, TableManager, ColorManager + +class ImageList(Choice): + def __init__(self, parent, title, unit, app=None): + Choice.__init__(self, parent, app.img_names(), str, title, unit) + +class TableList(Choice): + def __init__(self, parent, title, unit, app=None): + Choice.__init__(self, parent, app.table_names(), str, title, unit) + +class TableField(Choice): + def __init__(self, parent, title, unit, app=None): + Choice.__init__(self, parent, ['None'] + list(app.get_table().data.columns), lambda x:x, title, unit) + +class TableFields(Choices): + def __init__(self, parent, title, app=None): + self.tps = app.get_table() + Choices.__init__(self, parent, app.get_table().data.columns, title) + + def SetValue(self, value): + Choices.SetValue(self, self.tps.colmsk) + +class ColorMap(CMapSelPanel): + def __init__(self, parent, title, app=None): + CMapSelPanel.__init__(self, parent, title) + self.SetItems(ColorManager.luts) \ No newline at end of file diff --git a/sciwx/widgets/choicebook.py b/sciwx/widgets/choicebook.py new file mode 100644 index 00000000..85db3d0e --- /dev/null +++ b/sciwx/widgets/choicebook.py @@ -0,0 +1,31 @@ +import wx + +class ChoiceBook(wx.ScrolledWindow): + def __init__(self, parent, app=None): + wx.ScrolledWindow.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.HSCROLL|wx.VSCROLL ) + self.app = app or parent + self.SetSizer(wx.BoxSizer( wx.VERTICAL )) + + def add_wgts(self, name, wgts): + book = wx.Choicebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.CHB_DEFAULT ) + for name, wgt in wgts: + book.AddPage(wgt(book, self.app), name, False ) + self.GetSizer().Add( book, 0, wx.EXPAND |wx.ALL, 0 ) + self.Layout() + self.GetSizer().Fit(self) + + def load(self, data): + for name, wgts in data[1]: + self.add_wgts(name, wgts) + + def clear(self): + self.DestroyChildren() + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + book = ChoiceBook(frame) + book.load(('widgets', [('panels', [('A', wx.Panel), ('B', wx.Panel)]), + ('panels2', [('A', wx.Panel), ('B', wx.Panel)])])) + frame.Show() + app.MainLoop() diff --git a/imagepy/ui/widgets/cmappanel.py b/sciwx/widgets/cmappanel.py similarity index 88% rename from imagepy/ui/widgets/cmappanel.py rename to sciwx/widgets/cmappanel.py index 91905d32..d0aa8ff3 100644 --- a/imagepy/ui/widgets/cmappanel.py +++ b/sciwx/widgets/cmappanel.py @@ -3,8 +3,6 @@ from numpy.linalg import norm from scipy import interpolate -if sys.version_info[0]==2:memoryview=np.getbuffer - class CMapPanel(wx.Panel): """ HistCanvas: diverid from wx.core.Panel """ def __init__(self, parent ): @@ -16,7 +14,7 @@ def __init__(self, parent ): self.cmap = np.vstack([np.arange(256)]*3).T.astype(np.uint8) self.idx = -1 self.his = None - self.update = False + self.dirty = False self.pts = [(0,0,0,0), (255,255,255,255)] self.Bind(wx.EVT_SIZE, self.on_size) self.Bind(wx.EVT_IDLE, self.on_idle) @@ -28,8 +26,11 @@ def __init__(self, parent ): self.Bind( wx.EVT_LEFT_DCLICK, self.on_rdc ) self.handle = self.handle_ + def update(self): self.dirty = True + def init_buf(self): box = self.GetClientSize() + if min(box)==0: return self.buffer = wx.Bitmap(box.width, box.height) @classmethod @@ -44,12 +45,12 @@ def linear_color(cls, cs): def on_size(self, event): self.init_buf() - self.update = True + self.update() def on_idle(self, event): - if self.update == True: + if self.dirty == True: self.draw() - self.update = False + self.dirty = False def pick(self, x, y): if abs(y-10)>3:return -1 @@ -65,7 +66,7 @@ def on_ld(self, event): self.pts.append((x,)+tuple(self.cmap[x])) self.idx = len(self.pts)-1 self.cmap[:] = self.linear_color(self.pts) - self.update = True + self.update() self.handle() def on_lu(self, event): @@ -79,23 +80,23 @@ def on_rd(self, event): del self.pts[self.idx] self.idx = -1 self.cmap[:] = self.linear_color(self.pts) - self.update = True + self.update() self.handle() def on_rdc(self, event): x,y = event.GetX()-self.offset[0], event.GetY()-self.offset[1] self.idx = self.pick(x, y) if self.idx==-1:return - dlg = wx.ColourDialog(self) - dlg.GetColourData().SetChooseFull(True) - if dlg.ShowModal() == wx.ID_OK: - rst = dlg.GetColourData().GetColour() + dialog = wx.ColourDialog(self) + dialog.GetColourData().SetChooseFull(True) + if dialog.ShowModal() == wx.ID_OK: + rst = dialog.GetColourData().GetColour() x = self.pts[self.idx][0] self.pts[self.idx] = (x,)+rst[:-1] self.idx=-1 self.cmap[:] = self.linear_color(self.pts) - self.update = True - dlg.Destroy() + self.update() + dialog.Destroy() self.handle() @@ -112,7 +113,7 @@ def on_mv(self, event): cl = self.pts[self.idx][1:] self.pts[self.idx] = (x,)+cl self.cmap[:] = self.linear_color(self.pts) - self.update = True + self.update() self.handle() @@ -121,11 +122,11 @@ def on_paint(self, event): def set_hist(self, hist): self.hist = (hist*255/hist.max()).astype(np.uint8) - self.update = True + self.update() def set_pts(self, pts): self.x1, self.x2 = x1, x2 - self.update = True + self.update() def draw(self): ox, oy = self.offset @@ -143,20 +144,19 @@ def draw(self): brushes = [wx.Brush(i[1:]) for i in self.pts] dc.DrawPolygonList(polys,brushes=brushes) - def handle_(self):pass def set_handle(self, handle):self.handle = handle - def SetValue(self, value):pass + def SetValue(self, value):self.pts = value def GetValue(self): return sorted(self.pts) if __name__ == '__main__': - app = wx.PySimpleApp() + app = wx.App() frame = wx.Frame(None) hist = CMapPanel(frame) + hist.set_hist(np.random.rand(256)+2) frame.Fit() frame.Show(True) - hist.set_hist(np.random.rand(256)+2) - app.MainLoop() \ No newline at end of file + app.MainLoop() diff --git a/imagepy/ui/widgets/cmapselect.py b/sciwx/widgets/colormap.py similarity index 60% rename from imagepy/ui/widgets/cmapselect.py rename to sciwx/widgets/colormap.py index 7f6e5ca6..e7f78255 100644 --- a/imagepy/ui/widgets/cmapselect.py +++ b/sciwx/widgets/colormap.py @@ -1,26 +1,28 @@ -#!/usr/bin/env python - -import wx, sys -import wx.adv import numpy as np +import wx.adv +import sys, wx +from sciwx import ColorManager -if sys.version_info[0]==2:memoryview=np.getbuffer -class CMapSelPanel(wx.adv.OwnerDrawnComboBox): +class CMapSelCtrl(wx.adv.OwnerDrawnComboBox): def __init__(self, parent): wx.adv.OwnerDrawnComboBox.__init__(self, parent, choices=[], style=wx.CB_READONLY, pos=(20,40), size=(256, 30)) - self.handle = self.handle_ - self.Bind( wx.EVT_COMBOBOX, self.on_sel) + self.SetItems(ColorManager.gets()) - def SetItems(self, ks, vs): + def SetItems(self, kvs): self.Clear() - if 'Grays' in ks: - i = ks.index('Grays') - ks.insert(0, ks.pop(i)) - vs.insert(0, vs.pop(i)) + ks, vs = [i[0] for i in kvs], [i[1] for i in kvs] self.AppendItems(ks) self.Select(0) self.ks, self.vs = ks, vs + + def SetValue(self, x): + n = self.ks.index(x) if x in self.ks else 0 + self.SetSelection(n) + + def GetValue(self): + return self.ks[self.GetSelection()] + # Overridden from OwnerDrawnComboBox, called to draw each # item in the list def OnDrawItem(self, dc, rect, item, flags): @@ -37,8 +39,8 @@ def OnDrawItem(self, dc, rect, item, flags): # for painting the items in the popup dc.DrawText(self.GetString( item), - r.x + 3, - (r.y + 0) + ( (r.height/2) - dc.GetCharHeight() )/2 + int(r.x+0.5) + 3, + int((r.y + 0) + ((r.height/2) - dc.GetCharHeight())/2 + 0.5) ) #dc.DrawLine( r.x+5, r.y+((r.height/4)*3)+1, r.x+r.width - 5, r.y+((r.height/4)*3)+1 ) arr = np.zeros((10,256,3),dtype=np.uint8) @@ -46,7 +48,6 @@ def OnDrawItem(self, dc, rect, item, flags): bmp = wx.Bitmap.FromBuffer(256,10, memoryview(arr)) dc.DrawBitmap(bmp, r.x, r.y+15) - # Overridden from OwnerDrawnComboBox, called for drawing the # background area of each item. def OnDrawBackground(self, dc, rect, item, flags): @@ -57,51 +58,48 @@ def OnDrawBackground(self, dc, rect, item, flags): wx.adv.OwnerDrawnComboBox.OnDrawBackground(self, dc, rect, item, flags) return - def GetValue(self): - return self.ks[self.GetSelection()] - - # Overridden from OwnerDrawnComboBox, should return the height # needed to display an item in the popup, or -1 for default def OnMeasureItem(self, item): return 30 # Simply demonstrate the ability to have variable-height items - if item & 1: - return 36 - else: - return 24 + if item & 1: return 36 + else: return 24 # Overridden from OwnerDrawnComboBox. Callback for item width, or # -1 for default/undetermined def OnMeasureItemWidth(self, item): return -1; # default - will be measured from text width - def handle_(slef):return - - def set_handle(self, handle=None): - if handle is None: self.handle = self.handle_ - else: self.handle = handle +class CMapSelPanel(wx.Panel): + def __init__( self, parent, title, app=None): + wx.Panel.__init__(self, parent) + sizer = wx.BoxSizer(wx.VERTICAL) + lab_title = wx.StaticText( self, wx.ID_ANY, title, + wx.DefaultPosition, wx.DefaultSize) + lab_title.Wrap( -1 ) + sizer.Add( lab_title, 0, wx.ALL, 5 ) + self.ctrl = CMapSelCtrl(self) + sizer.Add( self.ctrl, 0, wx.ALL|wx.EXPAND, 0 ) + self.SetSizer(sizer) + self.GetValue = self.ctrl.GetValue + self.SetValue = self.ctrl.SetValue + self.SetItems = self.ctrl.SetItems + self.ctrl.Bind( wx.EVT_COMBOBOX, self.on_sel) + self.on_select = print + self.Fit() + + def Bind(self, event, f): self.on_select = f def on_sel(self, event): - print('aaa') - self.handle() + self.on_select(self.GetValue()) if __name__ == '__main__': - from glob import glob - import os - import numpy as np - filenames = glob('../../data/luts/*.lut') - keys = [os.path.split(filename)[-1][:-4] for filename in filenames] - values = [np.fromfile(filename, dtype=np.uint8).reshape((3,256)).T.copy() for filename in filenames] - lut = dict(zip(keys, values)) - - app = wx.PySimpleApp() + app = wx.App() frame = wx.Frame(None) - panel = wx.Panel(frame) - pscb = CMapSelPanel(panel, choices=[], style=wx.CB_READONLY, - pos=(20,40), size=(256, 30)) - pscb.SetItems(keys, values) - panel.Fit() + cmapsel = CMapSelPanel(frame, 'color map') + cmap = np.arange(256*3, dtype=np.uint8).reshape((3,-1)).T + cmapsel.SetItems({'gray':cmap}) frame.Fit() frame.Show(True) - app.MainLoop() \ No newline at end of file + app.MainLoop() diff --git a/imagepy/ui/widgets/curvepanel.py b/sciwx/widgets/curvepanel.py similarity index 77% rename from imagepy/ui/widgets/curvepanel.py rename to sciwx/widgets/curvepanel.py index f056cac6..40152422 100644 --- a/imagepy/ui/widgets/curvepanel.py +++ b/sciwx/widgets/curvepanel.py @@ -3,10 +3,9 @@ from numpy.linalg import norm from scipy import interpolate -if sys.version_info[0]==2:memoryview=np.getbuffer class CurvePanel(wx.Panel): """ HistCanvas: diverid from wx.core.Panel """ - def __init__(self, parent, hist=None, l=255): + def __init__(self, parent, hist=None, l=255, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(l+25, l+25), style = wx.TAB_TRAVERSAL ) @@ -15,7 +14,7 @@ def __init__(self, parent, hist=None, l=255): self.l, self.k = l, l/255.0 self.idx = -1 self.set_hist(hist) - self.update = False + self.dirty = False self.pts = [(0,0), (255, 255)] wx.Panel.Bind(self, wx.EVT_SIZE, self.on_size) wx.Panel.Bind(self, wx.EVT_IDLE, self.on_idle) @@ -25,6 +24,8 @@ def __init__(self, parent, hist=None, l=255): wx.Panel.Bind(self, wx.EVT_MOTION, self.on_mv ) wx.Panel.Bind(self, wx.EVT_RIGHT_DOWN, self.on_rd ) + def update(self): self.dirty = True + @classmethod def lookup(cls, pts): x, y = np.array(pts).T @@ -34,16 +35,17 @@ def lookup(cls, pts): def init_buf(self): box = self.GetClientSize() + if min(box)==0: return self.buffer = wx.Bitmap(box.width, box.height) def on_size(self, event): self.init_buf() - self.update = True + self.update() def on_idle(self, event): - if self.update == True: + if self.dirty == True: self.draw() - self.update = False + self.dirty = False def pick(self, x, y): dis = norm(np.array(self.pts)-(x,y), axis=1) @@ -57,8 +59,8 @@ def on_ld(self, event): if self.idx==-1: self.pts.append((x, 255-y)) self.idx = len(self.pts)-1 - self.update = True - self.handle(event) + self.update() + self.on_curve(self.GetValue()) def on_lu(self, event): self.idx = -1 @@ -71,8 +73,8 @@ def on_rd(self, event): if not self.pts[self.idx][0] in (0, 255): del self.pts[self.idx] self.idx = -1 - self.update = True - self.handle(event) + self.update() + self.on_curve(self.GetValue()) def on_mv(self, event): x = (event.GetX()-self.offset[0])/self.k @@ -87,8 +89,8 @@ def on_mv(self, event): else: x = np.clip(x, 1, 254) y = np.clip(y, 0, 255) self.pts[self.idx] = (x, 255-y) - self.update = True - self.handle(event) + self.update() + self.on_curve(self.GetValue()) def on_paint(self, event): @@ -96,12 +98,14 @@ def on_paint(self, event): def set_hist(self, hist): if hist is None:self.hist=None - else:self.hist = (hist*self.l/hist.max()).astype(np.uint8) - self.update = True + else: + self.hist = (hist*self.l/hist.max()) + self.logh = (np.log(self.hist+1.0))*(self.l/(np.log(self.l+1))) + self.update() def set_pts(self, pts): - self.x1, self.x2 = x1, x2 - self.update = True + self.x1, self.x2 = int(x1+0.5), int(x2+0.5) + self.update() def draw(self): ox, oy = self.offset @@ -111,10 +115,14 @@ def draw(self): # w, h = self.GetClientSize() # the main draw process - dc.SetPen(wx.Pen((100,100,100), width=1, style=wx.SOLID)) + if not self.hist is None: - for i in np.linspace(0,self.l,256).astype(np.int16): - dc.DrawLine(i+ox,self.l+1+oy,i+ox,self.l+1-self.hist[i]+oy) + dc.SetPen(wx.Pen((200,200,200), width=1, style=wx.SOLID)) + for i in np.linspace(0,self.l,256).astype(np.int32): + dc.DrawLine(i+ox,self.l+1+oy,i+ox, int(self.l+1-self.logh[i]+oy+0.5)) + dc.SetPen(wx.Pen((100,100,100), width=1, style=wx.SOLID)) + for i in np.linspace(0,self.l,256).astype(np.int32): + dc.DrawLine(i+ox,self.l+1+oy,i+ox,int(self.l+1-self.hist[i]+oy+0.5)) x, y = np.array(self.pts).T kind = 'linear' if len(self.pts)==2 else 'quadratic' f = interpolate.interp1d(x, y, kind=kind) @@ -125,10 +133,10 @@ def draw(self): for i in self.pts: dc.DrawCircle(round(i[0]*self.k+ox), round(self.l+1-i[1]*self.k+oy), 2) xs, ys = np.linspace(0,self.l, self.l+1)+ox, self.l+1-ys*self.k+oy - dc.DrawPointList(list(zip(xs.round(), ys.round()))) + dc.DrawPointList(list(zip(xs.astype(np.int32), ys.astype(np.int32)))) dc.SetPen(wx.Pen((0,0,0), width=1, style=wx.SOLID)) - for i in np.linspace(0, self.l+1, 5): + for i in np.linspace(0, self.l+1, 5).round().astype(np.int32): dc.DrawLine(0+ox, i+oy, self.l+1+ox, i+oy) dc.DrawLine(i+ox, 0+oy, i+ox, self.l+1+oy) dc.SetBrush(wx.Brush((0,0,0), wx.BRUSHSTYLE_TRANSPARENT)) @@ -142,24 +150,24 @@ def draw(self): dc.DrawBitmap(bmp, -15+ox, 0+oy) dc.DrawRectangle(-15+ox, 0+oy, 10, self.l+1) - def handle(self):pass + def on_curve(self, event): print(event) - def Bind(self, z, handle):self.handle = handle + def Bind(self, event, f):self.on_curve = f def SetValue(self, value=None): print('here', value) if not value is None: self.pts = value else: self.pts = [(0,0), (255, 255)] - self.update = True + self.update() def GetValue(self): return sorted(self.pts) if __name__ == '__main__': - app = wx.PySimpleApp() + app = wx.App() frame = wx.Frame(None) hist = CurvePanel(frame, l=255) + hist.set_hist(np.random.rand(256)+2) frame.Fit() frame.Show(True) - hist.set_hist(np.random.rand(256)+2) - app.MainLoop() \ No newline at end of file + app.MainLoop() diff --git a/sciwx/widgets/histpanel.py b/sciwx/widgets/histpanel.py new file mode 100644 index 00000000..650b573c --- /dev/null +++ b/sciwx/widgets/histpanel.py @@ -0,0 +1,76 @@ +import wx +import numpy as np + +class HistPanel(wx.Panel): + """ HistCanvas: diverid from wx.core.Panel """ + def __init__(self, parent, hist=None, size=(256, 80), app=None): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, + pos = wx.DefaultPosition, size = (size[0], size[1]+1), + style = wx.TAB_TRAVERSAL ) + self.init_buf() + self.hist = None + self.w, self.h = size + if not hist is None: self.SetValue(hist) + self.dirty = False + self.x1, self.x2 = 0, self.w-1 + self.Bind(wx.EVT_SIZE, self.on_size) + self.Bind(wx.EVT_IDLE, self.on_idle) + self.Bind(wx.EVT_PAINT, self.on_paint) + # self.Bind = lambda z, x:0 + + def update(self): self.dirty = True + + def init_buf(self): + box = self.GetClientSize() + if min(box)==0: return + self.buffer = wx.Bitmap(box.width, box.height) + + def on_size(self, event): + self.init_buf() + self.update() + + def on_idle(self, event): + if self.dirty == True: + self.draw() + self.dirty = False + + def on_paint(self, event): + wx.BufferedPaintDC(self, self.buffer) + + def SetValue(self, hist): + self.hist = (hist*self.h/hist.max()) + self.logh = (np.log(self.hist+1.0))*(self.h/(np.log(self.h+1))) + self.update() + + def set_lim(self, x1, x2): + self.x1, self.x2 = x1, x2 + self.update() + + def draw(self): + # get client device context buffer + dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) + dc.Clear() + # w, h = self.GetClientSize() + + # the main draw process + + if not self.hist is None: + hist = self.hist[np.linspace(0, len(self.hist)-1, self.w, dtype=np.int16)] + dc.SetPen(wx.Pen((200,200,200), width=1, style=wx.SOLID)) + for i in range(self.w): + dc.DrawLine(i,self.h,i,self.h-int(self.logh[i]+0.5)) + dc.SetPen(wx.Pen((100,100,100), width=1, style=wx.SOLID)) + for i in range(self.w): + dc.DrawLine(i,self.h,i,self.h-int(self.hist[i]+0.5)) + dc.SetPen(wx.Pen((0,0,0), width=1, style=wx.SOLID)) + dc.DrawLine(int(self.x1+0.5), self.h, int(self.x2+0.5), 0) + dc.DrawLines([(0,0),(self.w-1,0),(self.w-1,self.h),(0,self.h),(0,0)]) + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + hist = HistPanel(frame) + hist.SetValue(np.random.rand(256)) + frame.Fit() + frame.Show(True) + app.MainLoop() diff --git a/sciwx/widgets/menubar.py b/sciwx/widgets/menubar.py new file mode 100644 index 00000000..fd7b29ab --- /dev/null +++ b/sciwx/widgets/menubar.py @@ -0,0 +1,83 @@ +import wx + +def hot_key(txt): + sep = txt.split('-') + acc, code = wx.ACCEL_NORMAL, -1 + if 'Ctrl' in sep: acc|= wx.ACCEL_CTRL + if 'Alt' in sep: acc|= wx.ACCEL_ALT + if 'Shift' in sep: acc|= wx.ACCEL_SHIFT + fs = ['F%d'%i for i in range(1,13)] + if sep[-1] in fs: + code = 340+fs.index(sep[-1]) + elif len(sep[-1])==1: code = ord(sep[-1]) + return acc, code + +class MenuBar(wx.MenuBar): + def __init__(self, app): + wx.MenuBar.__init__(self) + self.app = app + app.SetMenuBar(self) + + def parse(self, ks, vs, pt, short, rst): + if isinstance(vs, list): + menu = wx.Menu() + for kv in vs: + if kv == '-': menu.AppendSeparator() + else: self.parse(*kv, menu, short, rst) + pt.Append(1, ks, menu) + else: + item = wx.MenuItem(pt, -1, ks) + if ks in short: + rst.append((short[ks], item.GetId())) + f = lambda e, p=vs: p().start(self.app) + self.Bind(wx.EVT_MENU, f, item) + pt.Append(item) + + def Append(self, id, item, menu): + wx.MenuBar.Append(self, menu, item) + + def load(self, data, shortcut={}): + rst = [] + for k,v in data[1]: + self.parse(k, v, self, shortcut, rst) + rst = [(*hot_key(i[0]), i[1]) for i in rst] + return wx.AcceleratorTable(rst) + + def on_menu(self, event): print('here') + + def clear(self): + while self.GetMenuCount()>0: self.Remove(0) + + +if __name__ == '__main__': + class P: + def __init__(self, name): + self.name = name + + def start(self, app): + print(self.name) + + def __call__(self): + return self + + data = ('menu', [ + ('File', [ + ('Open', P('O')), + '-', + ('Close', P('C'))]), + ('Edit', [ + ('Copy', P('C')), + ('A', [ + ('B', P('B')), + ('C', P('C'))]), + ('Paste', P('P'))])]) + + app = wx.App() + frame = wx.Frame(None) + menubar = MenuBar(frame) + acc = menubar.load(data, {'Open':'Ctrl-O'}) + frame.SetMenuBar(menubar) + menubar.SetAcceleratorTable(acc) + frame.Show() + app.MainLoop() + diff --git a/imagepy/ui/widgets/normal.py b/sciwx/widgets/normal.py similarity index 75% rename from imagepy/ui/widgets/normal.py rename to sciwx/widgets/normal.py index 2a3ce898..170597cd 100644 --- a/imagepy/ui/widgets/normal.py +++ b/sciwx/widgets/normal.py @@ -3,11 +3,11 @@ class NumCtrl(wx.Panel): """NumCtrl: diverid from wx.core.TextCtrl """ - def __init__(self, parent, rang, accury, title, unit): + def __init__(self, parent, rang, accury, title, unit, app=None): wx.Panel.__init__(self, parent) sizer = wx.BoxSizer( wx.HORIZONTAL ) self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -16,7 +16,7 @@ def __init__(self, parent, rang, accury, title, unit): sizer.Add( self.ctrl, 2, wx.ALL, 5 ) self.postfix = lab_unit = wx.StaticText( self, wx.ID_ANY, unit, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_unit.Wrap( -1 ) sizer.Add( lab_unit, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -30,7 +30,7 @@ def __init__(self, parent, rang, accury, title, unit): def Bind(self, z, f):self.f = f def ontext(self, event): - self.f(event) + self.f(self) if self.GetValue()==None: self.ctrl.SetBackgroundColour((255,255,0)) else: @@ -53,12 +53,11 @@ def GetValue(self): return num class TextCtrl(wx.Panel): - """NumCtrl: diverid from wx.core.TextCtrl """ - def __init__(self, parent, title, unit): + def __init__(self, parent, title, unit, app=None): wx.Panel.__init__(self, parent) sizer = wx.BoxSizer( wx.HORIZONTAL ) self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -66,7 +65,7 @@ def __init__(self, parent, title, unit): sizer.Add( self.ctrl, 2, wx.ALL, 5 ) self.postfix = lab_unit = wx.StaticText( self, wx.ID_ANY, unit, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_unit.Wrap( -1 ) sizer.Add( lab_unit, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -78,7 +77,7 @@ def __init__(self, parent, title, unit): def Bind(self, z, f):self.f = f def ontext(self, event): - self.f(event) + self.f(self) def SetValue(self, n): self.ctrl.SetValue(n) @@ -87,41 +86,45 @@ def GetValue(self): return self.ctrl.GetValue() class ColorCtrl(wx.Panel): - """ColorCtrl: deverid fron wx.coreTextCtrl""" - def __init__(self, parent, title, unit): + def __init__(self, parent, title, unit, app=None): wx.Panel.__init__(self, parent) sizer = wx.BoxSizer( wx.HORIZONTAL ) self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) self.ctrl = wx.TextCtrl(self, wx.TE_RIGHT) sizer.Add( self.ctrl, 2, wx.ALL, 5 ) self.postfix = lab_unit = wx.StaticText( self, wx.ID_ANY, unit, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_unit.Wrap( -1 ) sizer.Add( lab_unit, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) self.SetSizer(sizer) self.ctrl.Bind(wx.EVT_KEY_UP, self.ontext) - self.ctrl.Bind( wx.EVT_LEFT_DOWN, self.oncolor) + self.ctrl.Bind( wx.EVT_LEFT_DCLICK, self.oncolor) def Bind(self, z, f): self.f = f def ontext(self, event): - print('ColorCtrl') + self.f(self) + if self.GetValue()==None: + self.ctrl.SetBackgroundColour((255,255,255)) + else: + self.ctrl.SetBackgroundColour(self.GetValue()) + self.Refresh() def oncolor(self, event): rst = None - dlg = wx.ColourDialog(self) - dlg.GetColourData().SetChooseFull(True) - if dlg.ShowModal() == wx.ID_OK: - rst = dlg.GetColourData().GetColour() + dialog = wx.ColourDialog(self) + dialog.GetColourData().SetChooseFull(True) + if dialog.ShowModal() == wx.ID_OK: + rst = dialog.GetColourData().GetColour() self.ctrl.SetBackgroundColour(rst) self.ctrl.SetValue(rst.GetAsString(wx.C2S_HTML_SYNTAX)) - self.f(event) - dlg.Destroy() + self.f(self) + dialog.Destroy() def SetValue(self, color): self.ctrl.SetBackgroundColour(color) @@ -129,16 +132,61 @@ def SetValue(self, color): self.ctrl.SetValue(des) def GetValue(self): - return self.ctrl.GetBackgroundColour().Get(False) + rgb = self.ctrl.GetValue() + if len(rgb)!=7 or rgb[0]!='#': return None + try: rgb = int(rgb[1:], 16) + except: return None + return wx.Colour(rgb).Get(False)[::-1] + +class PathCtrl(wx.Panel): + def __init__(self, parent, filt, io, title, app=None): + wx.Panel.__init__(self, parent) + sizer = wx.BoxSizer( wx.HORIZONTAL ) + self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, + wx.DefaultPosition, wx.DefaultSize) + self.filt, self.io = filt, io + lab_title.Wrap( -1 ) + sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + self.ctrl = wx.TextCtrl(self, wx.TE_RIGHT) + sizer.Add( self.ctrl, 2, wx.ALL, 5 ) + self.SetSizer(sizer) + + self.ctrl.Bind(wx.EVT_KEY_UP, self.ontext) + self.ctrl.Bind( wx.EVT_LEFT_DCLICK, self.onselect) + + def Bind(self, z, f): self.f = f + + def ontext(self, event): + self.f(self) + + def onselect(self, event): + if isinstance(self.filt, str): self.filt = self.filt.split(',') + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in self.filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + if self.io=='folder': + dialog = wx.DirDialog(self, 'Path Select', '', wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST | wx.FD_CHANGE_DIR) + else: dialog = wx.FileDialog(self, 'Path Select', '', '.', filt, dic[self.io] | wx.FD_CHANGE_DIR) + + rst = dialog.ShowModal() + if rst == wx.ID_OK: + path = dialog.GetPath() + self.ctrl.SetValue(path) + self.f(self) + dialog.Destroy() + + def SetValue(self, value): + self.ctrl.SetValue(value) + + def GetValue(self): + return self.ctrl.GetValue() class Choice(wx.Panel): - """ColorCtrl: deverid fron wx.coreTextCtrl""" - def __init__(self, parent, choices, tp, title, unit): + def __init__(self, parent, choices, tp, title, unit, app=None): wx.Panel.__init__(self, parent) self.tp, self.choices = tp, choices sizer = wx.BoxSizer( wx.HORIZONTAL ) self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -149,7 +197,7 @@ def __init__(self, parent, choices, tp, title, unit): self.ctrl.SetSelection(0) sizer.Add( self.ctrl, 2, wx.ALL, 5 ) self.postfix = lab_unit = wx.StaticText( self, wx.ID_ANY, unit, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_unit.Wrap( -1 ) sizer.Add( lab_unit, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) self.SetSizer(sizer) @@ -159,7 +207,7 @@ def Bind(self, z, f): self.f = f def on_choice(self, event): - self.f(event) + self.f(self) def SetValue(self, x): n = self.choices.index(x) if x in self.choices else 0 @@ -169,18 +217,18 @@ def GetValue(self): return self.tp(self.choices[self.ctrl.GetSelection()]) class AnyType( wx.Panel ): - def __init__( self, parent, title, types = ['Int', 'Float', 'Str']): + def __init__( self, parent, title, app=None): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(-1, -1), style = wx.TAB_TRAVERSAL ) sizer = wx.BoxSizer( wx.HORIZONTAL ) self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) self.txt_value = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) sizer.Add( self.txt_value, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - com_typeChoices = types + com_typeChoices = ['Int', 'Float', 'Str'] self.postfix = self.com_type = wx.ComboBox( self, wx.ID_ANY, 'Float', wx.DefaultPosition, wx.DefaultSize, com_typeChoices, 0 ) sizer.Add( self.com_type, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) @@ -220,7 +268,7 @@ def GetValue(self): # Virtual event handlers, overide them in your derived class def on_text( self, event ): - self.f(event) + self.f(self) if self.GetValue()==None: self.txt_value.SetBackgroundColour((255,255,0)) else: self.txt_value.SetBackgroundColour((255,255,255)) @@ -233,13 +281,13 @@ def on_type( self, event ): self.Refresh() class Choices(wx.Panel): - def __init__( self, parent, choices, title): + def __init__( self, parent, choices, title, app=None): self.choices = list(choices) wx.Panel.__init__(self, parent) sizer = wx.BoxSizer(wx.VERTICAL) - self.prefix = lab_title = wx.StaticText( self, wx.ID_ANY, title, - wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + lab_title = wx.StaticText( self, wx.ID_ANY, title, + wx.DefaultPosition, wx.DefaultSize) lab_title.Wrap( -1 ) sizer.Add( lab_title, 0, wx.ALL, 5 ) self.ctrl = wx.CheckListBox(self, -1, (80, 50), wx.DefaultSize, [str(i) for i in choices]) @@ -252,7 +300,7 @@ def Bind(self, z, f): self.f = f def on_check(self, event): - self.f(event) + self.f(self) def GetValue(self): return [self.choices[i] for i in self.ctrl.GetCheckedItems()] @@ -263,8 +311,7 @@ def SetValue(self, value): self.choices.index(i) for i in value if i in self.choices]) class FloatSlider(wx.Panel): - - def __init__( self, parent, rang, accury, title): + def __init__( self, parent, rang, accury, title, unit='', app=None): self.linux = platform.system() == 'Linux' wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size(-1,-1), style = wx.TAB_TRAVERSAL ) sizer = wx.BoxSizer( wx.VERTICAL ) @@ -273,18 +320,24 @@ def __init__( self, parent, rang, accury, title): subsizer = wx.BoxSizer( wx.HORIZONTAL ) self.lab_min = wx.StaticText( self, wx.ID_ANY, '0', wx.DefaultPosition, wx.DefaultSize, 0 ) self.lab_min.Wrap( -1 ) - subsizer.Add( self.lab_min, 0, wx.BOTTOM|wx.LEFT|wx.ALIGN_CENTER, 5 ) - subsizer.AddStretchSpacer(prop=1) + subsizer.Add( self.lab_min, 1, wx.BOTTOM|wx.LEFT|wx.ALIGN_CENTER, 5 ) + #subsizer.AddStretchSpacer(prop=1) + self.lab_title = wx.StaticText( self, wx.ID_ANY, title, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_title.Wrap( -1 ) + subsizer.Add( self.lab_title, 0, wx.ALIGN_CENTER|wx.BOTTOM|wx.RIGHT, 5 ) self.text = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(50,-1), 0 ) subsizer.Add( self.text, 0, wx.BOTTOM|wx.ALIGN_CENTER, 5 ) self.spin = wx.SpinButton( self, wx.ID_ANY, wx.DefaultPosition, wx.Size([20,-1][self.linux],-1), [0, wx.SP_HORIZONTAL][self.linux]) self.spin.SetRange(0, 255) self.spin.SetValue(128) - subsizer.Add( self.spin, 0, wx.ALIGN_CENTER|wx.BOTTOM|wx.EXPAND, 5 ) - subsizer.AddStretchSpacer(prop=1) - self.lab_max = wx.StaticText( self, wx.ID_ANY, '0', wx.DefaultPosition, wx.DefaultSize, 0 ) + subsizer.Add( self.spin, 0, wx.BOTTOM|wx.EXPAND, 5 ) + self.lab_unit = wx.StaticText( self, wx.ID_ANY, unit, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_unit.Wrap( -1 ) + subsizer.Add( self.lab_unit, 0, wx.ALIGN_CENTER|wx.BOTTOM|wx.LEFT, 5 ) + #subsizer.AddStretchSpacer(prop=1) + self.lab_max = wx.StaticText( self, wx.ID_ANY, '0', wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) self.lab_max.Wrap( -1 ) - subsizer.Add( self.lab_max, 0, wx.ALIGN_CENTER|wx.BOTTOM|wx.RIGHT, 5 ) + subsizer.Add( self.lab_max, 1, wx.ALIGN_CENTER|wx.BOTTOM|wx.RIGHT, 5 ) sizer.Add( subsizer, 0, wx.EXPAND, 0 ) @@ -295,7 +348,7 @@ def __init__( self, parent, rang, accury, title): self.slider.Bind( wx.EVT_SCROLL, self.on_scroll ) self.text.Bind( wx.EVT_KEY_UP, self.on_text ) #if not self.linux: - self.spin.Bind( wx.EVT_SPIN, self.on_spin ) + self.spin.Bind( wx.EVT_SPIN, self.on_spin) self.set_para(rang, accury) def Bind(self, z, f): @@ -314,14 +367,14 @@ def on_scroll(self, event): n = value/255.0*(self.max-self.min)+self.min self.text.SetValue(str(round(n,self.accury) if self.accury>0 else int(n))) self.text.SetBackgroundColour((255,255,255)) - self.f(event) + self.f(self) def on_spin(self, event): self.slider.SetValue(self.spin.GetValue()) self.on_scroll(event) def on_text(self, event): - self.f(event) + self.f(self) if self.GetValue()==None: self.text.SetBackgroundColour((255,255,0)) else: @@ -351,7 +404,7 @@ def GetValue(self): return num class Label(wx.Panel): - def __init__(self, parent, title): + def __init__(self, parent, title, app=None): wx.Panel.__init__(self, parent) sizer = wx.BoxSizer(wx.VERTICAL) lab_title = wx.StaticText( self, wx.ID_ANY, title, @@ -365,7 +418,7 @@ def SetValue(self, v): pass def GetValue(self, v): pass class Check(wx.Panel): - def __init__(self, parent, title): + def __init__(self, parent, title, app=None): wx.Panel.__init__(self, parent) sizer = wx.BoxSizer(wx.VERTICAL) check = wx.CheckBox(self, -1, title) @@ -376,11 +429,11 @@ def __init__(self, parent, title): self.GetValue = check.GetValue check.Bind(wx.EVT_CHECKBOX, self.on_check) - def on_check(self, event):self.f(event) + def on_check(self, event):self.f(self) def Bind(self, z, f): self.f = f if __name__ == '__main__': app = wx.App() frame = wx.Frame(None) frame.Show() - app.MainLoop() \ No newline at end of file + app.MainLoop() diff --git a/sciwx/widgets/paradialog.py b/sciwx/widgets/paradialog.py new file mode 100644 index 00000000..767db2da --- /dev/null +++ b/sciwx/widgets/paradialog.py @@ -0,0 +1,167 @@ +import wx, platform +from .normal import * +from .advanced import * +from .histpanel import HistPanel +from .curvepanel import CurvePanel +from .threpanel import ThresholdPanel + +widgets = { 'ctrl':None, 'slide':FloatSlider, int:NumCtrl, 'path':PathCtrl, + float:NumCtrl, 'lab':Label, bool:Check, str:TextCtrl, list:Choice, + 'color':ColorCtrl, 'cmap':CMapSelPanel, 'any':AnyType, 'chos':Choices, 'hist':ThresholdPanel, + 'curve':CurvePanel, 'img':ImageList, 'tab':TableList, 'field':TableField, 'fields':TableFields} + +def add_widget(key, value): widgets[key] = value + +class ParaDialog (wx.Dialog): + def __init__( self, parent, title): + wx.Dialog.__init__ (self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE) + self.lst = wx.BoxSizer( wx.VERTICAL ) + self.tus = [] + self.on_ok = self.on_cancel = self.on_help = None + self.handle = print + self.ctrl_dic = {} + boxBack = wx.BoxSizer() + boxBack.Add(self.lst, 0, wx.ALL, 10) + self.SetSizer( boxBack ) + self.Layout() + + def commit(self, state): + self.Destroy() + if state=='ok' and self.on_ok:self.on_ok() + if state=='cancel' and self.on_cancel:self.on_cancel() + + def add_confirm(self, modal): + sizer = wx.BoxSizer( wx.HORIZONTAL ) + self.btn_ok = wx.Button( self, wx.ID_OK, 'OK', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + self.btn_ok.SetFocus() + sizer.Add( self.btn_ok, 0, wx.ALL, 5 ) + + self.btn_cancel = wx.Button( self, wx.ID_CANCEL, 'Cancel', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + sizer.Add( self.btn_cancel, 0, wx.ALL, 5 ) + + self.btn_help = wx.Button( self, wx.ID_HELP, 'Help', wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + sizer.Add( self.btn_help, 0, wx.ALL, 5 ) + self.lst.Add(sizer, 0, wx.ALIGN_RIGHT, 5 ) + self.btn_help.Bind(wx.EVT_BUTTON, lambda e: self.on_help and self.on_help()) + if not modal: + self.btn_ok.Bind( wx.EVT_BUTTON, lambda e:self.commit('ok')) + self.btn_cancel.Bind( wx.EVT_BUTTON, lambda e:self.commit('cancel')) + #self.lst.Add() + + def init_view(self, items, para, preview=False, modal=True, app=None): + self.para, self.modal = para, modal + for item in items: + self.add_ctrl_(widgets[item[0]], item[1], item[2:], app=app) + if preview: self.add_ctrl_(Check, 'preview', ('preview',), app=app) + self.reset(para) + for p in self.ctrl_dic: + if p in para: para[p] = self.ctrl_dic[p].GetValue() + self.add_confirm(modal) + wx.Dialog.Bind(self, wx.EVT_WINDOW_DESTROY, self.OnDestroy) + #wx.Dialog.Bind(self, wx.EVT_IDLE, lambda e: self.reset()) + + def OnDestroy( self, event ): + self.handle = print + self.on_cancel = self.on_ok = self.on_help = None + del self.ctrl_dic + + def parse(self, para) : + self.add_ctrl_(widgets[para[0]], *para[1:]) + + def add_ctrl_(self, Ctrl, key, p, app=None): + ctrl = Ctrl(self, *p, app=app) + + if not p[0] is None: + self.ctrl_dic[key] = ctrl + if hasattr(ctrl, 'Bind'): + ctrl.Bind(None, self.para_changed) + pre = ctrl.prefix if hasattr(ctrl, 'prefix') else None + post = ctrl.postfix if hasattr(ctrl, 'postfix') else None + self.tus.append((pre, post)) + self.lst.Add( ctrl, 0, wx.EXPAND, 0 ) + + def pack(self): + mint, minu = [], [] + for t,u in self.tus: + if not t is None: mint.append(t.GetSize()[0]) + if not u is None:minu.append(u.GetSize()[0]) + for t,u in self.tus: + if not t is None:t.SetInitialSize((max(mint),-1)) + if not u is None:u.SetInitialSize((max(minu),-1)) + self.Layout() + self.Fit() + + def para_check(self, para, key):pass + + def para_changed(self, obj): + key = '' + para = self.para + for p in self.ctrl_dic: + if p in para: + para[p] = self.ctrl_dic[p].GetValue() + if self.ctrl_dic[p] == obj: key = p + + sta = sum([i is None for i in list(para.values())])==0 + self.btn_ok.Enable(sta) + if not sta: return + self.para_check(para, key) + if 'preview' not in self.ctrl_dic:return + if not self.ctrl_dic['preview'].GetValue(): + if key=='preview' and self.on_cancel != None: + return self.on_cancel() + else: return + self.handle(para) + + def reset(self, para=None): + if para!=None:self.para = para + for p in self.para.keys(): + if p in self.ctrl_dic: + self.ctrl_dic[p].SetValue(self.para[p]) + + def get_para(self): return self.para + + def Bind(self, tag, f): + if tag == 'parameter': self.handle = f if not f is None else print + if tag == 'commit': self.on_ok = f + if tag == 'cancel': self.on_cancel = f + if tag == 'help': self.on_help = f + + def show(self): + self.pack() + if self.modal: + status = self.ShowModal() == 5100 + self.Destroy() + return status + else: self.Show() + + def __del__( self ): + print('panel config deleted!') + +def get_para(para, view, title='Parameter', parent=None): + pd = ParaDialog(parent, title) + pd.init_view(view, para) + pd.pack() + rst = pd.ShowModal() + pd.Destroy() + return rst == 5100 + +if __name__ == '__main__': + para = {'name':'yxdragon', 'age':10, 'h':1.72, 'w':70, 'sport':True, 'sys':'Mac', 'lan':['C/C++', 'Python'], 'c':(255,0,0)} + + view = [('lab', 'lab', 'This is a questionnaire'), + (str, 'name', 'name', 'please'), + (int, 'age', (0,150), 0, 'age', 'years old'), + (float, 'h', (0.3, 2.5), 2, 'height', 'm'), + ('slide', 'w', (1, 150), 0, 'weight','kg'), + (bool, 'sport', 'do you like sport'), + (list, 'sys', ['Windows','Mac','Linux'], str, 'favourite', 'system'), + ('chos', 'lan', ['C/C++','Java','Python'], 'lanuage you like(multi)'), + ('color', 'c', 'which', 'you like')] + + app = wx.App() + pd = ParaDialog(None, 'Test') + pd.init_view(view, para, preview=True, modal=False) + pd.pack() + pd.ShowModal() + print(para) + app.MainLoop() diff --git a/sciwx/widgets/progressbar.py b/sciwx/widgets/progressbar.py new file mode 100644 index 00000000..23503b43 --- /dev/null +++ b/sciwx/widgets/progressbar.py @@ -0,0 +1,56 @@ +import threading, time, wx + +class ProgressBar ( wx.Panel ): + def __init__( self, parent ): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.TAB_TRAVERSAL ) + + sizer = wx.BoxSizer( wx.HORIZONTAL ) + + self.lab_name = wx.StaticText( self, wx.ID_ANY, u"Process", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lab_name.Wrap( -1 ) + sizer.Add( self.lab_name, 0, wx.RIGHT, 5 ) + + self.gau_bar = wx.Gauge( self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL ) + self.gau_bar.SetValue( 0 ) + sizer.Add( self.gau_bar, 0, wx.ALL, 0 ) + + self.progress = [] + self.cur = 0 + self.SetSizer( sizer ) + self.Layout() + self.Fit() + + thread = threading.Thread(None, self.hold, ()) + thread.setDaemon(True) + thread.start() + + def SetValue(self, values=[]): + self.progress = values + + def hold(self): + span, c, t = 30, 0, 0 + while True: + time.sleep(0.1) + try: + if len(self.progress)==0 and self.IsShown(): self.Hide() + if len(self.progress)>0 and not self.IsShown(): self.Show() + if len(self.progress)==0: continue + t = (t + 1)%span + if t==0: self.cur = (self.cur + 1)%len(self.progress) + name, f = self.progress[self.cur] + wx.CallAfter(self.lab_name.SetLabel, name) + if f() is None: + c = (c + 5)%200 + wx.CallAfter(self.gau_bar.SetValue, 100-abs(c-100)) + else: wx.CallAfter(self.gau_bar.SetValue, f()) + self.Layout() + self.GetParent().Layout() + except: pass + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + pb = ProgressBar(frame) + pb.SetValue([('third', lambda : -1)]) + frame.Show() + app.MainLoop() diff --git a/sciwx/widgets/propdialog.py b/sciwx/widgets/propdialog.py new file mode 100644 index 00000000..471e5a9f --- /dev/null +++ b/sciwx/widgets/propdialog.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +import sys, time, math, os.path + +import wx, wx.adv +import wx.propgrid as wxpg + +from six import exec_ +_ = wx.GetTranslation + +class GridDialog( wx.Dialog ): + + def __init__( self, parent, title): + wx.Dialog.__init__ (self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE, size = wx.Size((300, 480))) + # wx.Panel.__init__(self, parent, wx.ID_ANY) + + def commit(self, state): + self.Destroy() + if state=='ok' and self.on_ok:self.on_ok() + if state=='cancel' and self.on_cancel:self.on_cancel() + + def add_confirm(self, modal): + rowsizer = wx.BoxSizer(wx.HORIZONTAL) + but = wx.Button(self, wx.ID_OK, "OK") + rowsizer.Add(but,1) + #but.Bind( wx.EVT_BUTTON, self.OnGetPropertyValues ) + but = wx.Button(self, wx.ID_CANCEL,"Cancel") + rowsizer.Add(but,1) + if not modal: + self.btn_ok.Bind( wx.EVT_BUTTON, lambda e:self.commit('ok')) + self.btn_cancel.Bind( wx.EVT_BUTTON, lambda e:self.commit('cancel')) + self.GetSizer().Add(rowsizer,0,wx.EXPAND) + + def init_view(self, items, para, preview=False, modal=True, app=None): + self.para, self.modal = para, modal + self.pg = pg = wxpg.PropertyGridManager(self, style=wxpg.PG_SPLITTER_AUTO_CENTER) + + sizer = wx.BoxSizer(wx.VERTICAL) + # Show help as tooltips + pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) + pg.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange ) + pg.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect ) + + pg.AddPage('') + for i in items: + if i[0] == 'lab': pg.Append(wxpg.PropertyCategory(i[2])) + if i[0] == int: pg.Append( wxpg.IntProperty(i[1], value=int(para[i[1]]) or 0)) + if i[0] == float: pg.Append( wxpg.FloatProperty(i[1], value=float(para[i[1]]) or 0)) + if i[0] == str: pg.Append( wxpg.StringProperty(i[1], value=para[i[1]] or '')) + if i[0] == 'txt': pg.Append( wxpg.LongStringProperty(i[1], value=para[i[1]] or '')) + if i[0] == bool: pg.Append( wxpg.BoolProperty(i[1])) + if i[0] == 'date': pg.Append( wxpg.DateProperty(i[1], value=wx.DateTime.Now())) + #if i[0] == 'list': pg.Append( wxpg.EnumProperty(i[1], i[1], [i.strip() for i in i[2][1:-1].split(',')])) + #if i[0] == 'img': pg.Append( wxpg.EnumProperty(v[1], v[1], ['a','b','c'])) + #if i[0] == 'tab': pg.Append( wxpg.EnumProperty(v[1], v[1], ['a','b','c'])) + + #if preview:self.add_ctrl_(Check, 'preview', ('preview',), app=app) + #self.reset(para) + sizer.Add(pg, 1, wx.EXPAND) + self.SetSizer(sizer) + self.add_confirm(modal) + self.Layout() + self.Fit() + wx.Dialog.Bind(self, wx.EVT_WINDOW_DESTROY, self.OnDestroy) + #wx.Dialog.Bind(self, wx.EVT_IDLE, lambda e: self.reset()) + print('bind close') + + def OnDestroy( self, event ): + self.handle = print + self.on_cancel = self.on_ok = self.on_help = None + del self.ctrl_dic + + def GetValue(self): + return self.pg.GetPropertyValues(as_strings=True) + + def OnGetPropertyValues(self,event): + para = self.pg.GetPropertyValues(inc_attributes=True) + + def OnPropGridChange(self, event): + p = event.GetProperty() + if p: print('%s changed to "%s"\n' % (p.GetName(),p.GetValueAsString())) + + def OnPropGridSelect(self, event): + p = event.GetProperty() + if p: self.txt_info.SetValue('%s: %s'%(p.GetName(), self.key[p.GetName()][3])) + +if __name__ == '__main__': + app = wx.App(False) + + para = {'sid':'001', 'name':'yxl', 'Date':None, 'age':5} + view = [('lab', None, 'Label1'), + (str, 'sid', 'Sample_ID'), + (str, 'name', 'Operator_Name'), + ('lab', None, 'Label2'), + (int, 'age', 'Your Age')] + + frame = GridDialog(None, 'Property Grid') + frame.init_view(view, para) + rst = frame.ShowModal() == wx.ID_OK + app.MainLoop() \ No newline at end of file diff --git a/sciwx/widgets/propertygrid.py b/sciwx/widgets/propertygrid.py new file mode 100644 index 00000000..60eb5f60 --- /dev/null +++ b/sciwx/widgets/propertygrid.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +import sys, time, math, os.path + +import wx, wx.adv +import wx.propgrid as wxpg + +from six import exec_ +_ = wx.GetTranslation + +data = [('Sheet1', [((4, 5), ['str', 'Sample_ID', '5', "image's name"]), ((4, 17), ['str', 'Operator_Name', None, 'your name']), ((4, 30), ['date', 'Date', None, 'today']), ((10, 1), ['img', 'Original_Image', '[8.16,5.76,0.9,0]', 'the original image']), ((10, 20), ['img', 'Mask_Image', '[8.16,5.76,0.9,0]', '']), ((28, 1), ['tab', 'Record', '[1,3,0,0]', 'records'])]), ('Sheet2', [((0,0), ['list', 'a', '[1,2,345]', 'nothing'])]), ('Sheet3', [])] +key = {'Sample_ID': ['str', 'Sample_ID', '5', "image's name"], 'Operator_Name': ['str', 'Operator_Name', None, 'your name'], 'Date': ['date', 'Date', None, 'today'], 'Original_Image': ['img', 'Original_Image', '[8.16,5.76,0.9,0]', 'the original image'], 'Mask_Image': ['img', 'Mask_Image', '[8.16,5.76,0.9,0]', ''], 'Record': ['tab', 'Record', '[1,3,0,0]', 'records']} + +class GridDialog( wx.Dialog ): + + def __init__( self, parent, title, tree, key): + wx.Dialog.__init__ (self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE, size = wx.Size((300, 480))) + # wx.Panel.__init__(self, parent, wx.ID_ANY) + self.app = parent + + self.panel = panel = wx.Panel(self, wx.ID_ANY) + topsizer = wx.BoxSizer(wx.VERTICAL) + + # Difference between using PropertyGridManager vs PropertyGrid is that + # the manager supports multiple pages and a description box. + self.pg = pg = wxpg.PropertyGridManager(panel, + style=wxpg.PG_SPLITTER_AUTO_CENTER) + + # Show help as tooltips + pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) + + pg.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange ) + pg.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect ) + + pg.AddPage('Page 1') + self.key = key + for page in tree: + pg.Append(wxpg.PropertyCategory(page[0])) + for item in page[1]: + v = item[1] + if v[0] == 'int': pg.Append( wxpg.IntProperty(v[1], value=int(v[2]) or 0)) + if v[0] == 'float': pg.Append( wxpg.FloatProperty(v[1], value=float(v[2]) or 0)) + if v[0] == 'str': pg.Append( wxpg.StringProperty(v[1], value=v[2] or '')) + if v[0] == 'txt': pg.Append( wxpg.LongStringProperty(v[1], value=v[2] or '')) + if v[0] == 'bool': pg.Append( wxpg.BoolProperty(v[1])) + if v[0] == 'date': pg.Append( wxpg.DateProperty(v[1], value=wx.DateTime.Now())) + if v[0] == 'list': pg.Append( wxpg.EnumProperty(v[1], v[1], [i.strip() for i in v[2][1:-1].split(',')])) + if v[0] == 'img': pg.Append( wxpg.EnumProperty(v[1], v[1], self.app.img_names())) + if v[0] == 'tab': pg.Append( wxpg.EnumProperty(v[1], v[1], self.app.table_names())) + + topsizer.Add(pg, 1, wx.EXPAND) + self.txt_info = wx.TextCtrl( self, wx.ID_ANY, 'information', wx.DefaultPosition, wx.Size(80, 80), wx.TE_MULTILINE|wx.TRANSPARENT_WINDOW ) + topsizer.Add(self.txt_info, 0, wx.EXPAND|wx.ALL, 0) + rowsizer = wx.BoxSizer(wx.HORIZONTAL) + but = wx.Button(panel, wx.ID_OK, "OK") + rowsizer.Add(but,1) + #but.Bind( wx.EVT_BUTTON, self.OnGetPropertyValues ) + but = wx.Button(panel, wx.ID_CANCEL,"Cancel") + rowsizer.Add(but,1) + topsizer.Add(rowsizer,0,wx.EXPAND) + + panel.SetSizer(topsizer) + topsizer.SetSizeHints(panel) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(panel, 1, wx.EXPAND) + self.SetSizer(sizer) + self.SetAutoLayout(True) + + def GetValue(self): + return self.pg.GetPropertyValues(as_strings=True) + + def OnGetPropertyValues(self,event): + para = self.pg.GetPropertyValues(inc_attributes=True) + + def OnPropGridChange(self, event): + p = event.GetProperty() + if p: print('%s changed to "%s"\n' % (p.GetName(),p.GetValueAsString())) + + def OnPropGridSelect(self, event): + p = event.GetProperty() + if p: self.txt_info.SetValue('%s: %s'%(p.GetName(), self.key[p.GetName()][3])) + +if __name__ == '__main__': + app = wx.App(False) + frame = GridDialog(None, 'Property Grid', data, key) + rst = frame.ShowModal() == wx.ID_OK + app.MainLoop() \ No newline at end of file diff --git a/sciwx/widgets/ribbonbar.py b/sciwx/widgets/ribbonbar.py new file mode 100644 index 00000000..3fa6634b --- /dev/null +++ b/sciwx/widgets/ribbonbar.py @@ -0,0 +1,152 @@ +import wx.lib.agw.ribbon as rb +import wx +import numpy as np + +def make_logo(path, w=40): + if isinstance(path, str): + img = wx.Bitmap(path).ConvertToImage() + else: img = wx.Image(1, 1, np.array(path, dtype=np.uint8).tobytes()) + return img.Rescale(w, w).ConvertToBitmap() + +def make_logo(obj, w=40): + bmp = None + if isinstance(obj, str) and '.' in obj: + bmp = wx.Bitmap(obj).ConvertToImage() + if isinstance(obj, str) and not '.' in obj: + bmp = wx.Bitmap(w, w) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + dc.SetBackground(wx.Brush((255,255,255))) + dc.Clear() + dc.SetTextForeground((100,0,128)) + font = dc.GetFont() + font.SetPointSize(22) + dc.SetFont(font) + ww, hh = dc.GetTextExtent(obj) + dc.DrawText(obj, (w-ww)//2, (w-hh)//2) + rgb = bytes(w * w * 3) + dc.SelectObject(wx.NullBitmap) + bmp.CopyToBuffer(rgb) + a = memoryview(rgb[::3]).tolist() + a = bytes([255-i for i in a]) + bmp = wx.Bitmap.FromBufferAndAlpha(w, w, rgb, a) + bmp = bmp.ConvertToImage() + if isinstance(obj, tuple): + bmp = wx.Image(1, 1, np.array(obj, dtype=np.uint8).tobytes()) + + return bmp.Rescale(w, w).ConvertToBitmap() + +def hot_key(txt): + sep = txt.split('-') + acc, code = wx.ACCEL_NORMAL, -1 + if 'Ctrl' in sep: acc|= wx.ACCEL_CTRL + if 'Alt' in sep: acc|= wx.ACCEL_ALT + if 'Shift' in sep: acc|= wx.ACCEL_SHIFT + fs = ['F%d'%i for i in range(1,13)] + if sep[-1] in fs: + code = 340+fs.index(sep[-1]) + elif len(sep[-1])==1: code = ord(sep[-1]) + return acc, code + +class RibbonBar(rb.RibbonBar): + def __init__(self, app): + rb.RibbonBar.__init__(self, app, wx.ID_ANY, wx.DefaultPosition, (-1, 140), rb.RIBBON_BAR_DEFAULT_STYLE|rb.RIBBON_BAR_SHOW_PANEL_EXT_BUTTONS ) + self.app = app.GetTopLevelParent() + + def parse(self, ks, vs, pt): + if isinstance(vs, list): + menu = wx.Menu() + for kv in vs: + if kv == '-': menu.AppendSeparator() + else: self.parse(*kv, menu) + pt.Append(1, ks, menu) + else: + item = wx.MenuItem(pt, -1, ks) + f = lambda e, p=vs: p().start(self.app) + self.Bind(wx.EVT_MENU, f, item) + pt.Append(item) + + def parse(self, ks, vs, pt, short, rst): + page = rb.RibbonPage( self, wx.ID_ANY, ks , wx.NullBitmap , 0 ) + panel = toolbar = None + + for kv1 in vs: + if len(kv1) == 2: + kv1 = (kv1[0], None, kv1[1]) + if kv1 == '-': continue + pname = kv1[0] if isinstance(kv1[2], list) else '--' + if panel is None and not isinstance(kv1[2], list): + panel = rb.RibbonPanel( page, wx.ID_ANY, pname , make_logo(kv1[1] or kv1[0][0]), wx.DefaultPosition, wx.DefaultSize, rb.RIBBON_PANEL_DEFAULT_STYLE ) + toolbar = rb.RibbonButtonBar( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) + if not isinstance(kv1[2], list): + btn = toolbar.AddSimpleButton( wx.NewId(), kv1[0], make_logo(kv1[1] or kv1[0][0]), wx.EmptyString) + toolbar.Bind(rb.EVT_RIBBONBUTTONBAR_CLICKED, lambda e, p=kv1[2]:p().start(self.app), id=btn.id) + self.GetParent().Bind(wx.EVT_MENU, lambda e, p=kv1[2]:p().start(self.app), id=btn.id) + if kv1[0] in short: rst.append((short[kv1[0]], btn.id)) + + else: + panel = rb.RibbonPanel( page, wx.ID_ANY, pname , make_logo(kv1[1] or kv1[0][0]) , wx.DefaultPosition, wx.DefaultSize, rb.RIBBON_PANEL_DEFAULT_STYLE ) + toolbar = rb.RibbonButtonBar( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) + for kv2 in kv1[2]: + if len(kv2) == 2: + kv2 = (kv2[0], None, kv2[1]) + if kv2 == '-': continue + btn = toolbar.AddSimpleButton( wx.NewId(), kv2[0], make_logo(kv2[1] or kv2[0][0]), wx.EmptyString) + toolbar.Bind(rb.EVT_RIBBONBUTTONBAR_CLICKED, lambda e, p=kv2[2]:p().start(self.app), id=btn.id) + self.GetParent().Bind(wx.EVT_MENU, lambda e, p=kv2[2]:p().start(self.app), id=btn.id) + if kv2[0] in short: rst.append((short[kv2[0]], btn.id)) + panel = toolbar = None + self.Realize() + + def Append(self, id, item, menu): + wx.MenuBar.Append(self, menu, item) + + def load(self, data, shortcut={}): + rst = [] + for klv in data[1]: + if len(klv) == 2: + self.parse(klv[0], klv[1], self, shortcut, rst) + else: + self.parse(klv[0], klv[2], self, shortcut, rst) + + rst = [(*hot_key(i[0]), i[1]) for i in rst] + return wx.AcceleratorTable(rst) + + def on_menu(self, event): + print('here') + + def clear(self): + self._pages.clear() + self._current_page = -1 + self.DestroyChildren() + self.Realize() + +if __name__ == '__main__': + class P: + def __init__(self, name): + self.name = name + + def start(self, app): + print(self.name) + + def __call__(self): + return self + + data = ('menu', [ + ('File', None, [ + ('Open CV', (255,0,0), P('O')), + '-', + ('Close', None, P('C'))]), + ('Edit', None, [('Copy', None, P('C')), + ('A', None, [('B', None, P('B')), + ('C', None, P('C'))]), + ('Paste', P('P'))])]) + app = wx.App() + frame = wx.Frame(None) + menubar = RibbonBar(frame) + acc = menubar.load(data, {'Open CV':'Ctrl-O'}) + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add( menubar, 0, wx.ALL | wx.EXPAND, 0 ) + frame.SetSizer(sizer) + frame.Show() + app.MainLoop() diff --git a/sciwx/widgets/threpanel.py b/sciwx/widgets/threpanel.py new file mode 100644 index 00000000..f1b0c591 --- /dev/null +++ b/sciwx/widgets/threpanel.py @@ -0,0 +1,52 @@ +import wx, math +from .histpanel import HistPanel +from .normal import FloatSlider + +class ThresholdPanel( wx.Panel ): + def __init__( self, parent, mode, hist, rang, accury, app=None): + wx.Panel.__init__ ( self, parent) + (self.lim1, self.lim2), self.mode = rang, mode + bSizer1 = wx.BoxSizer( wx.VERTICAL ) + + self.histpan = HistPanel(self) + self.histpan.SetValue(hist) + bSizer1.Add(self.histpan, 0, wx.ALL|wx.EXPAND, 5 ) + + self.sli_high = FloatSlider(self, rang, accury, '', '') + self.sli_high.SetValue(rang[0]) + bSizer1.Add( self.sli_high, 0, wx.ALL|wx.EXPAND, 0 ) + if mode == 'bc': rang, accury = (1, 89), 0 + self.sli_low = FloatSlider(self, rang, accury, '', '') + self.sli_low.SetValue(rang[1]) + bSizer1.Add( self.sli_low, 0, wx.ALL|wx.EXPAND, 0 ) + self.SetSizer(bSizer1) + + def on_threshold(self, dir, event): + if self.f is None: return + a, b = self.GetValue() + if self.mode == 'lh': + if dir: b = max(a, b) + else: a = min(a, b) + self.SetValue((a,b)) + a = int((a-self.lim1)/(self.lim2-self.lim1)*255) + b = int((b-self.lim1)/(self.lim2-self.lim1)*255) + self.histpan.set_lim(a, b) + if self.mode == 'bc': + mid = 128-a/(self.lim2-self.lim1)*255 + length = 255/math.tan(b/180.0*math.pi) + self.histpan.set_lim(mid-length/2, mid+length/2) + self.f(self) + + def Bind(self, z, f): + self.f = f + self.sli_high.Bind(z, lambda e: self.on_threshold(True, e)) + self.sli_low.Bind(z, lambda e: self.on_threshold(False, e)) + + def SetValue(self, n): + self.sli_high.SetValue(n[0]) + self.sli_low.SetValue(n[1]) + + def GetValue(self): + b = self.sli_low.GetValue() + a = self.sli_high.GetValue() + return None if None in (a,b) else (a,b) \ No newline at end of file diff --git a/sciwx/widgets/toolbar.py b/sciwx/widgets/toolbar.py new file mode 100644 index 00000000..41c84da5 --- /dev/null +++ b/sciwx/widgets/toolbar.py @@ -0,0 +1,132 @@ +import wx +from .paradialog import ParaDialog + +def make_logo(obj): + bmp = None + if isinstance(obj, str) and len(obj)>1: + bmp = wx.Bitmap(obj) + if isinstance(obj, str) and len(obj)==1: + bmp = wx.Bitmap(16, 16) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + dc.SetBackground(wx.Brush((255,255,255))) + dc.Clear() + dc.SetTextForeground((0,0,150)) + font = dc.GetFont() + font.SetPointSize(12) + dc.SetFont(font) + w, h = dc.GetTextExtent(obj) + dc.DrawText(obj, 8-w//2, 8-h//2) + rgb = bytes(768) + dc.SelectObject(wx.NullBitmap) + bmp.CopyToBuffer(rgb) + a = memoryview(rgb[::3]).tolist() + a = bytes([255-i for i in a]) + bmp = wx.Bitmap.FromBufferAndAlpha(16, 16, rgb, a) + # img = bmp.ConvertToImage() + # img.Resize((20,20), (2,2)) + # return img.ConvertToBitmap() + return bmp + +class ToolBar(wx.Panel): + def __init__(self, parent, vertical=False): + wx.Panel.__init__( self, parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + sizer = wx.BoxSizer( (wx.HORIZONTAL, wx.VERTICAL)[vertical] ) + self.SetSizer( sizer ) + self.app = parent + self.toolset = [] + self.curbtn = None + + def on_tool(self, evt, tol): + tol.start(self.app) + # evt.Skip() + btn = evt.GetEventObject() + #print(self.GetBackgroundColour()) + #print(btn.GetClassDefaultAttributes().colFg) + if not self.curbtn is None: + self.curbtn.SetBackgroundColour(self.GetBackgroundColour()) + self.curbtn = btn + btn.SetBackgroundColour(wx.SystemSettings.GetColour( wx.SYS_COLOUR_ACTIVECAPTION ) ) + + def on_config(self, evt, tol): + if not hasattr(tol, 'view'): return + self.app.show_para(tol.title, tol.para, tol.view) + tol.config() + + def on_help(self, evt, tol): pass + + def on_info(self, event, tol): + self.app.info(tol.title) + + def bind(self, btn, tol): + obj = tol() + btn.SetBackgroundColour(self.GetBackgroundColour()) + btn.Bind( wx.EVT_LEFT_DOWN, lambda e, obj=obj: self.on_tool(e, obj)) + btn.Bind( wx.EVT_RIGHT_DOWN, lambda e, obj=obj: self.on_help(e, obj)) + btn.Bind( wx.EVT_ENTER_WINDOW, lambda e, obj=obj: self.on_info(e, obj)) + #if not isinstance(data[0], Macros) and issubclass(data[0], Tool): + btn.Bind(wx.EVT_LEFT_DCLICK, lambda e, obj=obj: self.on_config(e, obj)) + + def clear(self): + del self.toolset[:] + self.GetSizer().Clear() + self.DestroyChildren() + + def add_tool(self, logo, tool): + btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(logo), + wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER ) + self.bind(btn, tool) + self.GetSizer().Add(btn, 0, wx.ALL, 1) + + def add_tools(self, name, tools, fixed=True): + if not fixed: self.toolset.append((name, [])) + for logo, tool in tools: + btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(logo), + wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER ) + self.bind(btn, tool) + self.GetSizer().Add(btn, 0, wx.ALL, 1) + if not fixed: self.toolset[-1][1].append(btn) + if fixed: + line = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL ) + self.GetSizer().Add( line, 0, wx.ALL|wx.EXPAND, 2 ) + + def active_set(self, name): + for n, tools in self.toolset: + for btn in tools: + if n==name: btn.Show() + if n!=name: btn.Hide() + self.Layout() + + def add_pop(self, logo, default): + self.GetSizer().AddStretchSpacer(1) + btn = wx.BitmapButton(self, wx.ID_ANY, make_logo(logo), + wx.DefaultPosition, (32,32), wx.BU_AUTODRAW|wx.RAISED_BORDER ) + btn.Bind(wx.EVT_LEFT_DOWN, self.menu_drop) + btn.SetBackgroundColour(self.GetBackgroundColour()) + self.GetSizer().Add(btn, 0, wx.ALL, 1) + self.active_set(default) + + def menu_drop(self, event): + menu = wx.Menu() + for name, item in self.toolset: + item = wx.MenuItem(menu, wx.ID_ANY, name, wx.EmptyString, wx.ITEM_NORMAL ) + menu.Append(item) + f = lambda e, name=name:self.active_set(name) + menu.Bind(wx.EVT_MENU, f, id=item.GetId()) + self.PopupMenu( menu ) + menu.Destroy() + +if __name__ == '__main__': + path = 'C:/Users/54631/Documents/projects/imagepy/imagepy/tools/drop.gif' + app = wx.App() + frame = wx.Frame(None) + tool = ToolBar(frame, vertical=True) + path = 'C:/Users/54631/Documents/projects/imagepy2/fucai/imgs/_help.png' + tool.add_tools('A', [('A', None)] * 3) + tool.add_tools('B', [('B', None)] * 3) + tool.add_tools('C', [('C', None)] * 3) + tool.add_pop('P', 'B') + tool.Layout() + frame.Fit() + frame.Show() + app.MainLoop() diff --git a/sciwx/widgets/util.py b/sciwx/widgets/util.py new file mode 100644 index 00000000..3328b1b1 --- /dev/null +++ b/sciwx/widgets/util.py @@ -0,0 +1,27 @@ +import wx + +def alert(info, title='SciWx'): + dialog=wx.MessageDialog(None, info, title, wx.OK) + dialog.ShowModal() == wx.ID_OK + dialog.Destroy() + +def get_path(title, filt, io, name=''): + filt = '|'.join(['%s files (*.%s)|*.%s'%(i.upper(),i,i) for i in filt]) + dic = {'open':wx.FD_OPEN, 'save':wx.FD_SAVE} + dialog = wx.FileDialog(None, title, '', name, filt, dic[io]) + rst = dialog.ShowModal() + path = dialog.GetPath() if rst == wx.ID_OK else None + dialog.Destroy() + return path + +if __name__ == '__main__': + app = wx.App() + frame = wx.Frame(None) + frame.Show() + + frame2 = wx.Frame(None) + frame2.Show() + + path = get_path('file', ['png','bmp'], 'save') + print(path) + app.MainLoop() \ No newline at end of file diff --git a/imagepy/ui/widgets/viewport.py b/sciwx/widgets/viewport.py similarity index 65% rename from imagepy/ui/widgets/viewport.py rename to sciwx/widgets/viewport.py index 2840b380..28a1f9dc 100644 --- a/imagepy/ui/widgets/viewport.py +++ b/sciwx/widgets/viewport.py @@ -1,56 +1,57 @@ import wx, sys import numpy as np from numpy.linalg import norm -from scipy import interpolate - -if sys.version_info[0]==2:memoryview=np.getbuffer class ViewPort(wx.Panel): """ HistCanvas: diverid from wx.core.Panel """ def __init__(self, parent): wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, - pos = wx.DefaultPosition, size = wx.Size(-1,-1), + pos = wx.DefaultPosition, size = wx.Size(100,100), style = wx.TAB_TRAVERSAL ) - self.init_buf() self.img = None self.boximg = None self.boxpan = None self.box = (0,0) self.ibox = (100,100) - self.update = False + self.dirty = False self.loc = (0,0) self.drag = False - self.Bind(wx.EVT_SIZE, self.on_size) - self.Bind(wx.EVT_IDLE, self.on_idle) - self.Bind(wx.EVT_PAINT, self.on_paint) - self.Bind(wx.EVT_LEFT_DOWN, self.on_ld) - self.Bind(wx.EVT_LEFT_UP, self.on_lu) - self.Bind(wx.EVT_MOTION, self.on_mv) + wx.Panel.Bind(self, wx.EVT_SIZE, self.on_size) + wx.Panel.Bind(self, wx.EVT_IDLE, self.on_idle) + wx.Panel.Bind(self, wx.EVT_PAINT, self.on_paint) + wx.Panel.Bind(self, wx.EVT_LEFT_DOWN, self.on_ld) + wx.Panel.Bind(self, wx.EVT_LEFT_UP, self.on_lu) + wx.Panel.Bind(self, wx.EVT_MOTION, self.on_mv) + self.init_buf() + + def update(self): self.dirty = True def init_buf(self): self.box = box = self.GetClientSize() + if min(box)==0: return self.buffer = wx.Bitmap(box.width, box.height) + self.update() def on_size(self, event): self.init_buf() - self.update = True + self.update() def on_idle(self, event): - if self.update == True: + if self.dirty == True: self.draw() - self.update = False + self.dirty = False def on_paint(self, event): wx.BufferedPaintDC(self, self.buffer) def on_ld(self, event): + self.on_view(self.GetValue(), True) x, y = event.GetX(), event.GetY() x = 1.0*(x-self.offx)/self.imgw y = 1.0*(y-self.offy)/self.imgh if x<0 or x>1 or y<0 or y>1:return self.loc = (x*self.ibox[0], y*self.ibox[1]) - self.handle() self.drag = True def on_lu(self, event): @@ -63,7 +64,7 @@ def on_mv(self, event): y = 1.0*(y-self.offy)/self.imgh if x<0 or x>1 or y<0 or y>1:return self.loc = (x*self.ibox[0], y*self.ibox[1]) - self.handle() + self.on_view(self.GetValue()) def GetValue(self):return self.loc @@ -72,20 +73,18 @@ def set_img(self, img, size): bmp = wx.Bitmap.FromBuffer(img.shape[1], img.shape[0], memoryview(img.copy())) if 1.0*self.box[0]/self.box[1]<1.0*self.ibox[0]/self.ibox[1]: k = 1.0*self.box[0]/self.ibox[0] - self.imgw, self.imgh = self.box[0], self.ibox[1]*k - self.offx, self.offy = 0, (self.box[1]-self.imgh)/2.0 + self.imgw, self.imgh = int(self.box[0]+0.5), int(self.ibox[1]*k+0.5) + self.offx, self.offy = 0, int((self.box[1]-self.imgh)/2.0+0.5) self.img = bmp.ConvertToImage().Rescale(self.imgw, self.imgh).ConvertToBitmap() else: k = 1.0*self.box[1]/self.ibox[1] - self.imgw, self.imgh = self.ibox[0]*k, self.box[1] - self.offx, self.offy = (self.box[0]-self.imgw)/2.0, 0 - print(self.imgw, self.imgh) + self.imgw, self.imgh = int(self.ibox[0]*k+0.5), int(self.box[1]+0.5) + self.offx, self.offy = int((self.box[0]-self.imgw)/2.0+0.5), 0 self.img = bmp.ConvertToImage().Rescale(self.imgw, self.imgh).ConvertToBitmap() - def set_box(self, boximg, boxpan): self.boximg, self.boxpan = boximg, boxpan - self.update = True + self.update() def draw(self): # get client device context buffer @@ -97,10 +96,11 @@ def draw(self): dc.DrawRectangle(0, 0, self.box[0], self.box[1]) return # w, h = self.GetClientSize() - l = 1.0*max(self.boxpan[0]-self.boximg[0], 0)/self.boximg[2] - r = 1.0*min((self.boxpan[2]-self.boximg[0])/self.boximg[2],1) - t = 1.0*max(self.boxpan[1]-self.boximg[1], 0)/self.boximg[3] - b = 1.0*min((self.boxpan[3]-self.boximg[1])/self.boximg[3],1) + (pa,pb,pc,pd), (ia,ib,ic,id) = self.boxpan, self.boximg + l = 1.0*max(pa-ia, 0)/(ic-ia) + r = 1.0*min((pc-pa-ia)/(ic-ia),1) + t = 1.0*max(pb-ib, 0)/(id-ib) + b = 1.0*min((pd-pb-ib)/(id-ib),1) # the main draw process @@ -109,19 +109,21 @@ def draw(self): dc.DrawRectangle(self.offx, self.offy, self.imgw, self.imgh) x,y,w,h = l*self.imgw+self.offx, t*self.imgh+self.offy,(r-l)*self.imgw,(b-t)*self.imgh dc.SetPen(wx.Pen((255,0,0), width=1, style=wx.SOLID)) - dc.DrawRectangle(x,y,w,h) + dc.DrawRectangle(int(x), int(y), int(w), int(h)) - def handle(self):pass + def on_view(self, event): print(event) - def set_handle(self, handle):self.handle = handle + def Bind(self, tag, f): self.on_view = f if __name__ == '__main__': - app = wx.PySimpleApp() - frame = wx.Frame(None) - hist = ViewPort(frame) - #hist.set_img(np.zeros((6,10))) - hist.set_box([0,0,10,6],[0,0,5,5]) + app = wx.App() + frame = wx.Frame(None, size=(300, 300)) + + view = ViewPort(frame) + img = np.random.randint(0,255, (512, 512, 3), dtype=np.uint8) frame.Fit() frame.Show(True) - app.MainLoop() \ No newline at end of file + view.set_img(img, (200,200)) + view.set_box([0,0,10,6],[0,0,5,5]) + app.MainLoop() diff --git a/sciwx/widgets/workflow.py b/sciwx/widgets/workflow.py new file mode 100644 index 00000000..186874ae --- /dev/null +++ b/sciwx/widgets/workflow.py @@ -0,0 +1,140 @@ +import wx + +def parse(cont): + ls = cont.split('\n') + workflow = {'title':ls[0], 'chapter':[]} + for line in ls[2:]: + line = line.strip() + if line == '':continue + if line.startswith('## '): + chapter = {'title':line[3:], 'section':[]} + workflow['chapter'].append(chapter) + elif line[1:3] == '. ': + section = {'title':line[3:]} + else: + section['hint'] = line + chapter['section'].append(section) + return workflow + +class WorkFlowPanel ( wx.Panel ): + def __init__( self, parent ): + wx.Panel.__init__ ( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TAB_TRAVERSAL ) + self.app, self.f = parent, print + + def SetValue(self, cont): + self.workflow, self.cont = parse(cont), cont + sizer_scroll = wx.BoxSizer( wx.HORIZONTAL ) + + self.scr_workflow = wx.ScrolledCanvas( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize) + self.scr_workflow.SetScrollRate( 30, 0 ) + self.scr_workflow.ShowScrollbars(wx.SHOW_SB_NEVER, wx.SHOW_SB_NEVER) + self.scr_workflow.SetMinSize((600,-1)) + + sizer_chapter = wx.BoxSizer( wx.HORIZONTAL ) + self.spn_scroll = wx.SpinButton( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_HORIZONTAL ) + sizer_scroll.Add( self.spn_scroll, 0, wx.ALL|wx.EXPAND, 3 ) + + for chapter in self.workflow['chapter']: + self.pan_chapter = wx.Panel( self.scr_workflow, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER|wx.TAB_TRAVERSAL ) + sizer_frame = wx.BoxSizer( wx.VERTICAL ) + + self.lab_chapter = wx.StaticText( self.pan_chapter, wx.ID_ANY, chapter['title'], wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + self.lab_chapter.Wrap( -1 ) + self.lab_chapter.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_INACTIVECAPTION ) ) + + sizer_frame.Add( self.lab_chapter, 0, wx.ALL|wx.EXPAND, 0 ) + + sizer_section = wx.BoxSizer( wx.HORIZONTAL ) + for section in chapter['section']: + btn = wx.Button( self.pan_chapter, wx.ID_ANY, section['title'], wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT ) + sizer_section.Add( btn, 0, wx.ALL, 3 ) + btn.Bind(wx.EVT_BUTTON, lambda e, x=section['title']: self.f(x)) + btn.Bind( wx.EVT_ENTER_WINDOW, lambda e, info=section['hint']: self.info(info)) + #self.m_button1.Bind( wx.EVT_LEAVE_WINDOW, self.on_out ) + + sizer_frame.Add( sizer_section, 0, wx.EXPAND, 3 ) + sizer_btn = wx.BoxSizer( wx.HORIZONTAL ) + sizer_btn.AddStretchSpacer(1) + + self.btn_snap = wx.StaticText( self.pan_chapter, wx.ID_ANY, u" Snap ", wx.DefaultPosition, wx.DefaultSize, 0|wx.SIMPLE_BORDER ) + self.btn_snap.Wrap( -1 ) + sizer_btn.Add( self.btn_snap, 0, wx.ALL, 3 ) + + self.btn_load = wx.StaticText( self.pan_chapter, wx.ID_ANY, u" Load ", wx.DefaultPosition, wx.DefaultSize, 0|wx.SIMPLE_BORDER ) + self.btn_load.Wrap( -1 ) + sizer_btn.Add( self.btn_load, 0, wx.ALL, 3 ) + + self.btn_step = wx.StaticText( self.pan_chapter, wx.ID_ANY, u" >> ", wx.DefaultPosition, wx.DefaultSize, 0|wx.SIMPLE_BORDER ) + self.btn_step.Wrap( -1 ) + sizer_btn.Add( self.btn_step, 0, wx.ALL, 3 ) + sizer_frame.Add( sizer_btn, 0, wx.EXPAND, 3 ) + + + self.pan_chapter.SetSizer( sizer_frame ) + self.pan_chapter.Layout() + sizer_frame.Fit( self.pan_chapter ) + sizer_chapter.Add( self.pan_chapter, 0, wx.EXPAND |wx.ALL, 3 ) + + sizer_scroll.Add( self.scr_workflow, 1, wx.EXPAND |wx.ALL, 0) + sizer_info = wx.BoxSizer( wx.VERTICAL ) + sizer_info.SetMinSize( wx.Size( 260,-1 ) ) + self.btn_help = wx.StaticText( self, wx.ID_ANY, u" Click For Detail Document ", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE|wx.SIMPLE_BORDER ) + self.btn_help.Wrap( -1 ) + self.btn_help.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_INACTIVECAPTION ) ) + + sizer_info.Add( self.btn_help, 0, wx.ALL|wx.EXPAND, 0 ) + + self.txt_info = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY ) + sizer_info.Add( self.txt_info, 1, wx.TOP|wx.EXPAND, 3 ) + + sizer_scroll.Add( sizer_info, 0, wx.EXPAND |wx.ALL, 3) + + self.scr_workflow.SetSizer( sizer_chapter ) + self.scr_workflow.Layout() + + self.SetSizer( sizer_scroll ) + + #self.Fit() + self.Layout() + + self.spn_scroll.Bind( wx.EVT_SPIN, self.on_spn ) + self.btn_help.Bind( wx.EVT_LEFT_DOWN, self.on_help ) + + def info(self, info): + self.txt_info.SetValue(info) + + def on_spn(self, event): + v = self.spn_scroll.GetValue() + self.scr_workflow.Scroll(v, 0) + self.spn_scroll.SetValue(self.scr_workflow.GetViewStart()[0]) + + def Bind(self, event, f=print): self.f = f + + def on_help(self, event): + self.app.show_md(self.cont, self.workflow['title']) + +if __name__ == '__main__': + + cont = '''Title +===== +## Chapter1 +1. Section1 +some coment for section1 ... +2. Section2 +some coment for section2 ... +## Chapter2 +1. Section1 +some coment for section1 ... +2. Section2 +some coment for section2 ... +''' + + app = wx.App() + frame = wx.Frame(None) + sizer = wx.BoxSizer(wx.VERTICAL) + wf = WorkFlowPanel(frame) + wf.SetValue(cont) + sizer.Add(wf, 0, wx.EXPAND, 0 ) + frame.SetSizer(sizer) + frame.Show() + app.MainLoop() \ No newline at end of file diff --git a/setup.py b/setup.py index c81cfcca..869c7955 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def get_data_files(): if __name__ == '__main__': setup(name='imagepy', - version='0.16', + version='0.22', url='https://github.com/Image-Py/imagepy', description='interactive python image-processing plugin framework', long_description=descr, @@ -22,11 +22,29 @@ def get_data_files(): author_email='yxdragon@imagepy.org', license='BSD 3-clause', packages=setuptools.find_packages(), + entry_points={ + 'console_scripts': [ + 'imagepy = imagepy:show', + ], + }, package_data=get_data_files(), install_requires=[ 'scikit-image', + 'scikit-learn', 'shapely', + 'pypubsub', 'wxpython', - 'numba' + 'read_roi', + 'numpy-stl', + 'pydicom', + 'pystackreg', + 'pandas', + 'xlrd', + 'xlwt', + 'openpyxl', + 'markdown', + 'python-markdown-math', + 'numba', + 'dulwich' ], )