Repository navigation
feat: Add BAQ mode decoder, chunk info extraction & fix FDBAQ channel desync - #41
Conversation
|
👀 This looks great, thank you! It'll take me a bit of time to properly review, but I wanted to send a holding response. |
|
Take your time with the review, there's absolutely no rush. Feel free to let me know whenever you're ready or if you need any clarification on the changes. |
Rich-Hall
left a comment
There was a problem hiding this comment.
Looks good, that bug in particular must have been quite hard to find and fix! I think we need to fix a couple of things before merging:
- Please remove
termcolorimports and calls - coloring log text is not worth adding a new project dependency, IMO. - We should discuss the
chunks_info.pyfunctions - I think these duplicate some logic that already exists inLevel0File.iter_chunks_matching(...), so I'd like to understand better the justification / use-case for these.
Otherwise, it's just a few nits around docstrings.
| if let Some(&byte) = data.get(*byte_idx) { | ||
| boundary_state_bits = (state.state_bits as u32) << 8 | byte as u32; | ||
| boundary_state_len = state.state_len + 8; | ||
| *byte_idx += 1; | ||
|
|
||
| // Determine how many bits we strictly need to read the header for this block. | ||
| // - BRC needs 3 bits | ||
| // - THIDX needs 8 bits | ||
| // - Other channels (IO, QO) need 0 bits (no header) | ||
| let bits_needed_for_header = if read_brc { 3 } else if read_thidx { 8 } else { 0 }; | ||
|
|
||
| // CRITICAL FIX: Only fetch a new byte from `data` if the leftover bits in `state` are | ||
| // strictly insufficient for the header. If we unconditionally fetch a byte here (especially | ||
| // on the last block of a channel), we might consume a byte that actually belongs to the NEXT | ||
| // channel. Since `state`is discarded at the end of `decode_channel`, that over-read byte | ||
| // would be lost forever, causing the next channel to lose its synchronization. | ||
| if state.state_len < bits_needed_for_header { | ||
| if let Some(&byte) = data.get(*byte_idx) { | ||
| boundary_state_bits = (state.state_bits as u32) << 8 | byte as u32; | ||
| boundary_state_len = state.state_len + 8; | ||
| *byte_idx += 1; | ||
| } else { | ||
| boundary_state_bits = state.state_bits as u32; | ||
| boundary_state_len = state.state_len; | ||
| } |
There was a problem hiding this comment.
Great find! Thank you for fixing this.
|
|
||
| pub fn decode_single_baq_packet_inner(data: &[u8], num_quads: usize, baq_bits: u8) -> Result<Vec<Complex32>, String> { |
There was a problem hiding this comment.
Could you add a docstring for this function?
There was a problem hiding this comment.
Sure, I'll add a docstring for this function.
| #[pyfunction] | ||
| fn decode_single_baq_packet( | ||
| data: &[u8], | ||
| num_quads: usize, | ||
| baq_bits: usize, | ||
| py: Python, | ||
| ) -> PyResult<Py<PyAny>> { | ||
| let complex_samples = decode_single_baq_packet_inner(data, num_quads, baq_bits as u8) | ||
| .map_err(|e| PyValueError::new_err(e))?; | ||
|
|
||
| Ok(complex_samples.into_pyarray(py).to_owned().into()) | ||
| } | ||
|
|
||
| #[pyfunction] | ||
| fn decode_batched_baq_packets( | ||
| packets: &Bound<'_, PyList>, | ||
| num_quads: usize, | ||
| baq_bits: usize, | ||
| py: Python, | ||
| ) -> PyResult<Py<PyAny>> { | ||
| decode_batched_packets_helper(packets, num_quads, py, |packet_data, nq| { | ||
| decode_batched_baq_packets_inner(packet_data, nq, baq_bits as u8) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Let's add some docstrings to these as well.
| def decode_single_baq_packet( | ||
| data: bytes, | ||
| num_quads: int, | ||
| baq_bits: int, | ||
| ) -> np.ndarray: | ||
| ... | ||
|
|
||
| def decode_batched_baq_packets( | ||
| packets: List[bytes], | ||
| num_quads: int, | ||
| baq_bits: int, | ||
| ) -> np.ndarray: | ||
| ... | ||
|
|
There was a problem hiding this comment.
Docstrings for these as well please!
| @@ -0,0 +1,171 @@ | |||
| from termcolor import colored | |||
There was a problem hiding this comment.
This introduces an additional project dependency - termcolor is not currently a package requirement. I'm not convinced we need to introduce a new dependency for these purposes - is there a strong reason you'd like this to be included?
There was a problem hiding this comment.
Got it, I’ll remove this termcolor import —it was just for temporary log highlighting.👌
| def extract_SM_chunk_info( | ||
| inputfile: str, | ||
| visualize: bool = True | ||
| ) -> tuple["Level0File", dict[str, list]]: | ||
| """ | ||
| Extract the data block information of the Sentinel-1 SM mode | ||
| Important Note: | ||
| When visualize=True, the function will output logs using logger.info. | ||
| Please configure logging before calling the function | ||
| """ |
There was a problem hiding this comment.
I think this function duplicates a lot of logic that already exists using the iter_chunk_matching function on Level0File. For example, you can get each of this function's results using the following:
list(l0file.iter_chunks_matching(signal_type=SignalType.ECHO))
list(l0file.iter_chunks_matching(signal_type=SignalType.NOISE))
list(l0file.iter_chunks_matching(signal_type=SignalType.RX_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.TX_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.EPDN_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.APDN_CAL_S1AB_ONLY))
list(l0file.iter_chunks_matching(signal_type=SignalType.TA_CAL_OR_TX_CAL_ISO))
list(l0file.iter_chunks_matching(signal_type=SignalType.TXH_CAL_ISO_S1AB_ONLY))
There was a problem hiding this comment.
I wasn't familiar with this existing API before. I'll refactor this to reuse iter_chunks_matching instead.🤣
| def extract_IW_chunk_info( | ||
| inputfile: str, | ||
| visualize: bool = True | ||
| ) -> tuple["Level0File", dict[str, list]]: | ||
| """ | ||
| Extract the data block information of the Sentinel-1 IW mode | ||
| Important Note: | ||
| When visualize=True, the function will output logs using logger.info. | ||
| Please configure logging before calling the function | ||
| """ |
There was a problem hiding this comment.
Similarly, there's a lot of overlap with logic that already exists here too. Most of the buckets map directly:
list(l0file.iter_chunks_matching(signal_type=SignalType.NOISE, swath_num=10))
list(l0file.iter_chunks_matching(signal_type=SignalType.NOISE, swath_num=11))
list(l0file.iter_chunks_matching(signal_type=SignalType.NOISE, swath_num=12))
list(l0file.iter_chunks_matching(signal_type=SignalType.RX_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.TX_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.EPDN_CAL))
list(l0file.iter_chunks_matching(signal_type=SignalType.APDN_CAL_S1AB_ONLY))
list(l0file.iter_chunks_matching(signal_type=SignalType.TA_CAL_OR_TX_CAL_ISO))
list(l0file.iter_chunks_matching(signal_type=SignalType.TXH_CAL_ISO_S1AB_ONLY))
list(l0file.iter_chunks_matching(signal_type=SignalType.ECHO, swath_num=10))
list(l0file.iter_chunks_matching(signal_type=SignalType.ECHO, swath_num=11))
list(l0file.iter_chunks_matching(signal_type=SignalType.ECHO, swath_num=12))
We don't currently have a way of listing skipped packets, because iter_chunks_matching doesn't have a filter for number of packets. I think it might be better to add the logic there, though, rather than introducing a new API surface here. Do you have a strong reason for having these utility functions exist separately here?
There was a problem hiding this comment.
You're right, these two new functions are redundant now that I know about this existing API. 😢
It makes sense to add the skipped packets listing feature directly to iter_chunks_matching.
There was a problem hiding this comment.
Instead of two standalone functions, I suggest adding a single get_chunks_summary() method on Level0File.
It does a single O(N) pass to group all chunks by type & swath, avoiding the O(n×N) cost of calling iter_chunks_matching 10+ times for a full overview. It complements the existing API, needs no new files.
Would you be happy with this approach?😁
| ) | ||
| from sentinel1decoder.enums import BaqMode | ||
|
|
||
| from termcolor import colored |
There was a problem hiding this comment.
Similarly to above, I don't think we should add a new package dependency just to color log text.
|
Thanks for the review and feedback! |
|
I noticed the |
91ba75a to
915a592
Compare
|
Hi again - I've updated the project dependencies and took the liberty of rebasing your branch here onto that change to fix CI. Hope that's okay. I'm going to get this merged in shortly. |
Rich-Hall
left a comment
There was a problem hiding this comment.
Looks like all feedback has been addressed - approving! Thanks. Going to make a couple of additional tweaks after merge here before releasing new version.
|
Just a quick follow-up to say that I've released v2.1.0 now, which features your changes. I've credited you for these in the changelog.md. Thanks again! 😁 |
|
Thanks for merging and crediting me! Glad my changes could help.😊 |
Hi! I've made some improvements, including implementing the BAQ mode decoder and fixing a critical bug in the FDBAQ decoder.
New Features
chunks_info.pyfor clearer chunk information output. It displays chunk IDs in a list format, grouped by signal category, which is incredibly helpful for IW mode data analysis.Bug Fixes
S1A_S3_RAW__0SDV_20160522T094430_20160522T094451_011370_011459_7607(Previously crashed immediately during decoding)S1A_S3_RAW__0SDV_20151218T094429_20151218T094449_009095_00D110_0BB2(Previously generated abnormal white spots during imaging without reporting an error)Additional changes
test_baq_decoder_rust.pyandtest_chunks_info.py.I have run the test suite locally and all tests passed.
I realize this PR contains both a bug fix and new feature additions. If you feel the new features (like the
Chunks Infomodule) don't currently align with the project's roadmap, or if you prefer I split the bug fix and the new features into separate smaller PRs, please just let me know! I am completely open to modifying, splitting, or dropping any part of this to match your vision for the project.Looking forward to your thoughts!