[HttpPost] [AllowUploadImagesOnly(".jpg,.gif,.png")] public virtual ActionResult ImageUpload(HttpPostedFileBase file) { var newFileName = Server.MapPath(Path.Combine(Links.SiteContents.Upload.Url(), file.FileName)); file.SaveAs(newFileName); var array = new { filelink = newFileName }; return Json(array, MediaTypeNames.Text.Plain, JsonRequestBehavior.AllowGet); }
برای طراحی یک صفحه modal چهار div باید اضافه شوند. بیرونیترین div باید دارای کلاس modal مجموعه Bootstrap باشد. میتوان به کلاس modal در اینجا کلاسهای hide fade را هم برای نمونه اضافه کرد. در این حالت، نمایش و بسته شدن صفحه modal به همراه پویانمایی ویژهای خواهد بود.
داخل این div، سه div با کلاسهای modal-header برای نمایش هدر، modal-body برای نمایش محتوایی در این صفحه modal و modal-footer برای تدارک محتوای footer این صفحه، قرار خواهند گرفت.
در این بین هر لینکی با ویژگی data-dismiss=modal، سبب بسته شدن خودکار صفحه باز شده خواهد شد.
افزونه bootstrapModalConfirm
اگر نکات یاد شده را بخواهیم کپسوله کنیم، میتوان یک افزونه جدید جیکوئری را با نام فایل jquery.bootstrap-modal-confirm.js برای این منظور تدارک دید:
// <![CDATA[ (function ($) { $.bootstrapModalConfirm = function (options) { var defaults = { caption: 'تائید عملیات', body: 'آیا عملیات درخواستی اجرا شود؟', onConfirm: null, confirmText: 'تائید', closeText: 'انصراف' }; var options = $.extend(defaults, options); var confirmContainer = "#confirmContainer"; var html = '<div class="modal hide fade" id="confirmContainer">' + '<div class="modal-header">' + '<a class="close" data-dismiss="modal">×</a>' + '<h5>' + options.caption + '</h5></div>' + '<div class="modal-body">' + options.body + '</div>' + '<div class="modal-footer">' + '<a href="#" class="btn btn-success" id="confirmBtn">' + options.confirmText + '</a>' + '<a href="#" class="btn" data-dismiss="modal">' + options.closeText + '</a></div></div>'; $(confirmContainer).remove(); $(html).appendTo('body'); $(confirmContainer).modal('show'); $('#confirmBtn', confirmContainer).click(function () { if (options.onConfirm) options.onConfirm(); $(confirmContainer).modal('hide'); }); }; })(jQuery); // ]]>
مثالی از استفاده از افزونه bootstrapModalConfirm
@{ ViewBag.Title = "Index"; } <h2> Index</h2> <a href="#" class="btn btn-danger" id="deleteBtn">حذف رکورد</a> @section JavaScript { <script type="text/javascript"> $(function () { $("#deleteBtn").click(function (e) { e.preventDefault(); //میخواهیم لینک به صورت معمول عمل نکند $.bootstrapModalConfirm({ caption: 'تائید عملیات', body: 'آیا عملیات درخواستی اجرا شود؟', onConfirm: function () { alert('در حال انجام عملیات'); }, confirmText: 'تائید', closeText: 'انصراف' }); }); }); </script> }
جدول بوتاسترپی
ویژگیها
1- داکیومنت مناسب + ایجکسی بودن
2- قابلیت نگهداری صفحه(اگر در صفحهی 6 مرورگر را ببندید. در بازدید بعدی باز هم همان صفحهی 6 نمایش داده میشود).
3- خروجی اکسل و ورد و...(pdf برای زبان فارسی مناسب نیست.)(با جاوااسکریپت کار میکنه)
4- نمایش متفاوت در گوشیها( هر ستون به یک سطر تبدیل میشه)
5- فیلتر کردن و جستجوی کلی(بدون زحمت)(نگهداری فیلتر و جستجو در بازدید بعدی)
6- افزودن دکمه در هر سلول با توجه به نیاز
7- کم زیاد کردن ستونها توسط کاربر و نگهداری تغییرات برای بازدید بعدی
8- فریز کردن عنوان جدول در زمان ارتفای زیاد
9- نمایش جدول در داخل جدول
10-تغییر اندازه ستون ها- مرتب سازی هر ستون- امکان استفاده از چند فیلتر با هم
var query = new BooleanQuery(); query.Add(new BooleanClause(new TermQuery(new Term("id", id.ToString(CultureInfo.InvariantCulture))), Occur.SHOULD)); // query.Add(... // query.Add(... // ... writer.DeleteDocuments(query);
نحوه ارتقاء برنامههای موجود MVC3 به MVC4
- برای سایر حالات سطر ذیل که در کدهای فوق موجود است:
var newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify());
<# if (!mvcHost.IsContentPage) { #> <# } } #> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> ×</button> <#= mvcHost.ViewDataType.Name #> </div> @using (Html.BeginForm()) { <div class="modal-body"> @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) <fieldset class="form-horizontal"> <legend><#= mvcHost.ViewDataType.Name #></legend> <# foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) { if (!property.IsPrimaryKey && !property.IsReadOnly && property.Scaffold) { #> <div class="control-group"> <# if (property.IsForeignKey) { #> @Html.LabelFor(model => model.<#= property.Name #>, "<#= property.AssociationName #>",new {@class="control-label"}) <# } else { #> @Html.LabelFor(model => model.<#= property.Name #>,new {@class="control-label"}) <# } #> <div class="controls"> <# if (property.IsForeignKey) { #> @Html.DropDownList("<#= property.Name #>", String.Empty) <# } else { #> @Html.EditorFor(model => model.<#= property.Name #>) <# } #> @Html.ValidationMessageFor(model => model.<#= property.Name #>,null,new{@class="help-inline"}) </div> </div> <# } } #> </fieldset> </div> <div class="modal-footer"> <button class="btn btn-primary" type="submit"> ارسال</button> <button class="btn" data-dismiss="modal" aria-hidden="true"> انصراف</button> </div> }
حالا تنها مشکلم نحوه نمایش گروه محصولات داخل DropDownList است.
1) دریافت کتابخانههای لازم
نیاز به کتابخانههای Lucene.NET و همچنین Lucene.Net Contrib است که هر دو مورد را به سادگی توسط NuGet میتوانید دریافت و نصب کنید.
Highlighter استفاده شده، در کتابخانه Lucene.Net Contrib قرار دارد. به همین جهت این مورد را نیز باید جداگانه دریافت کرد.
2) تهیه منبع داده
در اینجا جهت سادگی کار فرض کنید که لیستی از مطالب را به فرمت زیر دراختیار داریم:
public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } }
3) تبدیل اطلاعات به فرمت Lucene.NET
همانطور که عنوان شد نیاز است هر رکورد از اطلاعات خود را به شیء Document نگاشت کنیم. نمونهای از اینکار را در متد ذیل مشاهده مینمائید:
static Document MapPostToDocument(Post post) { var postDocument = new Document(); postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); postDocument.Add(new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); postDocument.Add(new Field("Body", post.Body, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); return postDocument; }
کار با ایجاد یک وهله از شیء Document شروع شده و سپس اطلاعات به صوت فیلدهایی به این سند اضافه میشوند.
توضیحات آرگومانهای مختلف سازنده کلاس Field:
- در ابتدا نام فیلد مورد نظر ذکر میگردد.
- سپس مقدار متناظر با آن فیلد، به صورت رشته باید معرفی شود.
- آرگومان سوم آن مشخص میکند که اصل اطلاعات نیز علاوه بر ایندکس شدن باید در فایلهای Lucene ذخیره شوند یا خیر. توسط Field.Store.YES مشخص میکنیم که بله؛ علاقمندیم تا اصل اطلاعات نیز از طریق Lucene قابل بازیابی باشند. این مورد جهت نمایش سریع نتایج جستجوها میتواند مفید باشد. اگر قرار نیست اطلاعاتی را از این فیلد خاص به کاربر نمایش دهید میتوانید از گزینه Field.Store.NO استفاده کنید. همچنین امکان فشرده سازی اطلاعات ذخیره شده با انتخاب گزینه Field.Store.COMPRESS نیز میسر است.
- توسط آرگومان چهارم آن تعیین خواهیم کرد که اطلاعات فیلد مورد نظر ایندکس شوند یا خیر. مقدار Field.Index.NOT_ANALYZED سبب عدم ایندکس شدن فیلد Id میشوند (چون قرار نیست روی id در قسمت جستجوی عمومی سایت، جستجویی صورت گیرد). به کمک مقدار Field.Index.ANALYZED، مقدار معرفی شده، ایندکس خواهد شد.
- پارامتر پنجم آنرا جهت سرعت عمل در نمایان سازی/برجسته کردن و highlighting عبارات جستجو شده در متنهای یافت شده معرفی کردهایم. الگوریتمهای متناظر با این روش در فایلهای Lucene.Net Contrib قرار دارند.
یک نکته
اگر اطلاعاتی را که قرار است ایندکس کنید از نوع HTML میباشند، بهتر است تمام تگهای آنرا پیش از افزودن به لوسین حذف کنید. به این ترتیب نتایج جستجوی دقیقتری را میتوان شاهد بود. برای این منظور میتوان از متد ذیل کمک گرفت:
public static string RemoveHtmlTags(string text) { return string.IsNullOrEmpty(text) ? string.Empty : Regex.Replace(text, @"<(.|\n)*?>", string.Empty); }
4) تهیه Full text index به کمک Lucene.NET
تا اینجا توانستیم اطلاعات خود را به فرمت اسناد لوسین تبدیل کنیم. اکنون ثبت و تبدیل آنها به فایلهای Full text search لوسین به سادگی زیر است:
static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_29; public static void CreateIdx(IEnumerable<Post> dataList) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { foreach (var post in dataList) { writer.AddDocument(MapPostToDocument(post)); } writer.Optimize(); writer.Commit(); writer.Close(); directory.Close(); } }
ذکر version در اینجا ضروری است؛ از این جهت که اگر ایندکسی با فرمت مثلا LUCENE_29 تهیه شود ممکن است با نگارش بعدی این کتابخانه سازگار نباشد و در صورت ارتقاء، نتایج جستجوی انجام شده، کاملا بیربط نمایش داده شوند. با ذکر صریح نگارش، دیگر این اتفاق رخ نخواهد داد.
نکته
StandardAnalyzer توکار لوسین، امکان دریافت لیستی از واژههایی که نباید ایندکس شوند را نیز دارا است. اطلاعات بیشتر در اینجا.
5) به روز رسانی ایندکسها
به کمک سه متد ذیل میتوان اطلاعات ایندکسهای موجود را به روز یا حذف کرد:
public static void UpdateIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { var newDoc = MapPostToDocument(post); indexWriter.UpdateDocument(new Term("Id", post.Id.ToString()), newDoc); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } } public static void DeleteIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { indexWriter.DeleteDocuments(new Term("Id", post.Id.ToString())); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } } public static void AddIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version, getStopWords()); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { var searchQuery = new TermQuery(new Term("Id", post.Id.ToString())); indexWriter.DeleteDocuments(searchQuery); var newDoc = MapPostToDocument(post); indexWriter.AddDocument(newDoc); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } }
محل فراخوانی این متدها هم میتواند در کنار متدهای به روز رسانی اطلاعات اصلی در بانک اطلاعاتی برنامه باشند. اگر رکوردی اضافه یا حذف شده، ایندکس متناظر نیز باید به روز شود.
6) جستجو در اطلاعات ایندکس شده و نمایش آنها به همراه نمایان/برجسته سازی عبارات جستجو شده
قسمت نهایی کار با لوسین و اطلاعات ایندکسهای تهیه شده، کوئری گرفتن از آنها است. متدهای کامل مورد نیاز را در ذیل مشاهده میکنید:
public static void Query(string term) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); using (var searcher = new IndexSearcher(directory, readOnly: true)) { var analyzer = new StandardAnalyzer(_version); var parser = new MultiFieldQueryParser(_version, new[] { "Body", "Title" }, analyzer); var query = parseQuery(term, parser); var hits = searcher.Search(query, 10).ScoreDocs; if (hits.Length == 0) { term = searchByPartialWords(term); query = parseQuery(term, parser); hits = searcher.Search(query, 10).ScoreDocs; } FastVectorHighlighter fvHighlighter = new FastVectorHighlighter(true, true); foreach (var scoreDoc in hits) { var doc = searcher.Doc(scoreDoc.doc); string bestfragment = fvHighlighter.GetBestFragment( fvHighlighter.GetFieldQuery(query), searcher.GetIndexReader(), docId: scoreDoc.doc, fieldName: "Body", fragCharSize: 400); var id = doc.Get("Id"); var title = doc.Get("Title"); var score = scoreDoc.score; Console.WriteLine(bestfragment); } searcher.Close(); directory.Close(); } } private static Query parseQuery(string searchQuery, QueryParser parser) { Query query; try { query = parser.Parse(searchQuery.Trim()); } catch (ParseException) { query = parser.Parse(QueryParser.Escape(searchQuery.Trim())); } return query; } private static string searchByPartialWords(string bodyTerm) { bodyTerm = bodyTerm.Replace("*", "").Replace("?", ""); var terms = bodyTerm.Trim().Replace("-", " ").Split(' ') .Where(x => !string.IsNullOrEmpty(x)) .Select(x => x.Trim() + "*"); bodyTerm = string.Join(" ", terms); return bodyTerm; }
اکثر سایتها را که بررسی کنید، جستجوی بر روی یک فیلد را توضیح دادهاند. در اینجا نحوه جستجو بر روی چند فیلد را به کمک MultiFieldQueryParser ملاحظه میکنید.
نکتهی مهمی را هم که در اینجا باید به آن دقت داشت، حساس بودن لوسین به کوچکی و بزرگی نام فیلدهای معرفی شده است و در صورت عدم رعایت این مساله، جستجوی شما نتیجهای را دربر نخواهد داشت.
در ادامه برای parse اطلاعات، از متد کمکی parseQuery استفاده شده است. ممکن است به ParseException بخاطر یک سری حروف خاص بکارگرفته شده در عبارات مورد جستجو برسیم. در اینجا میتوان توسط متد QueryParser.Escape، اطلاعات دریافتی را اصلاح کرد.
سپس نحوه استفاده از کوئری تهیه شده و متد Search را ملاحظه میکنید. در اینجا بهتر است تعداد رکوردهای بازگشت داده شده را تعیین کرد (به کمک آرگومان دوم متد جستجو) تا بیجهت سرعت عملیات را پایین نیاورده و همچنین مصرف حافظه سیستم را نیز بالا نبریم.
ممکن است تعداد hits یا نتایج حاصل صفر باشد؛ بنابراین بد نیست خودمان دست به کار شده و به کمک متد searchByPartialWords، ورودی کاربر را بر اساس زبان جستجوی ویژه لوسین اندکی بهینه کنیم تا بتوان به نتایج بهتری دست یافت.
در آخر نحوه کار با ScoreDocs یافت شده را ملاحظه میکنید. اگر محتوای فیلد را در حین ایندکس سازی ذخیره کرده باشیم، به کمک متد doc.Get میتوان به اطلاعات کامل آن نیز دست یافت.
همچنین نکته دیگری را که در اینجا میتوان ملاحظه کرد استفاده از FastVectorHighlighter میباشد. به کمک این Highlighter ویژه میتوان نتایج جستجو را شبیه به نتایج نمایش داده شده توسط موتور جستجوی گوگل درآورد. برای مثال اگر شخصی ef code first را جستجو کرد، توسط متد GetBestFragment، بهترین جزئی که شامل بیشترین تعداد حروف جستجو شده است، یافت گردیده و همچنین به کمک تگهای B، ضخیم نمایش داده خواهند شد.
برای آغاز به کار با این دیتابیس ابتدا باید آن را از سایت اصلی دریافت و بر روی سیستم نصب نمایید. متاسفانه سایت مونگو برای کشور ایران محدودیتی قرار داده است و باید از روشهای دیگری آن را دریافت نمایید و بر روی سیستم خود نصب نمایید. نحوه نصب این دیتابیس را میتوانید در مقاله MongoDb#3 مشاهده نمایید.
شاید نیاز باشد بجای کار کردن با محیط کنسول این دیتابیس، با یک محیط گرافیکی شبیه آن چیزی که Raven دارد کار کنید وتغییرات را مشاهده نمایید؛ برای همین به این آدرس رفته و محیط دلخواه خود را انتخاب نمایید.
یک پروژه از نوع کنسول را در ویژوال استادیو ایجاد کنید و سپس درایور رسمی مونگو را از این آدرس یا از طریق nuget نصب نمایید:
Install-Package mongocsharpdriver
ابتدا سه مدل را به شکل زیر ایجاد میکنیم:
public class Author { public ObjectId Id { get; set; } public string Name { get; set; } }
public class Language { public ObjectId Id { get; set; } public string Name { get; set; } }
public class Book { public ObjectId Id { get; set; } public string Title { get; set; } public string ISBN { get; set; } public int Price { get; set; } public List<Author> Authors { get; set; } public Language Language { get; set; } }
نوع ObjectId، نوعی است که توسط مونگو برای مشخص کردن کلید یکتای سند معرفی میشود.
در خطوط اولیه کد زیر، یک شیء از مدل بالا را ساخته و آن را مقداردهی میکنیم:
var book =new Book() { Title = "Gone With Wind", ISBN = "43442424", Price = 50000, Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Margaret Mitchell" }, new Author() { Name = "Ali Mahboobi (Translator)" }, } };
بعد از آن یک شیء کلاینت از نوع mongoClient میسازیم که نوع خروجی آن یک اینترفیس میباشد که توسط کلاسی از جنس آن مقداردهی شده است. بیشتر خروجیهای مونگو در این کتابخانه از نوع اینترفیس هستند. شیء کلاینت وظیفه دارد تا ارتباط شما را با سرور مونگو برقرار کند:
var client = new MongoClient();
string connectionString = "mongodb://localhost:27017"; MongoClientSettings settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); var client = new MongoClient(settings);
در قسمت بعد لازم است که از سرور جاری، دیتابیس خود را دریافت کنیم. در صورتیکه دیتابیس درخواستی وجود نداشته باشد، یک دیتابیس جدید با آن نام ساخته خواهد شد:
var db = client.GetDatabase("publisher");
در مونگو اصطلاحی به نام collection وجود دارد که اسناد در آن قرار گرفته و ارتباط با اسناد از طریق آنها انجام میپذیرد. پس در اینجا قبل از هر کاری باید یک collection را ایجاد کرد و در صورتیکه کالکشن درخواستی وجود نداشته باشد، آن را تولید و ارتباط با آن را برخواهد گرداند.
var collection = db.GetCollection<Book>("books");
در اینجا کالکشنی با نام books با تبدیلاتی بر اساس مدل Book ایجاد میشود. در مرحله بعد لازم است که شیء ایجاد شده بر اساس کلاس مدل را با استفاده از متدهای insert شیء کالکشن، در دیتابیس ارسال کنیم.
شیءهای درج یک سند جدید به دیتابیس حالات مختلفی را دارد: افزودن تک سند، افزودن چند سند و دو مورد قبلی به صورت غیر همزمان میباشند:
collection.InsertOneAsync(book);
فعلا موجودیتهای مؤلفان و زبان به دلیل اینکه سند اختصاصی برای خود ندارند، با صفر پر شدهاند؛ ولی شناسه یکتای سند، مقدار خود را گرفته است.
عملیات خواندن
قبل از هر چیزی برای اینکه در مانور دادن بر روی دادهها راحت باشیم و اطلاعات را با فیلترهای متفاوتی واکشی کنیم، 7 عدد کتاب را با مشخصات زیر اضافه میکنیم. دو فیلد سال و تاریخ آخرین موجودی انبار را هم اضافه میکنیم.
var client = new MongoClient(); var db = client.GetDatabase("publisher"); db.DropCollection("books"); var collection = db.GetCollection<Book>("books"); var book =new Book() { Title = "Gone With Wind", ISBN = "43442424", Price = 50000, Year = 1936, LastStock = DateTime.Now.AddDays(-13), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Margaret Mitchell" }, new Author() { Name = "Ali Mahboobi (Translator)" }, } }; var book2 = new Book() { Title = "Jane Eyre", ISBN = "87897897", Price = 60000, Year = 1847, LastStock = DateTime.Now.AddDays(-5), Language = new Language() { Name = "English" }, Authors = new List<Author>() { new Author() { Name = "Charlotte Brontë" }, } }; var book3 = new Book() { Title = "White Fang", ISBN = "43442424", Price = 50000, Year = 1936, LastStock = DateTime.Now.AddDays(-13), Language = new Language() { Name = "English" }, Authors = new List<Author>() { new Author() { Name = "Jack London" }, new Author() { Name = "Philippe Mignon" }, } }; var book4 = new Book() { Title = "The Lost Symbol", ISBN = "43442424", Price = 3500000, Year = 2009, LastStock = DateTime.Now.AddDays(-17), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Dan Brown" }, new Author() { Name = "Mehrdad" }, } }; var book7 = new Book() { Title = "The Lost Symbol", ISBN = "43442424", Price = 47000000, Year = 2009, LastStock = DateTime.Now.AddDays(-56), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Dan Brown" }, new Author() { Name = "Mehrdad" }, } }; var book5= new Book() { Title = "The Help", ISBN = "45345e3er3", Price = 9000000, Year = 2009, LastStock = DateTime.Now.AddDays(-2), Language = new Language() { Name = "Enlish" }, Authors = new List<Author>() { new Author() { Name = "Kathryn Stockett" }, } }; var book6 = new Book() { Title = "City of Glass", ISBN = "454534545", Price = 500000, Year = 2009, LastStock = DateTime.Now, Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Cassandra Clare" }, new Author() { Name = "Ali" }, } }; var books = new List<Book> {book, book2, book3, book4, book5, book6,book7}; collection.InsertManyAsync(books);
برای واکشی دیتاها کالکشنی از آن نوع را همانند قبل درخواست میکنیم. بعد از آن نیاز است که فیلتری برای واکشی اطلاعات تعریف کنیم که این فیلتر در قالب یک کلاس به نام BsonDocument ایجاد میشود که ما در اینجا، به دلیل اینکه میخواهیم همه اسناد را واکشی کنیم ، این سند Bson را مقداردهی نمیکنیم و توسط متد Find آن را در واکشی دیتاها شرکت میدهیم و سپس با صدا زدن متد ToList، عملیات واکشی را انجام میدهیم، برای اینکار میتوانیم از عملیات غیرهمزمان هم استفاده کنیم.
var client = new MongoClient(); var db = client.GetDatabase("publisher"); var collection = db.GetCollection<Book>("books"); var filter=new BsonDocument(); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
با اجرای کد بالا به نتایج زیر میرسیم:
Gone With Wind By Margaret Mitchell Jane Eyre By Charlotte Brontë White Fang By Jack London The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
اگر بخواهید فیلتری را بر روی این واکشی قرار دهید و مثلا بخواهید کتابهای منتشر شده در سال 2009 را واکشی نمایید، باید این سند Bson را مقداردهی نمایید. ولی برای راحتی اینکار، این کتابخانه شامل یک بیلدر Builder بوده که میتوان از طریق آن فیلترهای متنوعی را به صورت سادهتر طراحی کنید:
در خطوط بالا ابتدا یک بیلدر را برای کلاس مورد نظر ایجاد کرده و از خصوصیت Filter آن استفاده میکنیم و این خصوصیت شامل متدهای فراوانی است که میتوانید برای ایجاد شرط یا فیلتر استفاده کنید. تعدادی از متدهای پر استفاده آن همانند eq (برابری) ، gt (برزگتر از ...) ، gte (بزرگتر مساوی ...) و طبیعتا خانواده lt و ... موجود هستند.
var filter = Builders<Book>.Filter.Eq("Year", 2009); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
// var filter=new BsonDocument(); var filterBuilder = Builders<Book>.Filter; var filter= filterBuilder.Eq("Year", 2009) | filterBuilder.Gte("Price",700000); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
Gone With Wind By Margaret Mitchell White Fang By Jack London The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
برای اینکه بتوانید از linq به جای queryBuilder استفاده کنید، میتوانید از خصوصیت AsQueryable استفاده کنید. خط زیر همان شرط یا فیلتر بالا را توسط Linq اعمال میکند
var docs = collection.AsQueryable().Where(x => x.Year == 2009 || x.Price <= 50000).ToList();
Sort کردن دادهها
برای مرتب سازی اطلاعات به شیوه کوئری بیلدر، همانند فیلتر که از کلاس Builder استفاده میکردیم، از همین شیء استفاده میکنیم؛ با این تفاوت که بجای استفاده از خصوصیت Filter، از Sort استفاده میکنیم و شیء ایجاد شده را به متد Sort میدهیم:
var sort = Builders<Book>.Sort.Ascending("Title").Descending("Price"); var docs = collection.Find(filter).Sort(sort).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
City of Glass By Cassandra Clare Gone With Wind By Margaret Mitchell The Help By Kathryn Stockett The Lost Symbol By Dan Brown The Lost Symbol By Dan Brown White Fang By Jack London
توجه داشته باشید که متد sort بعد از فیلتر گذاری، یعنی عمل Find در دسترس میباشد.
در قسمت بعدی به روزرسانی، حذف و ایندکس گذاری را مورد بررسی قرار میدهیم.
OpenCVSharp #18
این مطلب را میتوان به عنوان جمع بندی مطالبی که تاکنون بررسی شدند درنظر گرفت و در اساس مطلب جدیدی ندارد و صرفا ترکیب یک سری تکنیک است؛ برای مثال:
چطور یک تصویر را به نمونهی سیاه و سفید آن تبدیل کنیم؟
کار با متد Threshold جهت بهبود کیفیت یک تصویر جهت تشخیص اشیاء
تشخیص کانتورها (Contours) و اشیاء موجود در یک تصویر
آشنایی با نحوهی گروه بندی تصاویر مشابه و مفاهیمی مانند برچسبهای تصاویر که بیانگر یک گروه از تصاویر هستند.
تهیه تصاویر اعداد انگلیسی جهت آموزش دادن به الگوریتم CvKNearest
در اینجا نیز از یکی دیگر از الگوریتمهای machine learning موجود در OpenCV به نام CvKNearest برای تشخیص اعداد انگلیسی استفاده خواهیم کرد. این الگوریتم نزدیکترین همسایهی اطلاعاتی مفروض را در گروهی از دادههای آموزش داده شدهی به آن پیدا میکند. خروجی آن شمارهی این گروه است. بنابراین نحوهی طبقهی بندی اطلاعات در اینجا چیزی شبیه به شکل زیر خواهد بود:
مجموعهای از تصاویر 0 تا 9 را جمع آوری کردهایم. هر کدام از پوشهها، بیانگر اعدادی از یک خانواده هستند. این تصویر را با فرمت ذیل جمع آوری میکنیم:
public class ImageInfo { public Mat Image { set; get; } public int ImageGroupId { set; get; } public int ImageId { set; get; } }
public IList<ImageInfo> ReadTrainingImages(string path, string ext) { var images = new List<ImageInfo>(); var imageId = 1; foreach (var dir in new DirectoryInfo(path).GetDirectories()) { var groupId = int.Parse(dir.Name); foreach (var imageFile in dir.GetFiles(ext)) { var image = processTrainingImage(new Mat(imageFile.FullName, LoadMode.GrayScale)); if (image == null) { continue; } images.Add(new ImageInfo { Image = image, ImageId = imageId++, ImageGroupId = groupId }); } } return images; }
private static Mat processTrainingImage(Mat gray) { var threshImage = new Mat(); Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour Point[][] contours; HiearchyIndex[] hierarchyIndexes; Cv2.FindContours( threshImage, out contours, out hierarchyIndexes, mode: ContourRetrieval.CComp, method: ContourChain.ApproxSimple); if (contours.Length == 0) { return null; } Mat result = null; var contourIndex = 0; while ((contourIndex >= 0)) { var contour = contours[contourIndex]; var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour var roi = new Mat(threshImage, boundingRect); //Crop the image //Cv2.ImShow("src", gray); //Cv2.ImShow("roi", roi); //Cv.WaitKey(0); var resizedImage = new Mat(); var resizedImageFloat = new Mat(); Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10 resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float result = resizedImageFloat.Reshape(1, 1); contourIndex = hierarchyIndexes[contourIndex].Next; } return result; }
ابتدا تصویر اصلی بارگذاری میشود؛ همان تصویر سمت چپ. سپس با استفاده از متد Threshold، شدت نور نواحی مختلف آن یکسان شده و آماده میشود برای تشخیص کانتورهای موجود در آن. در ادامه با استفاده از متد FindContours، شیء مرتبط با عدد جاری یافت میشود. سپس متد Cv2.BoundingRect مستطیل دربرگیرندهی این شیء را تشخیص میدهد (تصویر سمت راست). بر این اساس میتوان تصویر اصلی ورودی را به یک تصویر کوچکتر که صرفا شامل ناحیهی عدد مدنظر است، تبدیل کرد. در ادامه برای کار با الگوریتم CvKNearest نیاز است تا این تصویر بهبود یافته را تبدیل به یک ماتریس یک بعدی کردی که روش انجام کار توسط متد Reshape مشاهده میکنید.
از همین روش پردازش و بهبود تصویر ورودی، جهت پردازش اعداد یافت شدهی در یک تصویر با تعداد زیادی عدد نیز استفاده خواهیم کرد.
آموزش دادن به الگوریتم CvKNearest
تا اینجا تصاویر گروه بندی شدهای را خوانده و لیستی از آنها را مطابق فرمت الگوریتم CvKNearest تهیه کردیم. مرحلهی بعد، معرفی این لیست به متد Train این الگوریتم است:
public CvKNearest TrainData(IList<ImageInfo> trainingImages) { var samples = new Mat(); foreach (var trainingImage in trainingImages) { samples.PushBack(trainingImage.Image); } var labels = trainingImages.Select(x => x.ImageGroupId).ToArray(); var responses = new Mat(labels.Length, 1, MatType.CV_32SC1, labels); var tmp = responses.Reshape(1, 1); //make continuous var responseFloat = new Mat(); tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert to float var kNearest = new CvKNearest(); kNearest.Train(samples, responseFloat); // Train with sample and responses return kNearest; }
سپس نیاز است لیست گروههای متناظر با تصاویر اعداد را تبدیل به فرمت مورد انتظار متد Train کنیم. در اینجا صرفا لیستی از اعداد صحیح را داریم. این لیست نیز باید تبدیل به یک Mat شود که روش انجام آن در متد فوق بیان شدهاست. کلاس Mat سازندهی مخصوصی را جهت تبدیل لیست اعداد، به همراه دارد. این Mat نیز باید تبدیل به یک ماتریس یک بعدی شود که برای این منظور از متد Reshape استفاده شدهاست.
انجام عملیات OCR نهایی
پس از تهیهی لیستی از تصاویر و آموزش دادن آنها به الگوریتم CvKNearest، تنها کاری که باید انجام دهیم، یافتن اعداد در تصویر نمونهی مدنظر و سپس معرفی آن به متد FindNearest الگوریتم CvKNearest است. روش انجام اینکار بسیار شبیه است به روش معرفی شده در متد processTrainingImage که پیشتر بررسی شد:
public void DoOCR(CvKNearest kNearest, string path) { var src = Cv2.ImRead(path); Cv2.ImShow("Source", src); var gray = new Mat(); Cv2.CvtColor(src, gray, ColorConversion.BgrToGray); var threshImage = new Mat(); Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour Point[][] contours; HiearchyIndex[] hierarchyIndexes; Cv2.FindContours( threshImage, out contours, out hierarchyIndexes, mode: ContourRetrieval.CComp, method: ContourChain.ApproxSimple); if (contours.Length == 0) { throw new NotSupportedException("Couldn't find any object in the image."); } //Create input sample by contour finding and cropping var dst = new Mat(src.Rows, src.Cols, MatType.CV_8UC3, Scalar.All(0)); var contourIndex = 0; while ((contourIndex >= 0)) { var contour = contours[contourIndex]; var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour Cv2.Rectangle(src, new Point(boundingRect.X, boundingRect.Y), new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height), new Scalar(0, 0, 255), 2); var roi = new Mat(threshImage, boundingRect); //Crop the image var resizedImage = new Mat(); var resizedImageFloat = new Mat(); Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10 resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float var result = resizedImageFloat.Reshape(1, 1); var results = new Mat(); var neighborResponses = new Mat(); var dists = new Mat(); var detectedClass = (int)kNearest.FindNearest(result, 1, results, neighborResponses, dists); //Console.WriteLine("DetectedClass: {0}", detectedClass); //Cv2.ImShow("roi", roi); //Cv.WaitKey(0); //Cv2.ImWrite(string.Format("det_{0}_{1}.png",detectedClass, contourIndex), roi); Cv2.PutText( dst, detectedClass.ToString(CultureInfo.InvariantCulture), new Point(boundingRect.X, boundingRect.Y + boundingRect.Height), 0, 1, new Scalar(0, 255, 0), 2); contourIndex = hierarchyIndexes[contourIndex].Next; } Cv2.ImShow("Segmented Source", src); Cv2.ImShow("Detected", dst); Cv2.ImWrite("dest.jpg", dst); Cv2.WaitKey(); }
ابتدا تصویر اصلی که قرار است عملیات OCR روی آن صورت گیرد، بارگذاری میشود. سپس کانتورها و اعداد موجود در آن تشخیص داده میشوند. مستطیلهای قرمز رنگ در برگیرندهی این اعداد را در تصویر دوم مشاهده میکنید. سپس این کانتورهای یافت شده را که شامل یکی از اعداد تشخیص داده شدهاست، تبدیل به یک ماتریس یک بعدی کرده و به متد FindNearest ارسال میکنیم. خروجی آن نام گروه یا پوشهای است که این عدد در آن قرار دارد. در همینجا این خروجی را تبدیل به یک رشته کرده و در تصویر سوم با رنگ سبز رنگ نمایش میدهیم.
بنابراین در این تصویر، پنجرهی segmented image، همان اشیاء تشخیص داده شدهی از تصویر اصلی هستند.
پنجرهی با زمینهی سیاه رنگ، نتیجهی نهایی OCR است که نسبتا هم دقیق عمل کردهاست.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
ASP.NET MVC #21
<script type="text/javascript"> $(function () { getData(); }); function getData() { var $tbl = $('#tblEmployee'); $.ajax({ url: 'Home/EmployeeInfoData', type: 'Post', datatype: 'json', success: function (data) { if (data.length > 0) { $tbl.empty(); $tbl.append(' <tr><th>ID</th><th>Name</th><th>Family</th></tr>'); var rows = []; for (var i = 0; i < data.length; i++) { rows.push(' <tr><td>' + data[i].Id + '</td><td>' + data[i].Name + '</td><td>' + data[i].Family + '</td></tr>'); } $tbl.append(rows.join('')); } } }); } </script>
و کنترلر مربوط
[HttpPost] public ActionResult EmployeeInfoData() { InfoEmployee mp = new InfoEmployee(); var names = mp.GetData(); return Json(names); }