├── .gitignore
├── README.md
├── COPYING.LGPLv3.0
└── Vine.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vine
2 | ©2017 IFeelBloated, Vine Python Module for VapourSynth
3 |
4 | ## License
5 | LGPL v3.0
6 |
7 | ## Description
8 | Vine is a collection of a block/pixel matching based de-halo filter and a set of morphological filters.
9 |
10 | ## Requirements
11 | - [KNLMeansCL](https://github.com/Khanattila/KNLMeansCL)
12 | - [BM3D](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D)
13 | - [TCanny](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-TCanny)
14 | - [FMTConv](https://github.com/EleonoreMizo/fmtconv)
15 | - [MVTools (floating point ver)](https://github.com/IFeelBloated/vapoursynth-mvtools-sf/tree/master)
16 | - [NNEDI3](https://github.com/dubhater/vapoursynth-nnedi3)
17 |
18 | ## Function List
19 | ### De-halo Filters
20 | - Super
21 | - Basic
22 | - Final
23 |
24 | ### Morphological Filters
25 | - Dilation
26 | - Erosion
27 | - Closing
28 | - Opening
29 | - Gradient
30 | - TopHat
31 | - BlackHat
32 |
33 | ## Formats
34 | - Bit Depth: 32bits floating point
35 | - Color Space: Gray, RGB, YUV4XXPS
36 | - Scan Type: Progressive
37 |
38 | ## Notes
39 | - Only Y will be processed when the input is YUV, UV will be simply copied from the input
40 | - RGB input will be converted to an opponent color space(YUV alike) and only luma will be processed
41 | - **NO** scene change policy provided, take [Wobbly](https://github.com/dubhater/Wobbly) and cut each scene out and process them individually
42 | - **QUALITY (Dehalo)**: cutting edge
43 | - **PERFORMANCE (Dehalo)**: pretty slow but practical
44 |
45 | ## Details
46 | ### Dehalo Filters
47 | Dehalo removes halo artifacts caused by over-sharpening or sinc-like resizers or stuff like that
48 | #### Super
49 | Optional, it helps improve the precision of sub-pixel motion estimation and compensation, use it and get a quality boost or don't and get a performance boost
50 | ```python
51 | Super(src, pel=4)
52 | ```
53 | - src
54 | clip to be processed
55 | - pel
56 | sub-pixel precision, could be 2 or 4, 2 = precision by half a pixel, 4 = precision by quarter a pixel.
57 |
58 | #### Basic
59 | The basic estimation does a Non-Local Errors filtering to remove all visible halos.
60 |
61 | workflow:
62 | - apply a degraded (local mean/similarity window = 0) NLMeans filter to kill halos, it's not NLMeans technically, it weights on the error between 2 pixels instead of SSE between 2 blocks, which works ultra nice on halos
63 | - like the classic aliasing (nearest neighbor) and ringing (sinc) trade-off, non-local errors filtering annihilates halos and brings aliasing, so do it again with supersampling and clean the aliasing mess, the supersampled result will be blended with the result before supersampling, weight is determined by the "sharp" parameter
64 | - a cutoff filter replaces low frequencies of the filtered clip with low frequencies from the source clip since halos are medium to high frequency artifacts apparently
65 | - non-local errors might distort high frequency components since it does not make use of the neighborhood at all, especially with a large "h", so do an actual NLMeans here to refine high frequencies and therefore remove artifacts caused by non-local errors
66 |
67 | ```python
68 | Basic(src, a=32, h=6.4, sharp=1.0, cutoff=4)
69 | ```
70 | - src
71 | clip to be processed
72 | - a
73 | window size of the non-local errors filtering, greater value = higher quality and lower performance
74 | - h
75 | strength of the non-local errors filtering, greater value = more intense processing
76 | - sharp
77 | resampling sharpness of the anti-aliasing process, also related to the blending process mentioned above, blending weight = *constant* * sharp * ln(1 + 1 / (*constant* * sharp)), the mathematical limit for weight is 0 (simply returns the resampled result) as sharp goes infinitely close to 0, or 1 (simply returns the clip before resampling) as sharp goes towards infinity
78 | - cutoff
79 | strength of the cutoff filter, ranges from 0 (no low frequency protection) to 100 (almost no filtering)
80 |
81 | #### Final
82 | The final estimation refines the basic estimation with motion compensation and outputs the final result
83 |
84 | workflow:
85 | - refine the basic estimation with motion compensation
86 | - apply a modified canny detection to mask out edges with a big possibility to have halos around
87 | - mask halos out by doing morphological operations to the canny mask
88 | - replace masked areas in the source clip with the filtered clip
89 |
90 | ```python
91 | Final(src, super=[None, None], radius=[6, 1, None], pel=4, sad=400.0, sigma=0.6, alpha=0.36, beta=32.0, masking=True, show=False)
92 | ```
93 | - super
94 | optional, clips generated by Vine.Super
95 | - radius
96 | radius[0]: temporal radius of the motion compensation, frames that fall in [current frame - radius, current frame + radius] will be referenced
97 | radius[1]: exact radius of the halo mask
98 | radius[2]: peripheral(inflating) radius of the halo mask, default radius[2] = ceil(radius[1] / 2)
99 | - sad
100 | SAD threshold of the motion compensation, refer to MVTools doc for more details
101 | - sigma
102 | refer to TCanny doc for more details
103 | - alpha, beta
104 | so halos occur at fairly sharp transitions, and we want weak and insignificant edges that got no or little halos around gone, and that we should re-scale the gradient of the canny mask, and these 2 parameters are related to that process, say *x* is the value of some pixel in the mask and it will be scaled to *(x + alpha)^beta-alpha^beta*, basically any value < *1-alpha* will be close to 0 after that, so larger alpha = more edges
105 | - masking
106 | set it False and get a raw output with no mask protection, for very large radius halos
107 | - show
108 | set it True and the output will be the halo mask, for debugging and stuff
109 |
110 | ### Morphology Filters
111 | ```python
112 | Dilation/Erosion/Closing/Opening/Gradient/TopHat/BlackHat(src, radius=1)
113 | ```
114 | - [Dilation](https://en.wikipedia.org/wiki/Dilation_(morphology))
115 | - [Erosion](https://en.wikipedia.org/wiki/Erosion_(morphology))
116 | - [Closing](https://en.wikipedia.org/wiki/Closing_(morphology))
117 | - [Opening](https://en.wikipedia.org/wiki/Opening_(morphology))
118 | - [Gradient](https://en.wikipedia.org/wiki/Morphological_gradient)
119 | - [Top Hat and Black Hat](https://en.wikipedia.org/wiki/Top-hat_transform)
120 |
121 | ## Demos
122 | - Do a morphological gradient operation and get a simple edge mask
123 | ```python
124 | clip = Vine.Gradient(clip)
125 | ```
126 | 
127 | 
128 | - typical halo
129 | ```python
130 | #removing over/undershoot
131 | ref = Vine.Basic(clip, h=27.72)
132 | clip = Vine.Final([clip, ref], [Vine.Super(clip), Vine.Super(ref)], [6, 0, 0], sigma=1.5, alpha=0.06)
133 | #removing halos
134 | ref = Vine.Basic(clip, h=10.39)
135 | clip = Vine.Final([clip, ref], [Vine.Super(clip), Vine.Super(ref)], [6, 1, 4], sigma=1.5, alpha=0.06)
136 | ```
137 | 
138 | 
139 |
140 | *zoomed to 400%*
141 | *click the image and view at full size*
142 | 
143 | - analog video kind of severe and gross halo
144 | ```python
145 | ref = Vine.Basic(clip, h=31.18, sharp=0.48)
146 | ref = Vine.Basic(ref, h=13.86, sharp=0.16)
147 | clip = Vine.Final([clip, ref], [Vine.Super(clip), Vine.Super(ref)], [6, 2, 4], sigma=2.2, alpha=0.18)
148 | ```
149 | 
150 | 
151 |
--------------------------------------------------------------------------------
/COPYING.LGPLv3.0:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/Vine.py:
--------------------------------------------------------------------------------
1 | import vapoursynth as vs
2 | import math
3 |
4 | fmtc_args = dict(fulls=True, fulld=True)
5 | msuper_args = dict(hpad=0, vpad=0, sharp=2, levels=0, chroma=False)
6 | manalyze_args = dict(search=3, truemotion=False, trymany=True, levels=0, badrange=-24, divide=0, dct=0, chroma=False)
7 | mrecalculate_args = dict(truemotion=False, search=3, smooth=1, divide=0, dct=0, chroma=False)
8 | mdegrain_args = dict(plane=0, thscd1=16711680.0, thscd2=255.0)
9 | canny_args = dict(mode=1, op=0)
10 | nnedi_args = dict(field=1, dh=True, nns=4, qual=2, etype=1, nsize=0)
11 |
12 | class get_core:
13 | def __init__(self):
14 | self.MSuper = vs.core.mvsf.Super
15 | self.MAnalyze = vs.core.mvsf.Analyze
16 | self.MRecalculate = vs.core.mvsf.Recalculate
17 | self.MDegrain = vs.core.mvsf.Degrain
18 | self.KNLMeansCL = vs.core.knlm.KNLMeansCL
19 | self.Canny = vs.core.tcanny.TCanny
20 | self.NNEDI = vs.core.nnedi3.nnedi3
21 | self.RGB2OPP = vs.core.bm3d.RGB2OPP
22 | self.OPP2RGB = vs.core.bm3d.OPP2RGB
23 | self.Resample = vs.core.fmtc.resample
24 | self.Maximum = vs.core.std.Maximum
25 | self.Minimum = vs.core.std.Minimum
26 | self.Expr = vs.core.std.Expr
27 | self.Merge = vs.core.std.Merge
28 | self.MakeDiff = vs.core.std.MakeDiff
29 | self.MergeDiff = vs.core.std.MergeDiff
30 | self.Crop = vs.core.std.CropRel
31 | self.AddBorders = vs.core.std.AddBorders
32 | self.Transpose = vs.core.std.Transpose
33 | self.Inflate = vs.core.std.Inflate
34 | self.MaskedMerge = vs.core.std.MaskedMerge
35 | self.ShufflePlanes = vs.core.std.ShufflePlanes
36 | self.SetFieldBased = vs.core.std.SetFieldBased
37 |
38 | def CutOff(self, low, hi, p):
39 | def inline(src):
40 | upsmp = self.Resample(src, src.width*2, src.height*2, kernel="gauss", a1=100, **fmtc_args)
41 | clip = self.Resample(upsmp, src.width, src.height, kernel="gauss", a1=p, **fmtc_args)
42 | return clip
43 | hif = self.MakeDiff(hi, inline(hi))
44 | clip = self.MergeDiff(inline(low), hif)
45 | return clip
46 |
47 | def Pad(self, src, left, right, top, bottom):
48 | w = src.width
49 | h = src.height
50 | clip = self.Resample(src, w+left+right, h+top+bottom, -left, -top, w+left+right, h+top+bottom, kernel="point", **fmtc_args)
51 | return clip
52 |
53 | def NLMeans(self, src, a, s, h, rclip):
54 | pad = self.AddBorders(src, a+s, a+s, a+s, a+s)
55 | rclip = self.AddBorders(rclip, a+s, a+s, a+s, a+s) if rclip is not None else None
56 | nlm = self.KNLMeansCL(pad, d=0, a=a, s=s, h=h, rclip=rclip)
57 | clip = self.Crop(nlm, a+s, a+s, a+s, a+s)
58 | return clip
59 |
60 | def XYClosest(self, src1, src2, ref):
61 | clip = self.Expr([src1, src2, ref], "x z - abs y z - abs > y x ?")
62 | return clip
63 |
64 | class internal:
65 | def dilation(core, src, radius):
66 | for i in range(radius):
67 | src = core.Maximum(src)
68 | return src
69 |
70 | def erosion(core, src, radius):
71 | for i in range(radius):
72 | src = core.Minimum(src)
73 | return src
74 |
75 | def closing(core, src, radius):
76 | clip = internal.dilation(core, src, radius)
77 | clip = internal.erosion(core, clip, radius)
78 | return clip
79 |
80 | def opening(core, src, radius):
81 | clip = internal.erosion(core, src, radius)
82 | clip = internal.dilation(core, clip, radius)
83 | return clip
84 |
85 | def gradient(core, src, radius):
86 | erosion = internal.erosion(core, src, radius)
87 | dilation = internal.dilation(core, src, radius)
88 | clip = core.Expr([dilation, erosion], "x y -")
89 | return clip
90 |
91 | def tophat(core, src, radius):
92 | opening = internal.opening(core, src, radius)
93 | clip = core.Expr([src, opening], "x y -")
94 | return clip
95 |
96 | def blackhat(core, src, radius):
97 | closing = internal.closing(core, src, radius)
98 | clip = core.Expr([src, closing], "y x -")
99 | return clip
100 |
101 | def super(core, src, pel):
102 | src = core.Pad(src, 128, 128, 128, 128)
103 | clip = core.Transpose(core.NNEDI(core.Transpose(core.NNEDI(src, **nnedi_args)), **nnedi_args))
104 | if pel == 4:
105 | clip = core.Transpose(core.NNEDI(core.Transpose(core.NNEDI(clip, **nnedi_args)), **nnedi_args))
106 | return clip
107 |
108 | def basic(core, src, a, h, sharp, cutoff):
109 | c1 = 0.3926327792690057290863679493724
110 | c2 = 18.880334973195822973214959957208
111 | c3 = 0.5862453661304626725671053478676
112 | weight = c1 * sharp * math.log(1.0 + 1.0 / (c1 * sharp))
113 | h_refine = c2 * math.pow(h / c2, c3)
114 | upsampled = core.Transpose(core.NNEDI(core.Transpose(core.NNEDI(src, **nnedi_args)), **nnedi_args))
115 | upsampled = core.NLMeans(upsampled, a, 0, h, None)
116 | resampled = core.Resample(upsampled, src.width, src.height, sx=-0.5, sy=-0.5, kernel="cubic", a1=-sharp, a2=0)
117 | clean = core.NLMeans(src, a, 0, h, None)
118 | clean = core.Merge(resampled, clean, weight)
119 | clean = core.CutOff(src, clean, cutoff)
120 | dif = core.MakeDiff(src, clean)
121 | dif = core.NLMeans(dif, a, 1, h_refine, clean)
122 | clip = core.MergeDiff(clean, dif)
123 | return clip
124 |
125 | def final(core, src, super, radius, pel, sad, sigma, alpha, beta, masking, show):
126 | constant = 0.0009948813682897925944723492342
127 | me_sad = constant * math.pow(sad, 2.0) * math.log(1.0 + 1.0 / (constant * sad))
128 | if masking:
129 | mask = core.Canny(src[1], sigma=sigma, **canny_args)
130 | mask = core.Expr(mask, "x {alpha} + {beta} pow {gamma} - 0.0 max 1.0 min".format(alpha=alpha, beta=beta, gamma=math.pow(alpha, beta)))
131 | expanded = internal.dilation(core, mask, radius[1])
132 | closed = internal.closing(core, mask, radius[1])
133 | mask = core.Expr([expanded, closed, mask], "x y - z +")
134 | for i in range(radius[2]):
135 | mask = core.Inflate(mask)
136 | if show:
137 | return mask
138 | for i in range(2):
139 | src[i] = core.Pad(src[i], 128, 128, 128, 128)
140 | src[1] = core.MakeDiff(src[1], src[0])
141 | super[1] = core.MakeDiff(super[1], super[0]) if super[0] is not None and super[1] is not None else None
142 | blankdif = core.Expr(src[0], "0.0")
143 | supersearch = core.MSuper(src[0], pelclip=super[0], rfilter=4, pel=pel, **msuper_args)
144 | superdif = core.MSuper(src[1], pelclip=super[1], rfilter=2, pel=pel, **msuper_args)
145 | vmulti = core.MAnalyze(supersearch, radius=radius[0], overlap=64, blksize=128, **manalyze_args)
146 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=32, blksize=64, thsad=me_sad, **mrecalculate_args)
147 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=16, blksize=32, thsad=me_sad, **mrecalculate_args)
148 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=8, blksize=16, thsad=me_sad, **mrecalculate_args)
149 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=4, blksize=8, thsad=me_sad, **mrecalculate_args)
150 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=2, blksize=4, thsad=me_sad, **mrecalculate_args)
151 | vmulti = core.MRecalculate(supersearch, vmulti, overlap=1, blksize=2, thsad=me_sad, **mrecalculate_args)
152 | averaged_dif = core.MDegrain(src[1], superdif, vmulti, thsad=sad, **mdegrain_args)
153 | averaged_dif = core.XYClosest(averaged_dif, src[1], blankdif)
154 | averaged_dif = core.Crop(averaged_dif, 128, 128, 128, 128)
155 | src[0] = core.Crop(src[0], 128, 128, 128, 128)
156 | clean = core.MergeDiff(src[0], averaged_dif)
157 | clip = core.MaskedMerge(src[0], clean, mask) if masking else clean
158 | return clip
159 |
160 | def Super(src, pel=4):
161 | if not isinstance(src, vs.VideoNode):
162 | raise TypeError("Vine.Super: src has to be a video clip!")
163 | elif src.format.sample_type != vs.FLOAT or src.format.bits_per_sample < 32:
164 | raise TypeError("Vine.Super: the sample type of src has to be single precision!")
165 | if not isinstance(pel, int):
166 | raise TypeError("Vine.Super: pel has to be an integer!")
167 | elif pel != 2 and pel != 4:
168 | raise RuntimeError("Vine.Super: pel has to be 2 or 4!")
169 | core = get_core()
170 | src = core.SetFieldBased(src, 0)
171 | colorspace = src.format.color_family
172 | if colorspace == vs.RGB:
173 | src = core.RGB2OPP(src, 1)
174 | if colorspace != vs.GRAY:
175 | src = core.ShufflePlanes(src, 0, vs.GRAY)
176 | clip = internal.super(core, src, pel)
177 | del core
178 | return clip
179 |
180 | def Basic(src, a=32, h=6.4, sharp=1.0, cutoff=4):
181 | if not isinstance(src, vs.VideoNode):
182 | raise TypeError("Vine.Basic: src has to be a video clip!")
183 | elif src.format.sample_type != vs.FLOAT or src.format.bits_per_sample < 32:
184 | raise TypeError("Vine.Basic: the sample type of src has to be single precision!")
185 | if not isinstance(a, int):
186 | raise TypeError("Vine.Basic: a has to be an integer!")
187 | elif a < 1:
188 | raise RuntimeError("Vine.Basic: a has to be greater than 0!")
189 | if not isinstance(h, float) and not isinstance(h, int):
190 | raise TypeError("Vine.Basic: h has to be a real number!")
191 | elif h <= 0:
192 | raise RuntimeError("Vine.Basic: h has to be greater than 0!")
193 | if not isinstance(sharp, float) and not isinstance(sharp, int):
194 | raise TypeError("Vine.Basic: sharp has to be a real number!")
195 | elif sharp <= 0.0:
196 | raise RuntimeError("Vine.Basic: sharp has to be greater than 0!")
197 | if not isinstance(cutoff, int):
198 | raise TypeError("Vine.Basic: cutoff has to be an integer!")
199 | elif cutoff < 1 or cutoff > 100:
200 | raise RuntimeError("Vine.Basic: cutoff must fall in (0, 100]!")
201 | core = get_core()
202 | src = core.SetFieldBased(src, 0)
203 | colorspace = src.format.color_family
204 | if colorspace == vs.RGB:
205 | src = core.RGB2OPP(src, 1)
206 | if colorspace != vs.GRAY:
207 | src = core.ShufflePlanes(src, 0, vs.GRAY)
208 | clip = internal.basic(core, src, a, h, sharp, cutoff)
209 | del core
210 | return clip
211 |
212 | def Final(src, super=[None, None], radius=[6, 1, None], pel=4, sad=400.0, sigma=0.6, alpha=0.36, beta=32.0, masking=True, show=False):
213 | if not isinstance(src, list):
214 | raise TypeError("Vine.Final: src has to be an array!")
215 | elif len(src) != 2:
216 | raise RuntimeError("Vine.Final: src has to contain 2 elements exactly!")
217 | elif not isinstance(src[0], vs.VideoNode) or not isinstance(src[1], vs.VideoNode):
218 | raise TypeError("Vine.Final: elements in src must be video clips!")
219 | elif src[0].format.sample_type != vs.FLOAT or src[0].format.bits_per_sample < 32:
220 | raise TypeError("Vine.Final: the sample type of src[0] has to be single precision!")
221 | elif src[1].format.id != vs.GRAYS:
222 | raise RuntimeError("Vine.Final: corrupted basic estimation!")
223 | if not isinstance(super, list):
224 | raise TypeError("Vine.Final: super has to be an array!")
225 | elif len(super) != 2:
226 | raise RuntimeError("Vine.Final: super has to contain 2 elements exactly!")
227 | for i in range(2):
228 | if not isinstance(super[i], vs.VideoNode) and super[i] is not None:
229 | raise TypeError("Vine.Final: elements in super must be video clips or None!")
230 | elif super[i] is not None:
231 | if super[i].format.id != vs.GRAYS:
232 | raise RuntimeError("Vine.Final: corrupted super clips!")
233 | if not isinstance(radius, list):
234 | raise TypeError("Vine.Final: radius parameter has to be an array!")
235 | elif len(radius) != 3:
236 | raise RuntimeError("Vine.Final: radius parameter has to contain 3 elements exactly!")
237 | for i in range(2):
238 | if not isinstance(radius[i], int):
239 | raise TypeError("Vine.Final: radius[" + str(i) + "] has to be an integer!")
240 | if radius[0] <= 0:
241 | raise RuntimeError("Vine.Final: radius[0] has to be greater than 0!")
242 | if radius[1] < 0:
243 | raise RuntimeError("Vine.Final: radius[1] has to be no less than 0!")
244 | if not isinstance(radius[2], int) and radius[2] is not None:
245 | raise TypeError("Vine.Final: radius[2] has to be an integer or None!")
246 | elif radius[2] is not None:
247 | if radius[2] < 0:
248 | raise RuntimeError("Vine.Final: radius[2] has to be no less than 0!")
249 | if not isinstance(pel, int):
250 | raise TypeError("Vine.Final: pel has to be an integer!")
251 | elif pel != 1 and pel != 2 and pel != 4:
252 | raise RuntimeError("Vine.Final: pel has to be 1, 2 or 4!")
253 | if not isinstance(sad, float) and not isinstance(sad, int):
254 | raise TypeError("Vine.Final: sad has to be a real number!")
255 | elif sad <= 0:
256 | raise RuntimeError("Vine.Final: sad has to be greater than 0!")
257 | if not isinstance(alpha, float) and not isinstance(alpha, int):
258 | raise TypeError("Vine.Final: alpha has to be a real number!")
259 | elif alpha < 0.0 or alpha > 1.0:
260 | raise RuntimeError("Vine.Final: alpha must fall in [0.0, 1.0]!")
261 | if not isinstance(beta, float) and not isinstance(beta, int):
262 | raise TypeError("Vine.Final: beta has to be a real number!")
263 | elif beta <= 1.0:
264 | raise RuntimeError("Vine.Final: beta has to be greater than 1.0!")
265 | if not isinstance(masking, bool):
266 | raise TypeError("Vine.Final: masking has to be boolean!")
267 | if not isinstance(show, bool):
268 | raise TypeError("Vine.Final: show has to be boolean!")
269 | if not masking and show:
270 | raise RuntimeError("Vine.Final: masking has been disabled, set masking True to show the halo mask!")
271 | radius[2] = math.ceil(radius[1] / 2) if radius[2] is None else radius[2]
272 | core = get_core()
273 | for i in range(2):
274 | src[i] = core.SetFieldBased(src[i], 0)
275 | super[i] = core.SetFieldBased(super[i], 0) if super[i] is not None else None
276 | colorspace = src[0].format.color_family
277 | if colorspace == vs.RGB:
278 | src[0] = core.RGB2OPP(src[0], 1)
279 | if colorspace != vs.GRAY:
280 | src_color = src[0]
281 | src[0] = core.ShufflePlanes(src[0], 0, vs.GRAY)
282 | clip = internal.final(core, src, super, radius, pel, sad, sigma, alpha, beta, masking, show)
283 | if colorspace != vs.GRAY:
284 | clip = core.ShufflePlanes([clip, src_color], [0, 1, 2], vs.YUV)
285 | if colorspace == vs.RGB:
286 | clip = core.OPP2RGB(clip, 1)
287 | del core
288 | return clip
289 |
290 | def Dilation(src, radius=1):
291 | if not isinstance(src, vs.VideoNode):
292 | raise TypeError("Vine.Dilation: src has to be a video clip!")
293 | if not isinstance(radius, int):
294 | raise TypeError("Vine.Dilation: radius has to be an integer!")
295 | elif radius < 1:
296 | raise RuntimeError("Vine.Dilation: radius has to be greater than 0!")
297 | core = get_core()
298 | src = core.SetFieldBased(src, 0)
299 | clip = internal.dilation(core, src, radius)
300 | del core
301 | return clip
302 |
303 | def Erosion(src, radius=1):
304 | if not isinstance(src, vs.VideoNode):
305 | raise TypeError("Vine.Erosion: src has to be a video clip!")
306 | if not isinstance(radius, int):
307 | raise TypeError("Vine.Erosion: radius has to be an integer!")
308 | elif radius < 1:
309 | raise RuntimeError("Vine.Erosion: radius has to be greater than 0!")
310 | core = get_core()
311 | src = core.SetFieldBased(src, 0)
312 | clip = internal.erosion(core, src, radius)
313 | del core
314 | return clip
315 |
316 | def Closing(src, radius=1):
317 | if not isinstance(src, vs.VideoNode):
318 | raise TypeError("Vine.Closing: src has to be a video clip!")
319 | if not isinstance(radius, int):
320 | raise TypeError("Vine.Closing: radius has to be an integer!")
321 | elif radius < 1:
322 | raise RuntimeError("Vine.Closing: radius has to be greater than 0!")
323 | core = get_core()
324 | src = core.SetFieldBased(src, 0)
325 | clip = internal.closing(core, src, radius)
326 | del core
327 | return clip
328 |
329 | def Opening(src, radius=1):
330 | if not isinstance(src, vs.VideoNode):
331 | raise TypeError("Vine.Opening: src has to be a video clip!")
332 | if not isinstance(radius, int):
333 | raise TypeError("Vine.Opening: radius has to be an integer!")
334 | elif radius < 1:
335 | raise RuntimeError("Vine.Opening: radius has to be greater than 0!")
336 | core = get_core()
337 | src = core.SetFieldBased(src, 0)
338 | clip = internal.opening(core, src, radius)
339 | del core
340 | return clip
341 |
342 | def Gradient(src, radius=1):
343 | if not isinstance(src, vs.VideoNode):
344 | raise TypeError("Vine.Gradient: src has to be a video clip!")
345 | if not isinstance(radius, int):
346 | raise TypeError("Vine.Gradient: radius has to be an integer!")
347 | elif radius < 1:
348 | raise RuntimeError("Vine.Gradient: radius has to be greater than 0!")
349 | core = get_core()
350 | src = core.SetFieldBased(src, 0)
351 | clip = internal.gradient(core, src, radius)
352 | del core
353 | return clip
354 |
355 | def TopHat(src, radius=1):
356 | if not isinstance(src, vs.VideoNode):
357 | raise TypeError("Vine.TopHat: src has to be a video clip!")
358 | if not isinstance(radius, int):
359 | raise TypeError("Vine.TopHat: radius has to be an integer!")
360 | elif radius < 1:
361 | raise RuntimeError("Vine.TopHat: radius has to be greater than 0!")
362 | core = get_core()
363 | src = core.SetFieldBased(src, 0)
364 | clip = internal.tophat(core, src, radius)
365 | del core
366 | return clip
367 |
368 | def BlackHat(src, radius=1):
369 | if not isinstance(src, vs.VideoNode):
370 | raise TypeError("Vine.BlackHat: src has to be a video clip!")
371 | if not isinstance(radius, int):
372 | raise TypeError("Vine.BlackHat: radius has to be an integer!")
373 | elif radius < 1:
374 | raise RuntimeError("Vine.BlackHat: radius has to be greater than 0!")
375 | core = get_core()
376 | src = core.SetFieldBased(src, 0)
377 | clip = internal.blackhat(core, src, radius)
378 | del core
379 | return clip
380 |
--------------------------------------------------------------------------------