مقدمة:
مقدمة وخلفية:
R لغة وبيئة برمجية للحوسبة الإحصائية وتحليل البيانات وتمثيلها رسوميًا. وتوفر تقنيات متنوعة: النمذجة الخطية وغير الخطية، والاختبارات التقليدية، والسلاسل الزمنية والتصنيف والتجميع، وقابلية كبيرة للتوسع[1]. أما بايثون فتُستخدم لبناء المواقع والبرامج وأتمتة المهام والتحليل، وهي عامة الأغراض لا تختص بمشكلة بعينها، وجعلها تنوعها وسهولتها للمبتدئين من أكثر اللغات استخدامًا[2].
وفي 2021 نقلت مكتبات جامعة ولاية كولورادو CSUL مواد التراث من DSpace إلى CONTENTdm. ولتبسيط الانتقال، استخدمنا برامج R وبـايثون لمهام مستهلكة للوقت: تغيير أحجام الصور دفعة بصرف النظر عن اتجاهها، ومطابقة التفريغات بالصور، وإنشاء PDF وفق أسماء الملفات، وبناء مجلدات للإدخال الجماعي للكائنات المركبة.
الإعداد:
قد يصعب على المبتدئ إعداد بيئة التشغيل. وتساعد الورش والدروس؛ فمثلًا تتعاون الجامعة مع Software Carpentry لتوفير ورش مجانية للموظفين في R وبايثون: https://software-carpentry.org/ . وتوفر Coursera دروسًا تمهيدية مثل Python for Everyone: https://www.coursera.org/specializations/python . وتشرح هذه البرامج الإعداد تفصيلًا، وتتيح المساعدة عند التعثر.
ولتـشغيل R يلزم تثبيت اللغة و RStudio Desktop ، ويرد رابطاهما في إحالات الأصل. ولبايثون يمكن استخدام أمر python في الطرفية على ماك وويندوز ولينكس، أو توزيعة Anaconda المفتوحة. وللتحرير Jupyter Notebook أو Atom أو حتى Notepad.
تطبيع البيانات الوصفية باستخدام R: التحديات:
استضافت المكتبة سابقًا التراث والبيانات والمستودع التقليدي في DSpace المستخدم لدبلن كور، وكان Qualified Dublin Core مخططنا الافتراضي. ومن أمثلة مؤهلات dc.contributor: author و advisor و committeemember. وعند الترحيل زاد عددها على عشرين، وهو ما عُد غير مستدام، فقرر الفريق تبني إرشادات RDA للفهرسة الوصفية[3].
فمثلًا يظهر المؤلف في DSpace بصيغة dc.contributor.author Bright, Donald E. ، ويصبح في CONTENTdm: dc.creator Bright, Donald E., author . ومع أكثر من 90000 سجل مطلوب تنظيفها في أسبوعين، استحال العمل اليدوي. ومنع التعقيد استخدام برنامج جاهز، فاختيرت R لقدرتها على المعالجة.
الحل:
صُدرت بيانات كل المجموعات إلى CSV ، ثم قرأها برنامج R وحول dc.contributor.* إلى dc.creator ، وحُدثت مجموعات DSpace بالملفات الجديدة، ثم نقلتها OCLC إلى CONTENTdm.
كيف يعمل البرنامج؟
يقرأ ملف المجموعة، وينشئ نسخة، ويضيف عمود dc.creator إن لم يوجد. ثم يفحص الصفوف؛ فعند العثور على dc.contributor يعالج القيمة وينقلها إلى creator ويمحو الأصل، ويواصل حتى جميع الأعمدة والصفوف. ثم يكرر ذلك لملفات المجموعات في المجلد.
ونُقلت أسماء المؤهلين جميعًا، سواء مؤلف واحد أو عدة مؤلفين بأدوار مختلفة. وكان author الأصعب، لأن DSpace يلحق بالقيم معرّفات UUID العالمية الفريدة[4]. ومثالها ff4d344e-a2c5-4bfc-8271-553b416db8f0. ويظهر مثال الشكل 1 أسماء Albertson, Maurice L. و Birky-Kreutzer, Pauline مع معرّفيهما، ثم Peace Corps(U.S.).
ولم نجد آنذاك أداة تنظف هذه القيم بكفاءة، لكننا لاحظنا نمطًا: يبدأ الجزء المضاف بـ:: ثم 39 أو 41 محرفًا أبجديًا رقميًا، وتفصل || الأسماء المتعددة. فيسهل استخراجها برمجيًا وإلحاق حقول contributor الأخرى. وتصبح النتيجة: Albertson, Maurice L., author||Birky-Kreutzer, Pauline, author||Peace Corps(U.S.), institution . ويقارن الشكل 1 البيانات الأصلية والجديدة.
تشغيل R في RStudio:
برنامج R ملف نصي يمكن تشغيله بالواجهة أو بالأوامر. واخترنا RStudio ، بيئة تطوير مجانية مفتوحة تتيح رؤية الرسوم والجداول والشيفرة والمخرجات معًا[5]. ويعرض الشكل 2 الشيفرة أعلى، والتنفيذ والأخطاء والتحذيرات والطباعة أسفل.
أمثلة الشيفرة:
تتوفر وحدات كثيرة بدوال مفيدة؛ واستخدمنا sjmisc و stringr:
library(sjmisc) library(stringr)
تحدد setwd مجلد العمل للملفات الأصلية والجديدة، وتجلب list.files محتوياته:
setwd("~/Desktop/R/batch1")
file_list <- list.files()
وتقرأ الأوامر التالية CSV:
orgdata <- read.csv(file_list[[Z]]) newdata <- orgdata[,names(orgdata)!=""]
وتعرّف هذه الدالة تعديل الأسماء:
add_new_authors <- function(authorList, column, title){
for (c in 1:total_rows){ … }
وتحدد gregexpr مواضع::[6]:
positions <- gregexpr(pattern='::', org_names, ignore.case=FALSE)
ثم تستخرج substr اسم المؤلف:
authorname <- substr(org_names,name_first_char, name_last_char)
وتلحق paste0 وصف author بالاسم:
authorList[[c]] <- paste0(authorname,", author")
ولسبب لم نعرفه، ينشئ DSpace أحيانًا أعمدة بأقواس []، فيظهر author و/أو author[]. وللتحقق نستخدم %in% الذي يختبر انتماء عنصر لمتجه أو DataFrame. وتقرأ R الأقواس نقطتين؛ لذا نفحص dc.contributor.author..، ونستدعي add_new_authors لإنشاء creator عند وجودها:
if("dc.contributor.author" %in% colnames(newdata))
{ cList <- add_new_authors(cList, newdata$dc.contributor.author,"author")
newdata$dc.contributor.author <- ""
}
if("dc.contributor.author.." %in% colnames(newdata))
{ cList <- add_new_authors(cList, newdata$dc.contributor.author..,"author")
newdata$dc.contributor.author.. <- ""
}
وبعد جمع البيانات، تكتب write.csv ملفًا جديدًا ببادئة creators-:
newFileName <- paste0("creators-",file_list[[Z]])
write.csv(newdata, file = newFileName, row.names = FALSE)
التحسينات:
كان هذا أول برنامج للمؤلفة بلغة R ، وليس مثاليًا، ويمكن تحسين معالجة الأخطاء خصوصًا. لكنه رغم نقصه عالج آلاف القيم بثوانٍ، وساعدنا على الوفاء بموعد الترحيل.
تغيير أحجام الصور ببايثون:
يوفر CONTENTdm ملحق OCR في Project Client لتوليد تفريغات الصور[7]، بترخيص منفصل يتيح 10000 صفحة شهريًا فقط. وإذا تجاوزت الصورة 8.27×11.69 بوصة، تُحسب صفحتين أو أكثر حسب الحجم.
وكانت مسوح A4 ذات الحواف الزائدة تتجاوز الحد، فتستنزف الحصة وتؤخر الإدخال. ورغم تغيير Photoshop و Bridge الأحجام دفعة، لا يعالجان الاتجاه ومجلدات متعددة بالطريقة المطلوبة. ويعمل Lightroom بالضلع الأطول لكنه يتطلب الاستيراد لمكتبته، وهو وقت إضافي. لذلك أنشأنا برنامجًا بسيطًا يراعي الاتجاه، ويعالج المجلدات، وينشئ مجلدات للنتائج.
الشيفرة البشرية والمولدة بالذكاء الاصطناعي:
جربنا لاحقًا ChatGPT للمهمة نفسها، واحتجنا إلى تعديل الطلب مرات حتى حقق الوظيفة والتجربة المطلوبتين، ونقل سطر لتحسين الكفاءة. وكان الطلب: اكتب برنامج بايثون لتغيير حجم الصور وفق الضلع الأطول؛ اطلب مجلد الأصل الذي قد يتضمن مجلدات فرعية؛ واسم مجلد جذري للنتائج بجوار الأصل؛ وحافظ على بنية المجلدات وأسمائها؛ واقبل JPG و JPEG و PNG و TIF و TIFF واحفظ JPG ؛ واطلب الحجم بالبوصة، واجعل الدقة 300 نقطة/بوصة والضلع الأطول بالقيمة المدخلة.
واكتشفنا أنه حتى إن أخفقت المحاولة الأولى، يمكن الرجوع إلى الشيفرة المولدة وتعديلها للوصول بكفاءة أكبر.
الشيفرة التي كتبها الإنسان:
import os from PIL import Image, ImageOps Image.MAX_IMAGE_PIXELS = None
# Prompt for inputs
org_dir = input(‘Input the orginal folder name.’)
print(org_dir)
resized_dir = input(‘Input a folder name for resized images.’)
print(resized_dir)
os.mkdir(f'{resized_dir}’)
dir_list = os.listdir(org_dir)
input_long_side = input(‘Input long side length in inches. (e.g. 8,9,10 or 11): ‘)
long_side = int(input_long_side)
long_side_pixels = long_side * 300
# Batch process. Will process a directory with sub-directories.
for subDir in dir_list:
print(‘Now processing folder: ‘, subDir, ‘please wait…’)
os.mkdir(f'{resized_dir}/{subDir}’)
og = f'{org_dir}/{subDir}’
orgScans = os.listdir(og)
for orgImage in orgScans:
orgFileName = f'{org_dir}/{subDir}/{orgImage}’
newFileName = f'{resized_dir}/{subDir}/{orgImage}’
if(orgImage.startswith(‘.’)):
print(‘Ignore a hidden file: ‘, orgFileName, ‘still processing files. Please wait…’)
elif(orgImage == ‘Thumbs.db’):
print(‘Ignore file: ‘, orgFileName, ‘still processing files. Please wait…’)
else:
newJPG = newFileName.split(“.”)
resizedJPG = newJPG[0] + “.jpg”
img = Image.open(orgFileName)
width,height = img.size
area = width * height
if (area >= 89478485):
print(‘File: ‘, orgFileName, ‘exceeds pixel limitation and would not be converted. Please convert it manually.’)
else:
rgb_image = img.convert(‘RGB’)
resizedImage = ImageOps.contain(rgb_image, (long_side_pixels,long_side_pixels))
resizedImage.save(resizedJPG, dpi=(300, 300))
print(‘Total processed ‘, len(dir_list), ‘folders.’)
الشيفرة المولدة آليًا:
import os from PIL import Image
# Prompt for original directory
orig_dir = input(“Enter the path of the directory containing original images to be resized: “)
# Prompt for root folder name for resized images
root_folder_name = input(“Enter the name of the root folder to hold all resized images: “)
root_folder_path = os.path.join(os.path.dirname(orig_dir), root_folder_name)
long_side_inches = float(input(“Enter the size in inches for the long side of a resized image: “))
# Create root folder if it doesn’t already exist
if not os.path.exists(root_folder_path):
os.mkdir(root_folder_path)
# Walk through the original directory and its subdirectories
for dirpath, dirnames, filenames in os.walk(orig_dir):
# Create corresponding subdirectories in the root folder
subfolder_path = os.path.join(root_folder_path, os.path.relpath(dirpath, orig_dir))
if not os.path.exists(subfolder_path):
os.mkdir(subfolder_path)
# Loop through image files in current directory
for filename in filenames:
# Check if file is an image
if filename.lower().endswith((‘.jpg’, ‘.jpeg’, ‘.png’, ‘.tif’, ‘.tiff’)):
# Open image and get dimensions
filepath = os.path.join(dirpath, filename)
img = Image.open(filepath)
width, height = img.size
# Determine new dimensions based on user input and maintain aspect ratio
if width >= height:
new_width = int(long_side_inches * 300)
new_height = int(height * new_width / width)
else:
new_height = int(long_side_inches * 300)
new_width = int(width * new_height / height)
# Resize image and save as .jpg
resized_img = img.resize((new_width, new_height))
new_filepath = os.path.join(subfolder_path, filename)
new_filepath = os.path.splitext(new_filepath)[0] + ‘.jpg’
resized_img.save(new_filepath, format=’JPEG’)
معالجة المستندات الممسوحة والتفريغات:
يحوي المستودع يوميات ورسائل شخصية كثيرة مكتوبة بخط اليد، لا تناسب OCR ؛ ففرغها متطوعون أو طلاب. وأُدخلت المستندات وتفريغاتها إلى DSpace ملفات PDF ، فينزل المستفيد كليهما ويعرضهما بنافذتين متجاورتين. ويتيح CONTENTdm عرض النص بجوار الصورة، وهو أسهل كثيرًا، كما في الشكل 3.
واخترنا إدخال كائن مركب ببنية مجلدات. فلكل عنصر مجلد يحوي مجلد الصور، ومجلد تفريغات TXT لكل صفحة، وملف بيانات وصفية TXT للعنصر.
الملفات الأصلية:
لكل سجل PDF وتفريغ TXT وملف CSV لوصف المجموعة صادر من DSpace. والأخير يحوي بيانات عشرات أو آلاف السجلات الأخرى، كما يوضح الشكل 4.
العملية اليدوية:
تتطلب إنشاء ثلاثة مجلدات، وتصدير PDF إلى صور، ونسخ التفريغ ولصقه في ملفات صفحات، وهي أكثر الخطوات استهلاكًا للوقت كما في الشكل 5. ثم العثور على الصفوف الثلاثة الخاصة بالعنصر في وصف المجموعة، ودمجها في صف وإنشاء TXT مفصول بعلامات الجدولة، كما في الشكل 6. والعمل طويل وشديد التعرض للأخطاء التي تفشل الإدخال.
العملية البرمجية:
أتمتنا الخطوات لمعالجة عدة عناصر، بمدخلات ثلاثة: مجلد PDF ، ومجلد التفريغات المقابلة غير المقسمة إلى صفحات، و CSV لوصف المجموعة. ويبين الشكل 7 أمر التشغيل.
فينشئ البرنامج مجلد إدخال جماعي بمجلدات الكائنات، ويصدر PDF إلى مجلدات scans ، ويقرأ التفريغ ويقسمه إلى TXT للصفحات بأسماء صورها، ويستخرج الوصف الفردي من ملف المجموعة. ويقارن الشكل التالي ملفات البداية بالمخرجات.
استيراد الوحدات:
الوحدة مكتبة شيفرة بدوال قابلة للاستخدام، واستخدمنا:
import os import csv import pandas as pd from pdf2image import convert_from_path
المدخلات والمجلدات الجديدة:
تأخذ الشيفرة المدخلات الثلاثة، وتعثر على PDF ، وتنشئ BatchIngest لحفظ الكائنات:
pDir = input('Input a directory with original PDFs:')
orgTransDir = input('Input a directory with original transcript .txt files:')
orgMetadata = input('Input the master metadata file name in csv format:')
dir_list = os.listdir(pDir)
subDir = "BatchIngest"
os.mkdir(subDir)
وتنشئ حلقة لكل PDF مجلدًا باسمه، داخله مجلد الصور وآخر للنصوص:
for file in dir_list:
y = file.strip(".pdf")
os.mkdir(f'{subDir}/{y}')
os.mkdir(f'{subDir}/{y}/scans')
os.mkdir(f'{subDir}/{y}/transcripts')
تحويل PDF إلى JPG:
يلزم pdf2image لتحويل الملف إلى كائن PIL ، و Poppler لقراءة PDF وعرضه ومعالجته. ويحتاج مستخدم ويندوز إلى بنائه أو تنزيله[8]. واستخدمنا نسخة من https://github.com/oschwartz10612/poppler-windows/releases/ وفككناها في جذر C. ويرد شرح التثبيت في https://pdf2image.readthedocs.io/en/latest/installation.html ، ودليل التحويل في https://www.geeksforgeeks.org/convert-pdf-to-image-using-python/ . وأضفنا:
from pdf2image import convert_from_path poppath = r"C:\poppler-22.12.0\Library\bin"
وتستخدم pdftoimages الدالة convert_from_path للتحويل، و zfill(4) لتسمية متوافقة مع معيار الجامعة: _page_0015.jpg بدل _page_15.jpg.
def pdftoimages(pdfDir, pdfFile, imgDir): # Omitted code
images = convert_from_path(pdf_file_name, poppler_path=poppath)
for i in range(len(images)): # Omitted code
images[i].save(f'{imgDir}/{file_name_preceeding}_page_' + str(j).zfill(4)+'.jpg', 'JPEG')
توليد ملفات التفريغ:
لكل PDF تفريغ مقابل. واتفقنا على أربعة أسطر فارغة متتابعة علامةً لصفحة جديدة. يقرأ البرنامج سطرًا سطرًا، ويجمع النص ويعد الفراغات، ثم ينشئ TXT عند العلامة. فالصورة WREG03014_page_0001.jpg يقابلها WREG03014_page_0001.txt.
f = open(orgTrans, 'r')
newFile = []
for file in imageNames:
newFile = file.replace('jpg','txt')
…
if(len(aLine) == 1): # find empty lines
lineBreakCount += 1
aLine = f.readline()
if not aLine: #reach the end of file.
newFile.append(aLine)
توليد البيانات الوصفية:
تقرأ read_csv الملف إلى DataFrame باسم orgMD ، وتحدد index رقم صف الاسم الموافق، ثم loc الصف الذي يحوي PDF المطابق، وتدمج البيانات في resultRow ، وتكتبها TXT مفصولًا بالجدولة باستخدام to_csv.
orgMD = pd.read_csv(orgMetadata, encoding= 'unicode_escape', low_memory=False) matchingRowIndex = orgMD.index[orgMD['File Name'] == file]
resultRow = orgMD.loc[[matchingRowIndex]]
mdRow = int(matchingRowIndex[0])+2
resultRow = orgMD.loc[[mdRow],'Title':'File Name']
csvFileName = f'{file.strip(".pdf")}.txt'
tn = f'{csvPath}{csvFileName}'
resultRow.to_csv(exportedFileName, mode='a', sep ='\t', index=False, encoding='utf-8')
خاتمة:
وفرت برامج R وبايثون المخصصة ساعات كثيرة وقللت الأخطاء البشرية. وإلى جانب الحالات المعروضة، استُخدمت لتحليل GIS والتحقق بعد الإدخال. ومع شيوعها لدى علماء البيانات والمهندسين والاختصاصيين الجغرافيين، فهي مفيدة للمكتبيين أيضًا. ونشجع تعلمها بدروس أو ورش مجانية ثم تطبيقها في مشروعات بسيطة؛ فمع الخبرة تتكشف فرص أكثر.
المراجع:
https://software-carpentry.org/
https://www.coursera.org/specializations/python
http://ftp.ussg.iu.edu/CRAN/
https://www.rstudio.com/products/rstudio/download/#download
https://github.com/oschwartz10612/poppler-windows/releases/
https://pdf2image.readthedocs.io/en/latest/installation.html
https://www.geeksforgeeks.org/convert-pdf-to-image-using-python/
https://www.r-project.org/about.html
https://www.coursera.org/articles/what-is-python-used-for-a-beginners-guide-to-using-python
https://www.rdatoolkit.org/about#:~:text=RDA%3A%20Resource%20Description%20and%20Access,user%2Dfocused%20linked%20data%20applications
https://duo.com/labs/tech-notes/breaking-down-uuids#:~:text=Universally%20Unique%20Identifiers%2C%20or%20UUIDS,both%20the%20IETF%20and%20ITU
https://libguides.library.kent.edu/statconsulting/r
https://www.rdocumentation.org/packages/micropan/versions/1.0/topics/gregexpr
https://help.oclc.org/Metadata_Services/CONTENTdm/Project_Client/Enter_metadata/OCR?sl=en
حواشي الأصل بلغتها الأصلية:
[1] “What is R?” The R Foundation, accessed December 1, 2022, https://www.r-project.org/about.html. [2] “What is Python?” Coursera Inc., accessed December 12, 2022, https://www.coursera.org/articles/what-is-python-used-for-a-beginners-guide-to-using-python. [3] “RDA, Resource Description & Access Toolkit”, American Library Association, accessed December 12, 2022, https://www.rdatoolkit.org/about#:~:text=RDA%3A%20Resource%20Description%20and%20Access,user%2Dfocused%20linked%20data%20applications. [4] Steele, Nick. “Breaking Down UUIDS”, accessed December 12, 2022, https://duo.com/labs/tech-notes/breaking-down-uuids#:~:text=Universally%20Unique%20Identifiers%2C%20or%20UUIDS,both%20the%20IETF%20and%20ITU. [5] “Statistical & Qualitative Data Analysis Software: About R and RStudio”, 26, October er, 2022, accessed December 12, 2022, https://libguides.library.kent.edu/statconsulting/r. [6] “gregexpr: Extended gregexpr with substring retrieval”, RDocumentation.org, accessed December 12, 2022, https://www.rdocumentation.org/packages/micropan/versions/1.0/topics/gregexpr. [7] “OCR”, OCLC, 1 January, 2023, accessed March 23, 2023, https://help.oclc.org/Metadata_Services/CONTENTdm/Project_Client/Enter_metadata/OCR?sl=en. [8 kumar_satyam. “Convert PDF to Image using Python.” 21 Jan, 2021, accessed December 9, 2022, https://www.geeksforgeeks.org/convert-pdf-to-image-using-python/.