2025, Oct 05 07:32

12-बिट पैक्ड डेटा को Python और MATLAB में float में अनपैक करें

3 बाइट में पैक्ड दो 12-बिट सैंपल्स को Python और MATLAB में float32 में बदलें: लिटल-एंडियन, बिट मास्क/शिफ्ट, 20-बिट लेफ्ट-शिफ्ट और 2^-31 स्केलिंग की स्टेप-बाय-स्टेप गाइड.

Python और MATLAB में 12-बिट पैक किए गए सैंपल्स को float में अनपैक करना

डेटा अधिग्रहण का एक आम फ़ॉर्मेट 3 बाइट्स में दो 12-बिट सैंपल पैक करता है। इससे जगह बचती है, लेकिन आगे की प्रोसेसिंग कठिन हो जाती है, खासकर जब NumPy और MATLAB जैसे वातावरण में वेक्टराइज़्ड परफॉर्मेंस चाहिए। नीचे एक संक्षिप्त वॉकथ्रू है जो एक काम करती Python इम्प्लीमेंटेशन से शुरू होकर MATLAB में वही व्यवहार बिना गणित या स्केलिंग बदले दोहराने का तरीका दिखाता है।

पैक्ड लेआउट और व्यवहारिक पहलू

हर 3-बाइट ब्लॉक में दो सैंपल एन्कोड होते हैं। बिट डायग्राम बनाना आसान है, लेकिन असल मुद्दा यह है कि मेमोरी में बाइट्स की वास्तविक क्रमबद्धता कैसी है। काम करती इम्प्लीमेंटेशन डेटा को लिटल-एंडियन मानती है: हर 3-बाइट समूह का पहला बाइट 24-बिट हिस्से के सबसे कम महत्वपूर्ण बिट्स रखता है। इन्हीं 24 बिट्स से दो 12-बिट मान निकाले जाते हैं।

Python संदर्भ कार्यान्वयन

यहाँ एक Python फ़ंक्शन है जो 12-बिट पैक्ड मानों वाले बाइट बफ़र को float32 में बदलता है। यह 3-बाइट समूहों पर दो-स्तंभीय दृश्य बनाने के लिए stride tricks का उपयोग करता है, फिर दो 12-बिट पूर्णांकों को निकालने के लिए मास्क और शिफ्ट लगाता है, और स्केलिंग से पहले साइन बिट को सही जगह लाने के लिए लेफ्ट-शिफ्ट करता है।

def decode_s12_packed_to_f32(raw):
    import numpy as np
    import numpy.lib.stride_tricks as st
    i32 = np.frombuffer(raw, dtype=np.int32)
    view2 = np.copy(np.transpose(
        st.as_strided(i32,
            shape=(2, int((i32.size*4)/3)),
            strides=(0, 3), writeable=False)))
    mask12 = (1 << 12) - 1
    view2[:, 0] &= mask12
    view2[:, 0] <<= 20
    view2[:, 1] >>= 12
    view2[:, 1] &= mask12
    view2[:, 1] <<= 20
    return view2.reshape(-1).astype(np.float32) * (2.**-31)

असल में बिट्स के साथ क्या हो रहा है

बाइट क्रम स्पष्ट हो जाए तो मूल बात सरल है। हर 3 बाइट से दो 12-बिट इंटीजर बनते हैं। पहला सैंपल पहले बाइट के निचले 8 बिट्स और दूसरे बाइट के निचले 4 बिट्स लेता है। दूसरा सैंपल दूसरे बाइट के ऊपरी 4 बिट्स और तीसरे बाइट के सभी 8 बिट्स लेता है। 12-बिट इंटीजर निकालने के बाद, 20 बिट्स का लेफ्ट-शिफ्ट 32-बिट साइन किए गए इंटीजर के 31वें बिट पर साइन बिट को पहुँचा देता है। 2^-31 से गुणा करने पर वांछित फ्लोट स्केलिंग मिलती है।

व्यवहार में इसी कारण Python इम्प्लीमेंटेशन बिट ऑपरेशन्स के लिए int32 का उपयोग करती है, 12 बिट्स को अलग करने के लिए मास्क लगाती है, फील्ड्स को विभाजित और संरेखित करने के लिए शिफ्ट करती है, फिर अंतिम लेफ्ट-शिफ्ट के बाद float32 में कास्ट कर के स्केल करती है।

MATLAB अनुवाद, एक-से-एक

नीचे दिया MATLAB कोड वही बिट हेरफेर और स्केलिंग दोहराता है। यह मानता है कि इनपुट 3 के गुणज लंबाई वाला सादा बाइट एरे है। स्पष्टता के लिए एरे को 3-बाय-N में रेशेप किया जाता है, फिर हर 3-बाइट कॉलम से दो 12-बिट मान बनाए जाते हैं। bitshift से पहले int32 में कास्ट करना Python कोड के व्यवहार से मेल खाता है।

% raw8 बाइट मानों (0..255) का एक वेक्टर है, जिसकी लंबाई 3 से विभाज्य है
blk = int32(reshape(raw8, 3, []));
out_i32 = zeros(2, size(blk, 2), 'int32');
out_i32(2, :) = bitshift(blk(3, :), 4) + bitshift(blk(2, :), -4);
out_i32(1, :) = bitshift(bitand(blk(2, :), 15), 8) + blk(1, :);
out_i32 = bitshift(out_i32, 20);
out = single(out_i32) * 2^-31;
out = reshape(out, 1, []);

यह Python की लॉजिक को प्रतिबिंबित करता है: लिटल-एंडियन क्रम में दो 12-बिट मान जोड़ें, साइन को जगह देने के लिए 20 बिट लेफ्ट-शिफ्ट करें, single में बदलें, और 2^-31 से स्केल करें। 15 के साथ वैकल्पिक bitand केवल मंशा स्पष्ट करता है; परिणाम नहीं बदलता, क्योंकि बाद का लेफ्ट-शिफ्ट वैसे भी ऊपरी बिट्स गिरा देता है।

इसे सही करना क्यों जरूरी है

बिट पैकिंग फ़ॉर्मेट छोटी गलतियों को भी बढ़ा देते हैं। बाइट क्रम उलटना या चार बिट का गलत शिफ्ट हर सैंपल को चुपचाप बिगाड़ देता है। Python और MATLAB में बिल्कुल वही ऑपरेशन्स रखने से डेटा सेट वैलिडेट करते समय या विश्लेषण पाइपलाइनों का क्रॉस-चेक करते समय निरंतरता बनी रहती है। जिन वर्कफ़्लो में मापन पैरामीटर्स और मेटाडेटा के साथ संग्रहीत होते हैं, जैसे किसी ZIP कंटेनर के भीतर प्रविष्टियों के रूप में, वहाँ आम तौर पर पहले कच्चा बाइट पेलोड लिया जाता है और फिर ऊपर बताए गए की तरह निर्धारक अनपैकिंग लागू की जाती है।

कुछ वर्कफ़्लोज़ में, जब डेटा साधारण बाइनरी फ़ाइल में हो, तो सीधे 12-बिट प्रकार के साथ पढ़ना सुविधाजनक हो सकता है। उदाहरण के लिए, MATLAB fread के साथ ubit12 प्रिसीजन का उपयोग करके 12-बिट फ़ील्ड पढ़ सकता है—यह फ़ाइल में बाइट्स के लेआउट पर निर्भर करते हुए उपयोगी हो सकता है।

मुख्य बातें

डेटा लिटल-एंडियन है: हर 3 बाइट में दो 12-बिट सैंपल। Python फ़ंक्शन stride tricks, मास्किंग और शिफ्टिंग के साथ वेक्टराइज़्ड तरीका दिखाता है, फिर 20-बिट लेफ्ट-शिफ्ट से साइन को स्थिति में लाया जाता है और 2^-31 से स्केल कर के float32 में बदला जाता है। ऊपर दिया MATLAB कोड यही क्रम अपनाता है और समान संख्यात्मक परिणाम देता है। अलग-अलग वातावरणों में पोर्ट करते समय, शिफ्ट से पहले int32 में कास्ट करें, बाइट क्रम के अनुसार ठीक-ठीक वही दो 12-बिट मान पुनर्निर्मित करें, और अंतिम नॉर्मलाइज़ेशन स्टेप बनाए रखें।

यह लेख StackOverflow पर प्रश्न (लेखक: datenwolf) और Cris Luengo के उत्तर पर आधारित है।