diff --git a/atlas/tasks.py b/atlas/tasks.py index 266792ec..d7b788f9 100644 --- a/atlas/tasks.py +++ b/atlas/tasks.py @@ -336,6 +336,32 @@ def series_reconstruct_task( src_y = source_for_patient_axis[1] src_z = source_for_patient_axis[2] + # Build patient-axis basis vectors expressed in source/world space. + # This preserves source world-frame orientation (including obliquity/sign) + # for derived series instead of hardcoding cardinal axes. + patient_x_vec = np.asarray(source_axis_dirs[src_x], dtype=float) * float(sign_for_patient_axis[0]) + patient_y_vec = np.asarray(source_axis_dirs[src_y], dtype=float) * float(sign_for_patient_axis[1]) + patient_z_vec = np.asarray(source_axis_dirs[src_z], dtype=float) * float(sign_for_patient_axis[2]) + + def _norm_vec(vec): + nrm = float(np.linalg.norm(vec)) + if nrm == 0: + return np.asarray(vec, dtype=float) + return np.asarray(vec, dtype=float) / nrm + + patient_x_vec = _norm_vec(patient_x_vec) + patient_y_vec = _norm_vec(patient_y_vec) + patient_z_vec = _norm_vec(patient_z_vec) + + def _orientation_pair(col_vec, row_vec, desired_normal): + col = _norm_vec(col_vec) + row = _norm_vec(row_vec) + desired = _norm_vec(desired_normal) + normal = np.cross(col, row) + if float(np.dot(normal, desired)) < 0: + row = -row + return [float(col[0]), float(col[1]), float(col[2]), float(row[0]), float(row[1]), float(row[2])] + spacing_x = source_axis_spacings[src_x] spacing_y = source_axis_spacings[src_y] spacing_z = source_axis_spacings[src_z] @@ -350,7 +376,7 @@ def series_reconstruct_task( vol_zyx = np.flip(vol_zyx, axis=2) def source_indices_from_patient_zyx(iz, iy, ix): - src_indices = [0, 0, 0] + src_indices = [0.0, 0.0, 0.0] z_index = float(iz) y_index = float(iy) @@ -378,10 +404,6 @@ def series_reconstruct_task( ) return [float(pos[0]), float(pos[1]), float(pos[2])] - spacing_x = source_axis_spacings[src_x] - spacing_y = source_axis_spacings[src_y] - spacing_z = source_axis_spacings[src_z] - def _reduce_slab(slab, reduce_axis=0): if recon_thickness_mode == "max": out = np.max(slab, axis=reduce_axis) @@ -440,8 +462,8 @@ def series_reconstruct_task( "pixel_spacing_out": [float(spacing_y), float(spacing_x)], "spacing_between_slices_out": float(plane_spacing), "slice_thickness_out": float(plane_thickness), - "image_orientation_out": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0], - "plane_normal_out": [0.0, 0.0, 1.0], + "image_orientation_out": _orientation_pair(patient_x_vec, patient_y_vec, patient_z_vec), + "plane_normal_out": [float(patient_z_vec[0]), float(patient_z_vec[1]), float(patient_z_vec[2])], "position_fn": lambda idx, centers=slab_centers_idx: position_from_patient_zyx(centers[idx], 0.0, 0.0), } elif plane_norm == "coronal": @@ -452,13 +474,13 @@ def series_reconstruct_task( ) plane_builders[plane_norm] = { "count": int(len(slab_indices)), - "slice_fn": lambda idx, slabs=slab_indices: np.flipud(_reduce_slab(vol_zyx[:, slabs[idx], :], reduce_axis=1)), + "slice_fn": lambda idx, slabs=slab_indices: _reduce_slab(vol_zyx[:, slabs[idx], :], reduce_axis=1), "pixel_spacing_out": [float(spacing_z), float(spacing_x)], "spacing_between_slices_out": float(plane_spacing), "slice_thickness_out": float(plane_thickness), - "image_orientation_out": [1.0, 0.0, 0.0, 0.0, 0.0, -1.0], - "plane_normal_out": [0.0, 1.0, 0.0], - "position_fn": lambda idx, centers=slab_centers_idx: position_from_patient_zyx(float(vol_zyx.shape[0] - 1), centers[idx], 0.0), + "image_orientation_out": _orientation_pair(patient_x_vec, patient_z_vec, patient_y_vec), + "plane_normal_out": [float(patient_y_vec[0]), float(patient_y_vec[1]), float(patient_y_vec[2])], + "position_fn": lambda idx, centers=slab_centers_idx: position_from_patient_zyx(0.0, centers[idx], 0.0), } elif plane_norm == "sagittal": plane_spacing = float(requested_slice_spacing) if requested_slice_spacing is not None else float(spacing_x) @@ -468,13 +490,13 @@ def series_reconstruct_task( ) plane_builders[plane_norm] = { "count": int(len(slab_indices)), - "slice_fn": lambda idx, slabs=slab_indices: np.flipud(_reduce_slab(vol_zyx[:, :, slabs[idx]], reduce_axis=2)), + "slice_fn": lambda idx, slabs=slab_indices: _reduce_slab(vol_zyx[:, :, slabs[idx]], reduce_axis=2), "pixel_spacing_out": [float(spacing_z), float(spacing_y)], "spacing_between_slices_out": float(plane_spacing), "slice_thickness_out": float(plane_thickness), - "image_orientation_out": [0.0, 1.0, 0.0, 0.0, 0.0, -1.0], - "plane_normal_out": [1.0, 0.0, 0.0], - "position_fn": lambda idx, centers=slab_centers_idx: position_from_patient_zyx(float(vol_zyx.shape[0] - 1), 0.0, centers[idx]), + "image_orientation_out": _orientation_pair(patient_y_vec, patient_z_vec, patient_x_vec), + "plane_normal_out": [float(patient_x_vec[0]), float(patient_x_vec[1]), float(patient_x_vec[2])], + "position_fn": lambda idx, centers=slab_centers_idx: position_from_patient_zyx(0.0, 0.0, centers[idx]), } if not plane_builders: diff --git a/atlas/templates/atlas/series_reconstruct_advanced.html b/atlas/templates/atlas/series_reconstruct_advanced.html index 3a77e3e8..fe08e308 100644 --- a/atlas/templates/atlas/series_reconstruct_advanced.html +++ b/atlas/templates/atlas/series_reconstruct_advanced.html @@ -33,7 +33,7 @@