|Line 18:||Line 18:|
* [[Dither]] for lsb hack 16 bit processing (v1.26.5 or higher)
* [[Dither]] for lsb hack 16 bit processing (v1.26.5 or higher)
* [[RgTools]] for contrasharp=true or prefilter=1 or 2
* [[RgTools]] for contrasharp=true or prefilter=1 or 2
* [[LSFmod]] for contrasharp > 0.0
* [[LSFmod]] for contrasharp > 0.0
* [[dfttest]] for prefilter=3
* [[dfttest]] for prefilter=3
Latest revision as of 05:09, 15 April 2021
|Author||Caroliano, Dogway, Real.Finder|
|Discussion||VideoHelp, Doom9, Doom9 mod|
SMDegrain, the Simple MDegrain Mod, is mainly a convenience function for using MVTools. It is not a magic function (the magic comes from mvtools), it just takes the repetitive code blocks needed for mvtools+mdegrain denoising and wraps them into this function.
It has internal switches for interlaced or YUY2 content (but you should not use it in avs 2.6 and avs+ and use YV16 instead), saving you from writing long lines of code and preventing from possible mistakes. YUY2 is even automatically detected. Apart of that it adds some small and simple enhancements listed below, like easy prefilters, slightly better motion vectors (thanks to an internal TV->PC luma expansion and the possibility of dark enhancement), easy nnedi3 subpixel accuracy, globals import/export, show panel, and contra-sharpening option.
- MVTools (mod versions recommended) and MaskTools2 (it's not needed if TV_range set to false)
- Dither for lsb hack 16 bit processing (v1.26.5 or higher)
- RgTools and MedianBlur2 for contrasharp=true or prefilter=1 or 2
- LSFmod for contrasharp > 0.0
- dfttest for prefilter=3
- nnedi3 for subpixel=3
Beware, tp7's plugin mods have no YUY2 support, require AviSynth 2.6a5 or latest Set's MT for v2.6, and Visual C++ 2012 Redistributable.
Original SMDegrain() by Caroliano
This function uses code from Didée, and cretindesalpes' creations, and has parts inspired by LaTo's functions (old readme, Show panel, etc) and Jawed's Killer() function, apart of relying on third-party tools for certain features.
Special thanks go to: cretindesalpes, Didée, Gavino, Sagekilla, and MVtools people.
The benefit of using SMDegrain over directly calling MDegrain is:
- High bitdepth with lsb denoising: This is the first wrapper function to support 32bit (16bit pipeline) MDegrain denoising.
- Higher temporal radius (tr>3): The MVTools mod (and hence this script) also allows for hack-free higher temporal radius.
- Contrasharpening: The pretty much standarized sharpening method created by Didée is blended into the function, so you can easily use it in 16bit pipelines. An strength biased method is also possible. A CClip parameter is also supplied so you can choose another source to sharpen from, other than the default SMDegrain() input.
- YUY2 support: It automatically and fully supports YUY2. (but that not important now after avs 2.6 and avs+)
- Interlaced support: It supports interlaced sources with most parameters as well.
- Extended Subpixel Accuracy: nnedi3 is allowed as a subpixel filter, not necessary in most cases, but if you are quality mad try to set it on.
- Better Motion Vectors: An internal conversion from TV levels (reduced values range) to PC levels (extended values range) is done to the clip the motion analysis will be performed on, so you can get better motion vectors (up to 16%)
- Prefilters: An easy parameter to load generalist prefilters that perform well in most situations. It also accepts loading your personal prefiltered clips.
- Motion Filters: New in version 3.0d. It will allow you to filter those parts where SMDegrain() couldn't find a matching block for denoising. One use for it would be spatial denoisers or deblockers since high-motion areas are prone to heavy blocking and artifacts.
- Global Motion Vector In/Out: Reuse motion vectors globals for faster processing, or just use SMDegrain() as a shortcut for creating nice quality motion vectors.
- RefineMotion Option (MRecalculate): Slower, but sometimes a big quality boost.
- Show Panel: Everything is off by default, but some settings change in context. Check what is happening behind the scenes and have a fast look at all your settings.
- Robust: Everything of the above works with each other no problem (interoperability), also in the process some error proof, bugfixes and bug workarounds were implemented or at least warned so you know that most of what is done is reliable, bugs free and optimized. The general idea is to serve as an user friendly front end for vanilla mvtools2+mdegrain or as the original script creator entitled "To make your scripts shorter and less geeky". Most parameters are turned off or defaulted to mvtools2 defaults and features are based on popular general conceived procedures.
Parameters are arranged in 3 blocks.
- Basic: For people who just need the most basic parameters because either they don't have the time, knowledge or interest into looking for complex settings. Or they are content with defaults (pretty much mvtools defaults)
- High Bit Depth (with lsb hack) ((you should not use it in avs+)): There's not much to think about this. This just enables (default is disabled), high bit depth denoising. That is, more quality, but also needs more processing power hence slower.
- Advanced: If you run into some problems, are nitpicking or just want to fine tune your settings according to the source, have a look at this block, it contains the rest and bulk of the function parameters.
Temporal denoising is a widespread procedure for noise cleaning in most type of video sources. The only limitation for SMDegrain() being when there are (very) high levels of grain, in which case you would need to rely on more "creative" and source centric solutions (see this and prefilter notes), but those are the rare cases.
To picture what the function does, a simple SMDegrain() call matches the next code (for SD resolutions):
super_search = Dither_Luma_Rebuild(S0=1.0,c=0.0625).MSuper(rfilter=4) bv2 = super_search.MAnalyse(isb = true, delta = 2, overlap= 4) bv1 = super_search.MAnalyse(isb = true, delta = 1, overlap= 4) fv1 = super_search.MAnalyse(isb = false, delta = 1, overlap= 4) fv2 = super_search.MAnalyse(isb = false, delta = 2, overlap= 4) MDegrain2(MSuper(levels=1), bv1, fv1, bv2, fv2, thSAD=300, thSADC=150)
As you see no wizardry. The only changes made are the TV->PC luma expansion (Dither_Luma_Rebuild(S0=1.0,c=0.0625) similar to color_yuv(levels="TV->PC")) so you have more range to detect motion vectors, the rfilter=4 for a bit more quality super search, the changed default of overlap from 0 to 4 and thSAD and thSADC from 400 to 300 and 150 respectively for safer results. Nothing else. The problem is that as soon you want to change a little thing like say temporal radius it requires you to edit in more than a few places, so this is very annoying and so forth the main reason for SMDegrain().
 Using the filter
- Input can be YV12 or YUY2, and must be TV range (which is normal). It's also recommended to be in modulus 16 size (resolution multiple of 16), although not strictly necessary.
- Crop first (when not interlaced).
- Recommended to deblock if blocking is present, before crop (when cropping) and SMDegrain(). Or you can use prefilter=3 which is also a good deblocker (dfttest) and let SMDegrain() clean the blocking for you.
- Default output mode(dithering) for lsb=true (when non interlaced) is ordered dither (optimized for encoding), so it is not recommended to keep filtering non-edge areas of your source, you can instead use mode=6 (error diffusion) or lsb_out=true and keep filtering in 16bit
- Read the prefilter setting explanation for solutions to problematic sources
 Ready-to-run Samples
I consider this a good starting point where to start tweaking, whether be it for quality or speed:
A basic, fast, yet very functional call for light grain (mvtools2+masktools2 only needed)
A typical more worked call compatible with official mvtools2 (mvtools2+masktools2 only needed)
Good compromise between speed and quality (denoising with high precision)
Similar to the previous example, the next is a personal preference. Turned all chroma off for safeness (chroma is rarely temporal-reliable enough) and speed.
This is a good example to show that interlaced YUY2 sources can be processed seamlessly.
# mod4 (modulus 4) interlaced YUY2 source SMDegrain(tr=1,thSAD=250,interlaced=true,contrasharp=true,lsb=true)
HD sources are detected automatically, and as so settings are optimized for speed; pel=1, hpad=0, vpad=0, blksize=32, overlap=16, truemotion=false
# HD source (from 1100px wide or 600px high up) SMDegrain(tr=2,thSAD=180,prefilter=2,contrasharp=30,refinemotion=true,lsb=true,chroma=false,plane=0)
For dealing with dark scenes (you could also add truemotion=false)
Tackling a grainy source:
Here 2 things are shown; how to work on a 16bit pipeline, and reference a prior state of the script for contrasharp.
sharp=last dfttest(tbsize=1,sigma=10,lsb=true) SMDegrain(tr=3,thSAD=300,CClip=sharp,lsb_in=true,lsb_out=true) LinearResize(854,480,lsb_in=true, mode=0)
(Re)using motion vectors globals
SMDegrain(tr=1,thSAD=400,prefilter=3,str=1.4,globals=3) # Output vectors only MFlowFps(Super, bv1, fv1, num=60,den=1)
SMDegrain (clip input, int "tr", int "thSAD", int "thSADC", bool "RefineMotion", val "contrasharp", clip "CClip", bool "interlaced", int "plane", int "Globals", int "pel", int "subpixel", val "prefilter", clip "mfilter", int "blksize", int "overlap", int "search", bool "truemotion", int "limit", int "limitc", int "thSCD1", int "thSCD2", bool "chroma", int "hpad", int "vpad", bool "lsb", bool "lsb_in", bool "lsb_out", int "mode", val "Show", float "Str", float "Amp")
tr int (1-128, default 2)
- Temporal radius, selects the MDegrain to use. This can be considered the strength of the denoising. Higher is generally better, but also much slower and each extra frame gives less improvement.
- tr 4+ requires Dither's MVTools2 mod. Max 64 for interlaced.
thSAD int = 300
thSADC int = 150
- "Sum of Absolute Differences" threshold. This is the spatial difference threshold where the motion search will consider whether to denoise given the formula:
- Denoise_Weight = max( 0, 1 - 2*blockSAD² / (thSAD² + blockSAD²) )
- This spatial difference will be compared in blocks (blksize). If your noise is not getting into consideration for the denoising try raising this value, or reduce its SAD with previous prefiltering. You can alternatively raise blksize, which will likely even blocks SAD. Low values can result in staggered/blotchy denoising, large values can result in ghosting and artifacts. Values in the range 200~600 are usual.
- thSADC is the same logic applied to chroma planes, since there are less differences on the chroma planes it uses by default thSAD/2 for safer chroma results.
- Recommendation: Use slightly greater values than you are used to in other mdegrain based functions since the internal clip for the motion search in SMDegrain() has the contrast increased.
Contrasharp bool (True/False, default False)
Contrasharp int (0-100, default -)
- Contrasharpening is a technique that compares the differences between the clip before blurring (original) and after blurring (filtered), and sharpens locally with consequent strength. By default the "sharp" clip is the input, the "after" clip is the denoised clip. Alternately, a "before" clip can be specified with CClip (See Advanced).
- True: use Didée's Contrasharpening() function (ContraHD() for HD) which "Sharpens the denoised clip, but doesn't add more to any pixel than what was removed previously." In the practice you will get a slightly sharper result than the source, which is welcome.
- An integer: LSFmod() will be used instead. It will be much slower, but maybe better for certain sources. Its value will serve as a contrasharpening multiplier, use one around 50 for similar strength as Contrasharpening(). LSFmod is less-suitable for HD sources.
RefineMotion bool = False
- Refines and recalculates motion data of previously estimated (by MAnalyse) motion vectors. Turn it on for better motion vectors, specially when dealing with ghosting issues, small details or lineart fading and whatnot.
- Caution: It won't help much if you use a prefilter so strong that blurs too much or kills all the details you are aiming to protect in first place.
plane int (0-4, default 4)
- Select the planes you wish to process:
- 0: luma only
- 1: chroma U
- 2: chroma V
- 3: both chromas
- 4: all (Default)
- Caution: plane=1-4 can sometimes create chroma smearing. In such case I recommend denoising chroma planes in the spatial domain.
Interlaced bool = False
- If you want to denoise an interlaced source set this parameter to true. Output will also be interlaced. If you pair this with lsb_out=true then you will have to Weave().DitherPost(mode=6,interlaced=true) when converting back to 8 bit.
 High Bit Depth Processing And Dithering
All High Bit Depth parameters require Dither and MVTools 2.6+. The AviSynth+ doesn't need these parameters; it automatically outputs the same bitdepth as the input, but if you still need 99% alternative to lsb family there are n16 (don't has n16_in because it don't make any sense in that case).
lsb_in bool = False
- Set true if you use a 16 bits filter chain. *(EXPERIMENTAL, use under test environment)
lsb_out bool = False
- Output to 16-bit instead of 8-bit. Use the helpers at the end of the SMDegrain() script to use Ditherpost() with YUY2 formats.
lsb bool = False
- This enables 32 bit depth precision for denoising. You gain extra denoising accuracy, most noticeable in the prevention of banding in flat areas. Automatically set if either lsb_in or lsb_out are enabled.
mode int (-1-8, default 0)
- This is the mode of DitherPost when lsb_out=False, as a dithering method must be chosen for the 32bit->8bit conversion. (Interlaced content is locked to mode=6) The default mode=0 will help you optimize the dithering for optimum encodings when no further non-edge processing is done. Use mode=6 (error diffusion) if further processing will be done.
pel int (1,2,4, default 2)
- Accuracy of the motion estimation:
- 1: full-pixel (Default for HD)
- 2: half-pixel (Default for SD)
- 4: quarter-pixel (often more accurate, but much slower and not always better)
subpixel int (0-3, default 2)
- Subpixel interpolation method for pel=2 or 4. This is the 'sharp' parameter in MSuper(), although it isn't related to output sharpness but better accuracy for the motion estimation.
- 0: bilinear (softest)
- 1: bicubic (4 tap Catmull-Rom)
- 2: sharper Wiener (6 tap, similar to Lanczos) (Default)
- 3: nnedi3 (very high quality: useful for small sources but usually overkill. requires plugin)
prefilter int (-1-3, default -1)
prefilter clip = None
- -1: off (Default)
- 0: light controlled gauss blur
- 1: mild median/gauss blur
- 2: strong median/gauss blur
- 3: dfttest (spatial)
- clip: supply your own
- Denoises a version of the clip that will be used to obtain the motion vectors. Useful for very damaged/grainy sources, this will help to get better motion vectors.
- For sources with Gibbs noise, especially on anime, try prefilter=1 or 2 or better yet strong median filters (through clip input) in order to soothe the high SAD of sharp edges. For blocky sources, or a general more quality/safer prefilter use option 3 (dfttest)
- By default dfttest outputs a sstring txt file to your script folder, you can delete it. This doesn't happen when using the modded dfttest (version 1.9.2 or higher) of the Dither tools.
- For HD sources prefilter=2 makes more sense than 1, because it uses a wider denoising window.
- If instead you want to use your own prefilter denoiser, just define it in a variable and reference it here. Remember to:
- Feed always 8 bit clips.
- Input in fields for interlaced content *(read below)
- Feed here planar ( by using Interleaved2Planar() ) YUY2 clips (not interleaved) when working with YUY2 sources.
- You will likely want to use spatial denoisers like median filters (removegrain=17, medianblur, etc). For very grainy or fizz grain noise you can try prefiltering in temporal or if this is not enough, try adding a new SMDegrain() line again but without globals. Use lsb_in, lsb_out according so results are smooth. You can even use SMDegrain() as a prefilter for SMDegrain() in very temporal unstable sources.
- Another trick for fizz grain is to serve a prefiltered clip where bright values are more denoised than dark values, you can do this through luma masks with masktools2 code. Search in Doom9 for reference.
- For interlaced sources remember:
- Spatial filtering: pre=separatefields().spatial_prefilters_here()
- Temporal filtering: pre=separatefields().interleave(selecteven().temporal_filters(),selectodd().temporal_filters())
- SMDegrain() (and MDegrain) is likely to produce blending artifacts or oversmooth in dark areas specially on cartoons, to tackle this see RefineMotion or expand the luma range in the darks. For this there are many options, you can use your preferred gamma enhancer and input it as the prefilter, or use the examples below for linear brightening (masktools2) and input it as a prefilter as well. Or at last and the recommended solution, to use the built-in Str (strength) and Amp (amplitude) parameters for a curve type dark enhancement, check the effects by enabling Show and changing your previewing conditions to PC levels.
- For brightening dark values linearly (brighter parts less brightening)
str=1 mt_lut("x x "+string(str)+" 6 * - 255 - abs 255 / ^ x 1 - abs +")
- Or for a more contrasty image (although not as much dark enhancing - not linear)
str=40 mt_lut("x x "+string(str)+" 6 * - 255 - abs 255 / ^ "+string(str)+" / x 1 - abs +")
- Good resources for custom prefilters:
- Notes: To sum it up; generally in denoising, temporal filters are always preferred due to its natural look (versus spatial filters). But more often than not for good results prefiltering plays a key role on the output quality, and setting it up nicely can be looked as an art by itself.
- If there's a reached point where no prefiltering, no thSAD tweaking, etc, makes able to denoise a certain motion part/area/scene, then this is due to the motion vectors preventing them from being "denoised" (also read "artifacted"), in which case you will need to rely on alternative solutions (motion compensation, filters like MCTemporalDenoise(), TemporalDegrain(), Multilevel MDegrain, etc) or just use plain spatial filters with motion masks. Creating these discerning masks for static and motion scenes and combining them is a research task on the end user side.
mfilter clip = None
- Motion Filter, an optional user-supplied clip that will be passed through those areas where SMDegrain() couldn't find a matching block, normally high motion areas. One use for it would be to to use spatial filters, like deblockers or blurring filters that mimic motion blur.
Str float (0.0-8.0, default 1.0)
- Gamma correction. With this parameter you control the strength of the brightening of the prefilter clip, good for when problems with dark areas arise. Using this internally instead of externally creates less quantization artifacts, since it's done in the same stage as the TV->PC range conversion.
Amp float (0.0-1.0, default 0.0625)
- Used when Str <> 1.0. This defines the amplitude of the brightening in the luma range, for example by using 1.0 all the luma range will be used and the brightening will find its peak at luma value 128 in the original. Default is 0.0625 (1.0/16) which just sits over luma value 16 (256/16) This and Str are extrapolated arguments from cretindesalpes' function, you can find a graph and more insight explanations at the original post.
- Check these 2 parameters effects by enabling show.
blksize int (4,8,16, default 8 for SD, 16 for HD)
- Size of a block (horizontal x vertical). Larger blocks are faster and less sensitive to noise, but also less accurate.
overlap int = blksize/2
- Must be *even* and *less* than block size. Common values: blksize/4 or blksize/2. Larger overlap looks slightly better and runs slower.
Search int (0-7, default 4)
- This is the search of MAnalyze.
Truemotion bool = True for SD, False for HD
- Truemotion is a preset of some MVAnalyze parameter values. It allows easy to switch default values of all "true motion" parameters at once. Set it 'true' for true motion search (high vector coherence), set it 'false' to search motion vectors with best SAD. Truemotion is slower and may blur textures and thin details more, but will usually remove more noise.
- For HD sources where the main problems are just excessive grain, I have defaulted it to false, this is not only faster but honors detail accuracy (the main existing point for HD sources) and overall object shading.
Chroma bool = True
- Set false to totally ignore chroma for finding motion vectors, for more speed with little cost in quality or when your chroma is too bad to find good matches. Don't process chroma planes without chroma vectors, MVTools doesn't seem to work right.
Hpad int = blksize for SD, 0 for HD
Vpad int = blksize for SD, 0 for HD
- Horizontal/vertical padding added to source frame (left, right, top, and bottom) for better motion estimation near borders.
- Try to have clean borders in your source (no NAB/black borders) to start with before tweaking this setting. If necessary, crop beforehand ideally in multiples of 16. Turn it to 0 if you are running out of memory.
thSCD1 int = based on blksize: 4=
- Threshold which decides whether a block has changed between the previous frame and the current one, used to tweak the scene change detection. Raising it will lower the number of blocks detected as changed. It may be useful for noisy or flickered video. To fix blending on scene changes on dark scenes, first try Str and Amp.
thSCD2 int (0-250, default 130)
- Threshold which sets how many blocks have to change for the frame to be considered as a scene change.
limit int (1-255, default 255)
limitC int (1-255, default 255)
- Maximal change of pixel luma/chroma, to prevent some artifacts.
CClip clip = None
- Optional sharp version for contrasharp to compare against; video resolution and properties must be the same (different bit depth is OK). Using this option defaults contrasharp to True.
Globals int (0-3, default 0)
- With this parameter you can create or load pre-processed motion vectors, to save doing the work for multiple SMDegrain calls.
- 0: Ignore globals, just process
- 1: Read globals and Process
- 2: Process and output globals
- 3: Output globals only, don't process
- Some parameters MUST MATCH between output and input stages: pel, subpixel, chroma, and vpad/hpad cannot change. When reading (Globals=1) only some parameters work: tr, thSAD, plane, limit, limitc, contrasharp, CClip, interlaced and the lsb parameters. Others are ignored.
- Global names that can be reused are: Super, bv1, fv1, bv2, fv2, bv3, fv3, bv4, fv4, bv6, fv6, vmulti.
SMDegrain(tr=3,thSAD=400,globals=3) # Outputs vectors SMDegrain(tr=3,thSAD=400,globals=1) # You can use a lower "tr" or "thSAD" if you want
SMDegrain(tr=3,thSAD=400,globals=3) # Outputs vectors Super = MSuper(levels=1) # Add this line just before if you have some processing between Globals Output and Input. MDegrain3(Super, bv1, fv1, bv2, fv2, bv3, fv3, thSAD=400)
Show bool = False
Show str ("Speed", "Memory", "Quality", default -)
- This will show the prefiltered clip to be used for the motion search to the left(<--), and the used parameters list to the right (-->)
- If the parameters panel is broken i.e. overlapping lines, hidden lines, etc, most likely your settings are wrong, (e.g. lsb_in=true when input is 8bit) so it will also work well as a debug function. If you set Show to a string as indicated above, related parameters will be highlighted to guide you on correctly tweaking the function. This is loosely based, so it doesn't exclude you from reading carefully every parameter explanation.
- Even when you are not using any prefiltering, the clip to be used for the motion search where you obtain the motion vectors, will be converted from TV levels to PC levels, the only exception being when using Globals=1 (Read) in which case the "prefiltered" clip will be exactly the same as input clip (that is no luma conversion, although constrained to 8 bit). Additionally you can tweak the dark expansion with Str and Amp parameters and check the effects in the left panel, but be aware to change your viewing conditions as it is in PC levels.
- The left panel preserves its properties so you can crop out the right panel and use it as a prefilter clip of any MSuper(). Therefore if you are not processing chroma (chroma=false) the prefiltered clip will be green, don't panic, this is correct, this happens when the U and V planes are unset. In this regard for previewing tasks with prefilterings -- or just the Str and Amp effect -- set chroma to true temporally or follow with GreyScale(). For YUY2 sources set Planar2Interleaved() after cropping for previewing.
 Final Notes
If there is an important parameter not implemented, you have any issue or found a bug, please don't hesitate and ask in its VideoHelp thread.
Add Prefilter auto 16 (lsb) support
Fixed regression where external prefilters wouldn't be parsed for luma expansion
Workaround to force KNLMeansCL use discrete video card (now required)
New prefilter mode 4 for grainy sources. GPU based spatio-temporal KNLmeans
Adjusted SD<>HD discretion
Removed one residual variable in Contrasharpening function
Fixed Refinemotion Globals Input labels
Fixed and optimized chroma handling for lsb_in, contrasharpening and YUY2 content v.1.95
Added truemotion parameter (default=true), change it to false (as in MCTemporalDenoise()), so low frequency detail won't be lost (walls...), although it could leave more noise on high frequency details
Added automatic dark protection for prefilter=3 (dfttest performs badly in darks) v1.71d and extended its dark protection v1.8
Fixed and extended introduction explanation v.1.61d
|1.6d||2011-12-24 (first open release)||
Fixed and extended introduction explanation
Changed "srchSuper" Global variable to "QTGMC_srchSuper", compatible with QTGMC. Anyhow you may probably want to create a new super clip. Test it out! v.1.41d
Added pelclip option for top quality subpixel interpolation when pel > 1
|1.3d||2011-09-14||Fixed an important issue on the Interlaced parameter and improved overall handling (v.1.21d & v.1.3d)|
Added Interlaced parameter, for processing interlaced sources
Implemented MDegrainN for tr > 3, from the MVTools2 mod of Dither
Added CClip parameter. Reference an earlier stage of your processing chain as your sharp version for the contrasharpening
Added QTGMCV parameter, for reusing vectors from QTGMC bob deinterlacer
Fixed a few things related to the lsb_in option
Implemented "fake" lsb_in option. Output will have slightly more quality (if any), and will compress better. (Experimental)
Minor introduction and code optimization tweaks
Added dfttest option for preblur (now prefilter) option. Slower but better due to its internal deblock+denoise operation, plus sharper results
Added preblur option for slight vector blurring where motionmatch is bad (https://forum.doom9.org/showthread.php?t=161594)
First Mod version
Added introductory section
Basic working version