CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.5.1] - 2026-03-18

### Fixed

- Deflated Explicit VR Little Endian now uses raw RFC1951 deflate/inflate
  semantics instead of zlib-wrapped payload handling
- Streaming and eager parsing now fail closed on truncated encapsulated pixel
  fragments and other malformed binary edge cases that previously leaked
  partial or ambiguous values
- `Dicom.Value` and `Dicom.Json` now reject partial or malformed `DS`, `IS`,
  numeric VR, `PN`, and `AT` values instead of silently truncating, coercing,
  or exporting invalid JSON
- DICOM JSON now respects VM=1 text VRs, validates `AT` tuples on export,
  decodes multi-valued `AT` values correctly, and keeps compressed Pixel Data
  roundtrips writable when transfer syntax context is known
- Writer validation now rejects malformed encapsulated Pixel Data structure,
  invalid Basic Offset Tables, and other Pixel Data / transfer syntax
  mismatches before serialization
- `Dicom.PixelData` and `Dicom.DataSet.decoded_value/2` now fail safely on
  malformed numeric metadata instead of raising or leaking undecoded raw bytes

### Changed

- `Dicom.Json.from_map/2` now normalizes compressed Pixel Data to
  `{:encapsulated, fragments}` only when transfer syntax context is provided,
  and otherwise preserves raw binary payloads
- De-identification markers and option handling are now more honest and
  predictable, including support for direct boolean option flags and LO-safe
  `DeidentificationMethod` values
- Documentation now matches the hardened JSON boundary behavior, transfer
  syntax context requirements, de-identification option styles, and current
  release versioning
- Implementation version name updated to `DICOM_0.5.1`

## [0.5.0] - 2026-03-17

### Fixed

- Malformed input paths across the eager parser, streaming parser, DICOM JSON
  decoder, and pixel-data helpers now return structured errors instead of
  raising exceptions
- Top-level sequence de-identification now respects tag action rules instead of
  always preserving sequence containers
- File Meta Information validation now rejects empty or malformed required UID
  values before serialization
- `Dicom.PixelData` now handles parser-produced `{:encapsulated, fragments}`
  values, computes native frame sizes correctly for bit-packed data, and
  rejects ambiguous multi-frame encapsulated splitting
- `Dicom.P10.Stream.parse_file/2` now honors the documented `:read_ahead` option

### Changed

- `Dicom.Json.from_map/2` is now strict about malformed typed values and
  requires an explicit `bulk_data_resolver:` to materialize `BulkDataURI`
  content
- De-identification private-tag retention is now named and documented honestly
  via `retain_private_tags`; `retain_safe_private` remains as a compatibility
  alias for retaining all private tags
- Character set support now explicitly rejects ISO 2022 escape-sequence
  switching instead of implying code-extension support
- README and API docs were updated to match the stricter JSON, pixel-data,
  de-identification, and streaming behavior
- Implementation version name updated to `DICOM_0.5.0`

## [0.4.5] - 2026-03-17

### Fixed

- DICOM JSON multi-value handling now preserves array semantics for PN, AT,
  string VRs, and numeric VRs
- DICOM JSON encoder now omits Group Length attributes from output
- `Dicom.write_file/2` now returns writer errors instead of raising `MatchError`
- `Enumerable` slice implementation for `Dicom.DataSet` updated for Elixir 1.18's
  3-arity slice contract
- `Dicom.SOPClass` is now the canonical public module name

### Changed

- De-identification profile flags now affect supported tag groups instead of
  leaving most options inert
- Release docs corrected to match current registry counts and current behavior
- README and HexDocs now describe scope more precisely around PS3.10,
  compressed transfer syntaxes, DICOM JSON, and de-identification limits
- Hex package and HexDocs extras now include `SECURITY.md`, and source links
  default to the release tag
- Implementation version name updated to `DICOM_0.4.5`

## [0.4.2] - 2026-03-17

### Removed

- Dead code in `P10.Stream.Parser`: unreachable error branches guarded by `ensure_bytes`
  (9 branches across `read_tag`, `read_uint32`, `read_next_data_element`,
  `read_item_elements_until_delimiter_eager`, `read_item_elements_bounded_eager`,
  `read_fragments_eager`)
- Dead code in `CharacterSet`: unreachable `iso8859_to_unicode(byte, 1)` clause
  (`ISO_IR 100` maps to `:latin1`, never to `{:iso8859, 1}`)
- Dead code in `PixelData`: unreachable error branch in `frame/2`
  (`extract_encapsulated_frames` always returns `{:ok, ...}`)

## [0.4.0] - 2026-03-17

### Added

- **VR metadata** — `VR.all/0`, `VR.description/1`, `VR.max_length/1`,
  `VR.fixed_length?/1` backed by compile-time maps per PS3.5 Table 6.2-1.
- **Tag utilities** — `Tag.parse/1` for "(GGGG,EEEE)" and "GGGGEEEE" formats,
  `Tag.from_keyword/1` for dictionary keyword lookup, `Tag.repeating?/1` for
  50XX/60XX/7FXX repeating groups.
- **Date/time conversion** — `Value.to_date/1`, `Value.to_time/1`,
  `Value.to_datetime/1`, `Value.from_date/1`, `Value.from_time/1`,
  `Value.from_datetime/1` for bidirectional DICOM DA/TM/DT ↔ Elixir Date/Time/DateTime.
  Supports partial TM, fractional seconds, and timezone offsets.
- **DataSet ergonomics** — `DataSet.has_tag?/2`, `DataSet.get/3` (with default),
  `DataSet.fetch/2`, `DataSet.merge/2`, `DataSet.from_list/1`,
  `DataSet.decoded_value/2` (VR-aware decode).
- **Protocol implementations** — `Inspect` for `DataElement` (shows tag, VR,
  truncated value; SQ item count; encapsulated fragment count) and `DataSet`
  (element count, patient, modality). `Enumerable` for `DataSet` (sorted by tag,
  file_meta merged). `Access` behaviour for `DataSet` (`ds[tag]`, `get_in`,
  `put_in`, `pop`).

### Changed

- Expanded test suite to 1000+ tests (35 doctests, 7 property tests) at 97%+ coverage
- `UID.transfer_syntax?/1` now uses `TransferSyntax.known?/1` for authoritative
  O(1) registry lookup instead of prefix matching (fixes false positives for UIDs
  like Storage Commitment `1.2.840.10008.1.20.1`)
- Implementation version name updated from `DICOM_EX_0.1.1` to `DICOM_0.4.0`
- Updated AGENTS.md with current test counts and architecture diagram

## [0.3.0] - 2026-03-17

### Added

- **62 transfer syntaxes** — expanded from 29 to all 49 active + 13 retired
  DICOM transfer syntaxes. New fields: `retired` and `fragmentable` flags.
  New functions: `retired?/1`, `fragmentable?/1`, `active/0`.
- **Complete PS3.6 tag dictionary** — 5,035 entries generated from innolitics
  `attributes.json` via `mix dicom.gen_dictionary`. Keyword reverse lookup
  with `find_by_keyword/1`, retired tag detection with `retired?/1`, and
  expanded repeating group support (50XX curve, 60XX overlay, 7FXX waveform).
- **DICOM JSON model** (`Dicom.Json`) — encode/decode DataSets to/from the
  DICOM JSON format (PS3.18 Annex F.2) for DICOMweb. Supports all VR types,
  Person Name component groups, sequence recursion, InlineBinary (base64),
  and BulkDataURI callbacks. Zero runtime dependencies — produces plain maps.
- **Pixel data frame extraction** (`Dicom.PixelData`) — extract individual
  frames from native and encapsulated pixel data (PS3.5 Section A.4).
  O(1) native frame access via `binary_part/3`. Encapsulated support with
  Basic Offset Table, fragment-per-frame convention, and single-frame
  concatenation. Functions: `frames/1`, `frame/2`, `frame_count/1`,
  `encapsulated?/1`.
- **De-identification / anonymization** (`Dicom.DeIdentification`) — Basic
  Application Level Confidentiality Profile (PS3.15 Table E.1-1) with action
  codes D, Z, X, K, C, U. Consistent UID replacement across elements.
  Configurable profile with 10 boolean options. Recursive SQ processing and
  private tag stripping.
- **ISO 8859-{2..9} full lookup tables** — replaced identity mapping with
  correct Unicode codepoint tables for all 8 ISO 8859 variants.
- **JIS X 0201 character set** — `ISO_IR 13` support for Roman + half-width
  Katakana decoding.
- Mix task `mix dicom.gen_dictionary` for regenerating the tag dictionary
  from the innolitics DICOM standard JSON source.

### Changed

- Expanded test suite to 621 tests (5 doctests, 4 property tests) at 91%+ coverage
- VR module exposes `string_vrs/0`, `numeric_vrs/0`, `binary_vrs/0` list accessors
- Transfer syntax `all/0` and `active/0` cached at compile time

### Fixed

- De-identification `:D` action now computes correct `byte_size` for dummy values
- ISO 8859-4 table: removed erroneous `0xE3` mapping
- Character set decoding DRYed with shared `decode_bytewise/3`

## [0.2.0] - 2026-03-17

### Added

- Streaming DICOM P10 parser via `Dicom.P10.Stream` with lazy, event-based parsing
  - `Dicom.stream_parse/1` and `Dicom.stream_parse_file/2` convenience functions
  - `Dicom.P10.Stream.parse/1` for in-memory binary streaming (`Stream.unfold/2`)
  - `Dicom.P10.Stream.parse_file/2` for file I/O streaming (`Stream.resource/3`)
  - `Dicom.P10.Stream.to_data_set/1` to materialize a stream into a `DataSet`
  - Event types: `:file_meta_start`, `{:file_meta_end, ts_uid}`, `{:element, elem}`,
    `{:sequence_start, tag, length}`, `:sequence_end`, `{:item_start, length}`,
    `:item_end`, `{:pixel_data_start, tag, vr}`, `{:pixel_data_fragment, index, binary}`,
    `:pixel_data_end`, `:end`, `{:error, reason}`
  - Source abstraction (`Dicom.P10.Stream.Source`) for binary and file I/O with
    64 KB read-ahead buffering
  - State machine parser (`Dicom.P10.Stream.Parser`) supporting all 4 transfer
    syntaxes, sequences, items, and encapsulated pixel data
- BEAM DICOM library comparison table in the README covering licensing, features,
  test coverage, and CI status
- **Comprehensive PS3.6 data dictionary** — expanded from ~95 hand-written entries
  to 5032 entries generated from the DICOM standard. Overlay repeating group (60XX)
  support included. Previously-unknown tags (especially SQ) now resolve correctly
  in Implicit VR parsing.
- **Specific Character Set support** (`Dicom.CharacterSet`) — decodes text values
  according to (0008,0005). Supports default repertoire, ISO_IR 100 (Latin-1),
  ISO 8859-2 through 9, and ISO_IR 192 (UTF-8). Returns explicit errors for
  unsupported charsets instead of silently producing incorrect text.
- **Expanded transfer syntax registry** — from 9 to 29 entries. Added JPEG-LS,
  JPEG 2000 Part 2, MPEG-2/4, HEVC/H.265, HTJ2K, and JPIP transfer syntaxes.
- Interoperability test suite exercising unknown TS rejection, expanded dictionary
  in implicit VR parsing, rich multi-element roundtrips across all four uncompressed
  transfer syntaxes, encapsulated pixel data with compressed TSes, and character set
  integration through the parse pipeline.

### Fixed

- Reject serialization when required File Meta Information is missing
- Encode numeric values according to their VR width instead of forcing 32-bit
  little-endian output
- Respect transfer syntax endianness for numeric value encoding and decoding
- Preserve leading spaces for padded text VRs such as `LT` and `UT`
- Return `{:error, :unexpected_end}` for truncated defined-length sequence payloads
  instead of crashing the parser
- Tighten UID validation for invalid root arcs

### Changed

- **Strict transfer syntax policy** — `TransferSyntax.encoding/2` now returns
  `{:error, :unknown_transfer_syntax}` for unrecognized UIDs instead of silently
  falling back to Explicit VR Little Endian. Use `encoding(uid, lenient: true)` to
  opt in to the old fallback behavior. Reader and writer now propagate this error.
- Expanded test coverage to 448 tests (streaming, big-endian numeric value
  decode/encode, eager path, edge cases, property-based equivalence) at 91%+ coverage

## [0.1.0] - 2026-03-17

### Added

- P10 file parsing with `Dicom.parse/1` and `Dicom.parse_file/1`
- P10 file writing with `Dicom.write/1` and `Dicom.write_file/2`
- Data set creation and manipulation via `Dicom.DataSet`
- DICOM data dictionary (PS3.6) with tag lookup via `Dicom.Dictionary.Registry`
- Tag constants for common clinical attributes via `Dicom.Tag`
- UID constants for SOP Classes and Transfer Syntaxes via `Dicom.UID`
- UID generation and validation
- VR-aware value encoding and decoding via `Dicom.Value`
- Transfer syntax support:
  - Implicit VR Little Endian
  - Explicit VR Little Endian
  - Explicit VR Big Endian (retired)
  - Deflated Explicit VR Little Endian
- Sequence support (SQ) with defined and undefined length items
- Encapsulated pixel data with fragment parsing
- File Meta Information validation per PS3.10 Section 7.1
- Preamble validation and sanitization (PS3.10 Section 7.5)
- Data Set Trailing Padding support (FFFC,FFFC)

### Changed

- Expanded PS3.10 support with compliance tests, deflated transfer syntax support,
  sequence handling, additional VR coverage, and Explicit VR Big Endian support
- Hardened parsing and validation around malformed input, edge cases, and file meta handling
- Optimized hot paths in the reader, writer, transfer syntax registry, and VR utilities
- Prepared the project for public release with CI, licensing, contribution policy,
  security policy, and open-source documentation

### Performance

- Performance benchmarks (parse, write, roundtrip, VR lookup)
- 100% test coverage across all 12 modules (259 tests)
- Property-based tests with StreamData for encode/decode roundtrips

[Unreleased]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.5.1...HEAD
[0.5.1]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.4.5...v0.5.0
[0.4.5]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.4.2...v0.4.5
[0.4.2]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.4.0...v0.4.2
[0.4.0]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/Balneario-de-Cofrentes/dicom/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/Balneario-de-Cofrentes/dicom/commit/cdd216b7adc62cb8282f7a150130f7b51d7e724f