diff --git a/MRIcroGL.app/Contents/Resources/script/startup.py b/MRIcroGL.app/Contents/Resources/script/startup.py new file mode 100644 index 0000000..fa6e29f --- /dev/null +++ b/MRIcroGL.app/Contents/Resources/script/startup.py @@ -0,0 +1,25 @@ +import gl +gl.resetdefaults() +gl.loadimage('spm152') +#open overlay: show positive regions +gl.overlayload('aal') +gl.scriptformvisible(1) +#gl.atlashide(1) +#gl.atlasshow(1, 17) +gl.atlasshow(1, (22, 23, 17)) +mx = gl.atlasmaxindex(1) +print(mx) +#gl.opacity(1,50) +# +# if (layer > 0) then +# Vol1.UpdateOverlays(vols); +#str = gl.atlaslabels(1) +#print(str) +#gl.minmax(1, 4, 100) + + +#gl.overlayload('aal') +# + +#gl.generateclusters(0) +#gl.generateclusters(0, 0.5, 32, 1, 0) \ No newline at end of file diff --git a/MRIcroGL.app/Contents/Resources/script/startupx.py b/MRIcroGL.app/Contents/Resources/script/startupx.py new file mode 100644 index 0000000..03d9855 --- /dev/null +++ b/MRIcroGL.app/Contents/Resources/script/startupx.py @@ -0,0 +1,4 @@ +import gl +gl.resetdefaults() +gl.loadimage('spm152') +gl.pitch(45) \ No newline at end of file diff --git a/MRIcroGL.app/Contents/Resources/script/ystartup.py b/MRIcroGL.app/Contents/Resources/script/ystartup.py deleted file mode 100644 index d027179..0000000 --- a/MRIcroGL.app/Contents/Resources/script/ystartup.py +++ /dev/null @@ -1,13 +0,0 @@ -import gl -gl.resetdefaults() -#the desai atlas comes with AFNI -# if this fails, use File/OpenAFNI to set AFNI folder -# gl.loadimage(pth+'stats.FT+orig.HEAD') -#gl.loadimage('TTatlas+tlrc.HEAD') -gl.resetdefaults() -gl.orthoviewmm(0,0,0) -#gl.loadimage('TTatlas+tlrc') -gl.loadimage('/Users/chris/src/AICHAlr') - -#gl.generateclusters(0) -#gl.generateclusters(0, 0.5, 32, 1, 0) \ No newline at end of file diff --git a/MRIcroGL.lps b/MRIcroGL.lps index bcd49fa..1690c21 100755 --- a/MRIcroGL.lps +++ b/MRIcroGL.lps @@ -3,7 +3,7 @@ - + @@ -17,8 +17,9 @@ - - + + + @@ -37,9 +38,11 @@ - - + + + + @@ -58,9 +61,9 @@ - + - + @@ -72,10 +75,12 @@ - + - + + + @@ -143,40 +148,41 @@ - + - - - + + + + - + - - + + - - - + + + - + @@ -184,8 +190,9 @@ - - + + + @@ -194,254 +201,285 @@ - + - + - - - - - + + + + - - - + + + + - - - - - - - - - - + + + - - - + + + - + + + + + + + + + - + - - - + + - - - - - + + + + + + - - - - - - - - - - - - - - + - - + + - + - + - - + + - - - + + + - - - + + + - - + - + - - + + - + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + + + + + diff --git a/MRIcroGL_Metal.lps b/MRIcroGL_Metal.lps index a3c432e..21c1b10 100644 --- a/MRIcroGL_Metal.lps +++ b/MRIcroGL_Metal.lps @@ -3,7 +3,7 @@ - + @@ -15,8 +15,9 @@ - - + + + @@ -106,7 +107,7 @@ - + @@ -134,17 +135,17 @@ - - + + - + - + @@ -164,8 +165,7 @@ - - + @@ -337,145 +337,153 @@ - - + + - + - + - + + + + + + + + + - - + + - - + + - + - + - + - + - + - + - + - - + + - - + + - + - + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/Resources/script/basic.py b/Resources/script/basic.py old mode 100755 new mode 100644 diff --git a/Resources/script/clip.py b/Resources/script/clip.py old mode 100755 new mode 100644 diff --git a/Resources/script/cluster.py b/Resources/script/cluster.py old mode 100755 new mode 100644 diff --git a/Resources/script/ct_abdomen.py b/Resources/script/ct_abdomen.py old mode 100755 new mode 100644 diff --git a/Resources/script/ct_head.py b/Resources/script/ct_head.py old mode 100755 new mode 100644 diff --git a/Resources/script/cutout.py b/Resources/script/cutout.py old mode 100755 new mode 100644 diff --git a/Resources/script/explode.py b/Resources/script/explode.py old mode 100755 new mode 100644 diff --git a/Resources/script/explode2.py b/Resources/script/explode2.py old mode 100755 new mode 100644 diff --git a/Resources/script/glass.py b/Resources/script/glass.py old mode 100755 new mode 100644 diff --git a/Resources/script/help.py b/Resources/script/help.py old mode 100755 new mode 100644 index 9315002..66f228c --- a/Resources/script/help.py +++ b/Resources/script/help.py @@ -1,3 +1,6 @@ +import sys +print(sys.version) +print(sys.path) import gl print(gl.__doc__) for key in dir( gl ): diff --git a/Resources/script/hidezeros.py b/Resources/script/hidezeros.py old mode 100755 new mode 100644 diff --git a/Resources/script/invert.py b/Resources/script/invert.py old mode 100755 new mode 100644 diff --git a/Resources/script/jagged.py b/Resources/script/jagged.py old mode 100755 new mode 100644 diff --git a/Resources/script/mask_atlas.py b/Resources/script/mask_atlas.py new file mode 100644 index 0000000..883b30a --- /dev/null +++ b/Resources/script/mask_atlas.py @@ -0,0 +1,9 @@ +import gl +gl.resetdefaults() +gl.loadimage('spm152') +#open overlay: show only 3 regions +gl.overlayload('aal') +gl.atlasshow(1, (22, 23, 17)) +#gl.atlashide(1) +#gl.atlasshow(1, 17) +#gl.atlashide(1, (22, 23, 17)) diff --git a/Resources/script/mip.py b/Resources/script/mip.py old mode 100755 new mode 100644 diff --git a/Resources/script/mosaic.py b/Resources/script/mosaic.py old mode 100755 new mode 100644 diff --git a/Resources/script/mosaic2.py b/Resources/script/mosaic2.py old mode 100755 new mode 100644 diff --git a/Resources/shader/Default.glsl b/Resources/shader/Default.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Edges.glsl b/Resources/shader/Edges.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Glass.glsl b/Resources/shader/Glass.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/MIP.glsl b/Resources/shader/MIP.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Matte.glsl b/Resources/shader/Matte.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Minimal.glsl b/Resources/shader/Minimal.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Occlusion.glsl b/Resources/shader/Occlusion.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/OverlaySurface.glsl b/Resources/shader/OverlaySurface.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Scale.glsl b/Resources/shader/Scale.glsl new file mode 100644 index 0000000..dfcba24 --- /dev/null +++ b/Resources/shader/Scale.glsl @@ -0,0 +1,161 @@ +//pref +brighten|float|0.5|2|3.5 +surfaceColor|float|0.0|1.0|1.0 +translucency|float|0.01|0.5|0.5 +overlayFuzzy|float|0.01|0.5|1 +overlayDepth|float|0.0|0.15|0.99 +overlayClip|float|0|0|1|Does clipping also influence overlay layers? +//frag +uniform float brighten = 1.5; +uniform float surfaceColor = 1.0; +uniform float translucency = 0.5; +uniform float overlayDepth = 0.3; +uniform float overlayFuzzy = 0.5; +uniform float overlayClip = 0.0; +uniform mat3 NormalMatrix; +uniform sampler2D matcap2D; + +void main() { + vec3 start = TexCoord1.xyz; + vec3 backPosition = GetBackPosition(start); + vec3 dir = backPosition - start; + float len = length(dir); + dir = normalize(dir); + vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize); + vec4 gradSample, colorSample; + float bgNearest = len; //assume no hit + vec4 colAcc = vec4(0.0,0.0,0.0,0.0); + vec4 prevGrad = vec4(0.0,0.0,0.0,0.0); + //background pass + float noClipLen = len; + vec4 samplePos = vec4(start.xyz, 0.0); + vec4 clipPos = applyClip(dir, samplePos, len); + float opacityCorrection = stepSize/sliceSize; + //fast pass - optional + fastPass (len, dir, intensityVol, samplePos); + #if ( __VERSION__ > 300 ) + if ((samplePos.a > len) && ( overlays < 1 )) { //no hit + FragColor = colAcc; + return; + } + #else + if ((textureSz.x < 1) || ((samplePos.a > len) && ( overlays < 1 ))) { //no hit + gl_FragColor = colAcc; + return; + } + #endif + if (samplePos.a < clipPos.a) { + samplePos = clipPos; + bgNearest = clipPos.a; + float stepSizeX2 = samplePos.a + (stepSize * 2.0); + while (samplePos.a <= stepSizeX2) { + colorSample = texture3Df(intensityVol,samplePos.xyz); + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + colorSample.a = clamp(colorSample.a*3.0,0.0, 1.0); + colorSample.rgb *= colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + samplePos += deltaDir; + } + + } + //end fastpass - optional + float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453); + samplePos += deltaDir * ran; + vec3 defaultDiffuse = vec3(0.5, 0.5, 0.5); + float transRecip = 1.0/translucency; + + while (samplePos.a <= len) { + colorSample = texture3Df(intensityVol,samplePos.xyz); + if (colorSample.a > 0.0) { + //colorSample.a = 1.0 - pow(1.0- colorSample.a, translucency); + colorSample.a = (colorSample.a / (((transRecip - 2)*(1 - colorSample.a))+1)); + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + bgNearest = min(samplePos.a,bgNearest); + gradSample= texture3Df(gradientVol,samplePos.xyz); + gradSample.rgb = normalize(gradSample.rgb*2.0 - 1.0); + //reusing Normals http://www.marcusbannerman.co.uk/articles/VolumeRendering.html + if (gradSample.a < prevGrad.a) + gradSample.rgb = prevGrad.rgb; + prevGrad = gradSample; + vec3 n = normalize(NormalMatrix * gradSample.rgb); + vec3 d = texture2D(matcap2D, n.xy * 0.5 + 0.5).rgb; + vec3 surf = mix(defaultDiffuse, colorSample.rgb, surfaceColor); //0.67 as default Brighten is 1.5 + colorSample.rgb = d * surf * brighten * colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + if ( colAcc.a > 0.95 ) + break; + } + samplePos += deltaDir; + } //while samplePos.a < len + colAcc.a = colAcc.a/0.95; + colAcc.a *= backAlpha; + if ( overlays< 1 ) { + #if ( __VERSION__ > 300 ) + FragColor = colAcc; + #else + gl_FragColor = colAcc; + #endif + return; + } + //overlay pass + float overFarthest = len; + float ambient = 1.0; + float diffuse = 0.3; + float specular = 0.25; + float shininess = 10.0; + vec4 overAcc = vec4(0.0,0.0,0.0,0.0); + prevGrad = vec4(0.0,0.0,0.0,0.0); + if (overlayClip > 0) + samplePos = clipPos; + else { + len = noClipLen; + samplePos = vec4(start.xyz +deltaDir.xyz* ran, 0.0); + } + //fast pass - optional + clipPos = samplePos; + fastPass (len, dir, intensityOverlay, samplePos); + if (samplePos.a < clipPos.a) + samplePos = clipPos; + //deltaDir = vec4(dir.xyz * stepSize, stepSize); + //end fastpass - optional + while (samplePos.a <= len) { + colorSample = texture3Df(intensityOverlay,samplePos.xyz); + if (colorSample.a > 0.00) { + if (overAcc.a < 0.3) + overFarthest = samplePos.a; + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + colorSample.a *= overlayFuzzy; + //gradient based lighting http://www.mccauslandcenter.sc.edu/mricrogl/gradients + gradSample = texture3Df(gradientOverlay,samplePos.xyz); //interpolate gradient direction and magnitude + gradSample.rgb = normalize(gradSample.rgb*2.0 - 1.0); + //reusing Normals http://www.marcusbannerman.co.uk/articles/VolumeRendering.html + if (gradSample.a < prevGrad.a) + gradSample.rgb = prevGrad.rgb; + prevGrad = gradSample; + float lightNormDot = dot(gradSample.rgb, lightPosition); + vec3 a = colorSample.rgb * ambient; + vec3 d = max(lightNormDot, 0.0) * colorSample.rgb * diffuse; + float s = specular * pow(max(dot(reflect(lightPosition, gradSample.rgb), dir), 0.0), shininess); + colorSample.rgb = a + d + s; + colorSample.rgb *= colorSample.a; + overAcc= (1.0 - overAcc.a) * colorSample + overAcc; + if (overAcc.a > 0.95 ) + break; + } + samplePos += deltaDir; + } //while samplePos.a < len + overAcc.a = overAcc.a/0.95; + float overMix = overAcc.a; + if (((overFarthest) > bgNearest) && (colAcc.a > 0.0)) { //background (partially) occludes overlay + float dx = (overFarthest - bgNearest)/1.73; + dx = colAcc.a * pow(dx, overlayDepth); + overMix *= 1.0 - dx; + } + colAcc.rgb = mix(colAcc.rgb, overAcc.rgb, overMix); + colAcc.a = max(colAcc.a, overAcc.a); + #if ( __VERSION__ > 300 ) + FragColor = colAcc; + #else + gl_FragColor = colAcc; + #endif +} \ No newline at end of file diff --git a/Resources/shader/Shell.glsl b/Resources/shader/Shell.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/ShellEdges.glsl b/Resources/shader/ShellEdges.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Shiny.glsl b/Resources/shader/Shiny.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Slow.glsl b/Resources/shader/Slow.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/SpecialEffects.glsl b/Resources/shader/SpecialEffects.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Standard.glsl b/Resources/shader/Standard.glsl old mode 100755 new mode 100644 diff --git a/Resources/shader/Tomography.glsl b/Resources/shader/Tomography.glsl old mode 100755 new mode 100644 diff --git a/colorTable.pas b/colorTable.pas index ab31eb3..89f5186 100755 --- a/colorTable.pas +++ b/colorTable.pas @@ -302,7 +302,6 @@ procedure TCLUT.GenerateLUT(saturation: float = 1.0; LabelAlpha: integer = -1); fLUT[255].R := lPreInvert[1].R; fLUT[255].G := lPreInvert[1].G; fLUT[255].B := lPreInvert[1].B; - end; end; diff --git a/drawvolume.pas b/drawvolume.pas index de24b3a..fe5c540 100644 --- a/drawvolume.pas +++ b/drawvolume.pas @@ -51,6 +51,7 @@ interface public //constructor Create(); + Filename: string; PenColorOnRelease: integer; property OpacityFraction: single read fOpacityFrac write fOpacityFrac; property ColorTable: TLUT read colorLUt; @@ -1754,6 +1755,7 @@ constructor TDraw.Create();//niftiFileName: string; tarMat: TMat4; tarDim: TVec3 view2d := nil; undo2d := nil; modified2d := nil; + filename := ''; //view3d := nil; //isForceCreate := false; isMouseDown:= false; diff --git a/glvolume2.pas b/glvolume2.pas index 86474bc..2efe80b 100644 --- a/glvolume2.pas +++ b/glvolume2.pas @@ -49,10 +49,11 @@ TGPUVolume = class {$ENDIF} {$IFDEF COREGL}vboBox3D: GLuint;{$ENDIF} shaderPrefs: TShaderPrefs; - RayCastQuality1to5, maxDim,fAzimuth,fElevation, overlayNum, overlayGradTexWidth: integer; + RayCastQuality1to5, maxDim,fAzimuth,fElevation, fPitch, overlayNum, overlayGradTexWidth: integer; fDistance: single; fLightPos: TVec4; fClipPlane: TVec4; + {$IFDEF MTX}fModelMatrix: TMat4;{$ENDIF} {$IFDEF CLRBAR}clrbar: TGPUClrbar;{$ENDIF} glControl: TOpenGLControl; prefLoc: array [1..kMaxUniform] of GLint; @@ -103,6 +104,8 @@ TGPUVolume = class procedure SetShaderSlider(idx: integer; newVal: single); property Azimuth: integer read fAzimuth write fAzimuth; property Elevation: integer read fElevation write fElevation; + property Pitch: integer read fPitch write fPitch; + {$IFDEF MTX} property ModelMatrix3D: TMat4 read fModelMatrix write fModelMatrix; {$ENDIF} property Distance: single read fDistance write fDistance; property LightPosition: TVec4 read fLightPos write fLightPos; property ClipPlane: TVec4 read fClipPlane write fClipPlane; @@ -1408,6 +1411,14 @@ constructor TGPUVolume.Create(fromView: TOpenGLControl); fDistance := kDefaultDistance; fAzimuth := 110; fElevation := 30; + fPitch := 0; + {$IFDEF MTX} + fModelMatrix := TMat4.Identity; + fModelMatrix *= TMat4.Translate(0, 0, -fDistance); + fModelMatrix *= TMat4.RotateY(-DegToRad(32)); + fModelMatrix *= TMat4.RotateX(-DegToRad(90-fElevation)); + fModelMatrix *= TMat4.RotateZ(DegToRad(fAzimuth)); + {$ENDIF} overlayNum := 0; overlayGradTexWidth := 2; //refresh RaycastQuality1to5 := 5; @@ -1684,7 +1695,7 @@ function TGPUVolume.LoadTexture(var vol: TNIfTI; deferGradients: boolean): boole glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_HEIGHT, @height); glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_DEPTH, @depth); //https://www.opengl.org/archives/resources/faq/technical/texture.htm - printf(format('intensityTexture3D proxy test %dx%dx%d',[width, height, depth])); + // printf(format('intensityTexture3D proxy test %dx%dx%d',[width, height, depth])); if (width < 1) then begin printf(format('Unable to create large intensity texture (%dx%dx%d). Solution: adjust "MaxVox" or press "Reset" button in preferences.', [Vol.Dim.X, Vol.Dim.Y, Vol.Dim.Z])); glControl.ReleaseContext; @@ -1900,7 +1911,6 @@ procedure TGPUVolume.PaintMosaicRender(var vol: TNIfTI; lRender: TMosaicRender); modelMatrix *= TMat4.Translate(-vol.Scale.X/2, -vol.Scale.Y/2, -vol.Scale.Z/2); modelLightPos := (modelMatrix.Transpose * fLightPos); modelMatrix *= TMat4.Scale(vol.Scale.X, vol.Scale.Y, vol.Scale.Z); //for volumes that are rectangular not square - glActiveTexture(GL_TEXTURE6); glBindTexture(GL_TEXTURE_2D, matcap2D); glUniform1i(matcapLoc, 6); @@ -2396,10 +2406,16 @@ procedure TGPUVolume.Paint(var vol: TNIfTI); glUniform1f(sliceSizeLoc, 1/maxDim); //glUniform1i(loopsLoc,round(maxDim*2.2)); //glUniform3f(clearColorLoc, fClearColor.r/255, fClearColor.g/255, fClearColor.b/255); + {$IFDEF MTX} + modelMatrix := fModelMatrix; + {$ELSE} modelMatrix := TMat4.Identity; modelMatrix *= TMat4.Translate(0, 0, -fDistance); + modelMatrix *= TMat4.RotateX(-DegToRad(90-fElevation)); modelMatrix *= TMat4.RotateZ(DegToRad(fAzimuth)); + modelMatrix *= TMat4.RotateX(DegToRad(fPitch)); + {$ENDIF} modelMatrix *= TMat4.Translate(-vol.Scale.X/2, -vol.Scale.Y/2, -vol.Scale.Z/2); modelLightPos := (modelMatrix.Transpose * fLightPos); modelLightPos := modelLightPos.Normalize; @@ -2509,8 +2525,9 @@ procedure TGPUVolume.Paint(var vol: TNIfTI); glCullFace(GL_BACK); {$ENDIF} //gCube.Size := 0.02; - gCube.Azimuth:=fAzimuth; - gCube.Elevation:=-fElevation; + gCube.Azimuth := fAzimuth; + gCube.Elevation := -fElevation; + gCube.Pitch := fPitch; gCube.Draw(glControl.ClientWidth, glControl.ClientHeight); end; {$ENDIF}{$ENDIF} diff --git a/mainunit.lfm b/mainunit.lfm index ead07ae..e0ca298 100755 --- a/mainunit.lfm +++ b/mainunit.lfm @@ -1953,7 +1953,7 @@ object GLForm1: TGLForm1 OnClick = ReorientMenuClick end object ResizeMenu1: TMenuItem - Caption = 'Resize and Resample' + Caption = 'Resize' OnClick = ResizeMenuClick end object CropMenu1: TMenuItem @@ -1970,6 +1970,7 @@ object GLForm1: TGLForm1 end object DrawSaveMenu: TMenuItem Caption = 'Save VOI' + ShortCut = 16467 OnClick = DrawSaveMenuClick end object DrawCloseMenu: TMenuItem diff --git a/mainunit.pas b/mainunit.pas index cb2f5aa..d852d1a 100755 --- a/mainunit.pas +++ b/mainunit.pas @@ -42,7 +42,7 @@ interface nifti_hdr_view, fsl_calls, math, nifti, niftis, prefs, dcm2nii, strutils, drawVolume, autoroi, VectorMath; const - kVers = '1.2.20200707e'; //+ save image + kVers = '1.2.20201102'; type { TGLForm1 } @@ -481,7 +481,7 @@ TGLForm1 = class(TForm) procedure LayerResetBrightnessMenuClick(Sender: TObject); procedure LayerUpDownClick(Sender: TObject); procedure NewWindowMenuClick(Sender: TObject); - function NiftiSaveDialogFilename(isVOI: boolean = false): string; + function NiftiSaveDialogFilename(isVOI: boolean = false; initialFilename: string = ''): string; procedure SaveNIfTIMenuClick(Sender: TObject); procedure ClipIntensity(Lo, Hi: single); procedure ScriptingPyVersionClick(Sender: TObject); @@ -573,6 +573,7 @@ TGLForm1 = class(TForm) procedure ViewGPUDblClick(Sender: TObject); procedure ViewGPUKeyPress(Sender: TObject; var Key: char); procedure ViewGPUKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); + procedure ViewGPUKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ViewGPUgKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ViewGPUgResize(Sender: TObject); procedure setShaderSliders; @@ -1567,6 +1568,84 @@ function PyGUI_INPUT(Self, Args : PPyObject): PPyObject; cdecl; function PyATLASSHOWHIDE(Self, Args : PPyObject; isHide: boolean): PPyObject; cdecl; //https://stackoverflow.com/questions/8001923/python-extension-module-with-variable-number-of-arguments +var + layer, i, isHide01, n, idx, maxIdx: integer; + //f: single; + v: TNIfTI; + ob, obi: PPyObject; +begin + Result:= GetPythonEngine.PyBool_FromLong(Ord(False)); + n := GetPythonEngine.PyTuple_Size(args); + if ((n < 1) or (n > 2))then begin + GLForm1.ScriptOutputMemo.lines.add('atlashide: requires at least two arguments (layer, (regions)) '); + exit; + end; + ob := GetPythonEngine.PyTuple_GetItem(Args,0); + if(ob = nil) then exit; + if (GetPythonEngine.PyNumber_Check(ob) <> 1) then begin + GLForm1.ScriptOutputMemo.lines.add('atlashide: layer argument should be an integer'); + exit; + end; + layer := GetPythonEngine.PyLong_AsLong(ob); + if (layer < 0) or (layer >= vols.NumLayers) then begin + GLForm1.ScriptOutputMemo.lines.add('atlashide: layer should be in range 0..'+inttostr(vols.NumLayers-1)); + exit; + end; + if not vols.Layer(layer,v) then begin + GLForm1.ScriptOutputMemo.lines.add('atlashide: fatal error'); + exit; + end; + if (not v.IsLabels) or (v.fLabels.count < 1) then begin + GLForm1.ScriptOutputMemo.lines.add('atlashide: selected layer is not an indexed label map (NIfTI header intention not Labels)'); + GLForm1.updateTimer.Enabled := true; + exit; + end; + //GLForm1.ScriptOutputMemo.lines.add('atlashide:::: '+inttostr(v.fLabels.count)); + maxIdx := v.fLabels.count - 1; + //for i := 0 to v.fLabels.count -1 do + // GLForm1.ScriptOutputMemo.lines.add(inttostr(i)+':'+v.fLabels[i]); + if (v.fLabelMask = nil) or (length(v.fLabelMask) < v.fLabels.count) then begin + setlength(v.fLabelMask, v.fLabels.count); + for i := 0 to maxIdx do + v.fLabelMask[i] := 0; //assume no masks + end; + Result:= GetPythonEngine.PyBool_FromLong(Ord(True)); + isHide01 := 0; + if isHide then + isHide01 := 1; + for i := 0 to maxIdx do + v.fLabelMask[i] := 1 - isHide01; + if n < 2 then begin + for i := 0 to maxIdx do + v.fLabelMask[i] := isHide01; + v.ForceUpdate; + exit; + end; + ob := GetPythonEngine.PyTuple_GetItem(Args,1); + if(ob = nil) then exit; + if not (GetPythonEngine.PyTuple_Check(ob)) then begin //2nd argument of atlashide(1,(17,22)) is tuple, atlashide(1,(17)) is integer + idx := GetPythonEngine.PyLong_AsLong(ob); + idx := min(idx, maxIdx); + idx := max(0, idx); + v.fLabelMask[idx] := isHide01; + end else begin + n := GetPythonEngine.PyTuple_Size(ob); + if n < 1 then exit; + for i := 0 to (n-1) do begin + obi := GetPythonEngine.PyTuple_GetItem(ob,i); + idx := GetPythonEngine.PyLong_AsLong(obi); + idx := min(idx, maxIdx); + idx := max(0, idx); + v.fLabelMask[idx] := isHide01; + end; + end; + v.ForceUpdate; + //if (layer > 0) then + // Vol1.UpdateOverlays(vols); +end; //PyATLASSHOWHIDE() + +(*function PyATLASSHOWHIDE(Self, Args : PPyObject; isHide: boolean): PPyObject; cdecl; +//https://stackoverflow.com/questions/8001923/python-extension-module-with-variable-number-of-arguments var layer, i, isHide01, n, idx, maxIdx: integer; //f: single; @@ -1639,7 +1718,7 @@ function PyATLASSHOWHIDE(Self, Args : PPyObject; isHide: boolean): PPyObject; cd //if (layer > 0) then // Vol1.UpdateOverlays(vols); end; //PyATLASSHOWHIDE() - +*) function PyATLASHIDE(Self, Args : PPyObject): PPyObject; cdecl; begin result := PyATLASSHOWHIDE(Self, Args, true); @@ -1861,6 +1940,55 @@ function PyCOLOREDITOR(Self, Args : PPyObject): PPyObject; cdecl; ViewGPU1.Invalidate; end; +function PyCOLORNODE(Self, Args : PPyObject): PPyObject; cdecl; +var + layer, index, intensity, R,G,B, A: integer; + vol: TNIfTI; +begin + Result:= GetPythonEngine.PyBool_FromLong(Ord(True)); + with GetPythonEngine do + if Boolean(PyArg_ParseTuple(Args, 'iiiiiiI:colornode', @layer, @index, @intensity, @R,@G,@B, @A)) then begin + if not vols.Layer(layer,vol) then exit; + if (vol.CX.FullColorTable.numnodes < 2) then exit; + if (index >= vol.CX.FullColorTable.numnodes) then begin + pyprintf(format('index should be in the range 0..%d',[vol.CX.FullColorTable.numnodes - 1])); + exit; + end; + vol.CX.ChangeNode(index, intensity,R,G,B,A); + end; + ViewGPU1.Invalidate; +end; + +function PyCOLORNAME(Self, Args : PPyObject): PPyObject; cdecl; +var + PtrName: PChar; + StrName: string; + V, i: integer; + ret: boolean; + niftiVol: TNIfTI; +begin + Result:= GetPythonEngine.PyBool_FromLong(Ord(FALSE)); + with GetPythonEngine do + if Boolean(PyArg_ParseTuple(Args, 'is:overlaycolorname', @V, @PtrName)) then + begin + StrName:= string(PtrName); + i := GLForm1.LayerColorDrop.Items.IndexOf(StrName); //search is case-insensitive! + ret := (i >= 0); + Result:= GetPythonEngine.PyBool_FromLong(Ord(True)); + if ret then begin + GLForm1.LayerChange(V, i, -1, kNaNsingle, kNaNsingle); //kNaNsingle + end else if fileexists(StrName) and vols.Layer(V,niftiVol)then begin + while (isBusy) or (GLForm1.Updatetimer.enabled) do + Application.ProcessMessages; + niftiVol.SetDisplayColorScheme(StrName, 0); + niftiVol.CX.NeedsUpdate := true; + niftiVol.CX.GenerateLUT(); + niftiVol.ForceUpdate(); + GLForm1.UpdateTimer.Enabled := true; + end else + Result:= GetPythonEngine.PyBool_FromLong(Ord(false)); + end; +end; function PyVERSION(Self, Args : PPyObject): PPyObject; cdecl; var @@ -1969,6 +2097,21 @@ function PyAZIMUTHELEVATION(Self, Args : PPyObject): PPyObject; cdecl; ViewGPU1.Invalidate; end; +function PyPITCH(Self, Args : PPyObject): PPyObject; cdecl; +var + P : integer; +begin + Result:= GetPythonEngine.PyBool_FromLong(Ord(True)); + with GetPythonEngine do + if Boolean(PyArg_ParseTuple(Args, 'i:pitch', @P)) then begin + Vol1.Pitch := P; + {$IFDEF COMPILEYOKE} + SetShareFloats3D(Vol1.Azimuth, Vol1.Elevation); + {$ENDIF} + end; + ViewGPU1.Invalidate; +end; + function PyDrawLOAD(Self, Args : PPyObject): PPyObject; cdecl; var PtrName: PChar; @@ -2284,27 +2427,6 @@ function PyHIDDENBYCUTOUT(Self, Args : PPyObject): PPyObject; cdecl; end; end; -function PyOVERLAYCOLORNAME(Self, Args : PPyObject): PPyObject; cdecl; -var - PtrName: PChar; - StrName: string; - V, i: integer; - ret: boolean; -begin - Result:= GetPythonEngine.PyBool_FromLong(Ord(FALSE)); - with GetPythonEngine do - if Boolean(PyArg_ParseTuple(Args, 'is:overlaycolorname', @V, @PtrName)) then - begin - StrName:= string(PtrName); - i := GLForm1.LayerColorDrop.Items.IndexOf(StrName); //search is case-insensitive! - ret := (i >= 0); - if ret then begin - GLForm1.LayerChange(V, i, -1, kNaNsingle, kNaNsingle); //kNaNsingle - end; - Result:= GetPythonEngine.PyBool_FromLong(Ord(True)); - end; -end; - function PyOPACITY(Self, Args : PPyObject): PPyObject; cdecl; var PCT: integer; @@ -2850,12 +2972,12 @@ procedure TGLForm1.PyModInitialization(Sender: TObject); begin with Sender as TPythonModule do begin //AddMethod('smoothmask', @PySMOOTHMASK, ' smoothmask(i) -> Blur edges of a masked image.'); - AddMethod('atlashide', @PyATLASHIDE, ' atlashide(layer, indices...) -> Hide all (e.g. "atlashide(1)") or some (e.g. "atlashide(1, 17, 22)") regions of an atlas.'); - AddMethod('atlasshow', @PyATLASSHOW, ' atlasshow(layer, indices...) -> Show all (e.g. "atlasshow(1)") or some (e.g. "atlasshow(1, 17, 22)") regions of an atlas.'); + AddMethod('atlashide', @PyATLASHIDE, ' atlashide(layer, indices...) -> Hide all (e.g. "atlashide(1)") or some (e.g. "atlashide(1, (17, 22))") regions of an atlas.'); + AddMethod('atlasshow', @PyATLASSHOW, ' atlasshow(layer, indices...) -> Show all (e.g. "atlasshow(1)") or some (e.g. "atlasshow(1, (17, 22))") regions of an atlas.'); AddMethod('atlasmaxindex', @PyATLASMAXINDEX, ' atlasmaxindex(layer) -> Returns maximum region humber in specified atlas. For example, if you load the CIT168 atlas (which has 15 regions) as your background image, then atlasmaxindex(0) will return 15.'); AddMethod('atlaslabels', @PyATLASLABELS, ' atlasmaxindex(layer) -> Returns string listing all regions in an atlas'); //AddMethod('array', @PyARRAY, 'profound'); - AddMethod('azimuthelevation', @PyAZIMUTHELEVATION, ' azimuthelevation(azi, elev) -> Sets the camera location.'); + AddMethod('azimuthelevation', @PyAZIMUTHELEVATION, ' azimuthelevation(azi, elev) -> Sets the render camera location.'); AddMethod('backcolor', @PyBACKCOLOR, ' backcolor(r, g, b) -> changes the background color, for example backcolor(255, 0, 0) will set a bright red background'); AddMethod('bmpzoom', @PyBMPZOOM, ' bmpzoom(z) -> changes resolution of savebmp(), for example bmpzoom(2) will save bitmaps at twice screen resolution'); AddMethod('bmptransparent', @pyBMPTRANSPARENT, ' bmptransparent(v) -> set if bitmaps use transparent (1) or opaque (0) background'); @@ -2864,7 +2986,8 @@ procedure TGLForm1.PyModInitialization(Sender: TObject); AddMethod('colorbarsize', @PyCOLORBARSIZE, ' colorbarsize(p) -> Change width of color bar f is a value 0.01..0.5 that specifies the fraction of the screen used by the colorbar.'); AddMethod('coloreditor', @PyCOLOREDITOR, ' coloreditor(s) -> Show (1) or hide (0) color editor and histogram.'); AddMethod('colorfromzero', @PyCOLORFROMZERO, ' colorfromzero(layer, isFromZero) -> Color scheme display range from zero (1) or from treshold value (0)?'); - AddMethod('colorname', @PyOVERLAYCOLORNAME, ' colorname(layer, colorName) -> Set the colorscheme for the target overlay (0=background layer) to a specified name.'); + AddMethod('colorname', @PyCOLORNAME, ' colorname(layer, colorName) -> Set the colorscheme for the target overlay (0=background layer) to a specified name.'); + AddMethod('colornode', @PyCOLORNODE, ' colornode(layer, index, intensity, r, g, b, a) -> Adjust color scheme for image.'); AddMethod('clipazimuthelevation', @PyCLIPAZIMUTHELEVATION, ' clipazimuthelevation(depth, azi, elev) -> Set a view-point independent clip plane.'); AddMethod('clipthick', @PyCLIPTHICK, ' clipthick(thick) -> Set size of clip plane slab (0..1).'); AddMethod('cutout', @PyCUTOUT, ' cutout(L,A,S,R,P,I) -> Remove sector from volume.'); @@ -2893,6 +3016,7 @@ procedure TGLForm1.PyModInitialization(Sender: TObject); AddMethod('overlayloadsmooth', @PyOVERLAYLOADSMOOTH, ' overlayloadsmooth(0) -> Will future overlayload() calls use smooth (1) or jagged (0) interpolation?'); AddMethod('minmax', @PyOVERLAYMINMAX, ' minmax(layer, min, max) -> Sets the color range for the overlay (layer 0 = background).'); AddMethod('opacity', @PyOVERLAYOPACITY, ' opacity(layer, opacityPct) -> Make the layer (0 for background, 1 for 1st overlay) transparent(0), translucent (~50) or opaque (100).'); + AddMethod('pitch', @PyPITCH, ' pitch(degrees) -> Sets the pitch of object to be rendered.'); AddMethod('quit', @PyQUIT, ' quit() -> Terminate the application.'); //pyREMOVESMALLCLUSTERS Set the colorscheme for the target overlay (0=background layer) to a specified name. AddMethod('removesmallclusters', @pyREMOVESMALLCLUSTERS, ' removesmallclusters(layer, thresh, mm, neighbors) -> only keep clusters where intensity exceeds thresh and size exceed mm. Clusters based on neighbors that share faces (1), faces+edges (2) or faces+edges+corners (3)'); @@ -4295,20 +4419,17 @@ procedure GetRemoveHazePrefs(var isSmoothEdges, isSingleObject: boolean; var Ots threshLabel: TLabel; threshEdit: TEdit; smoothCheck, singleCheck: TCheckBox; - - - //NeighborCombo: TComboBox; begin PrefForm:=TForm.Create(nil); //PrefForm.SetBounds(100, 100, 512, 212); PrefForm.AutoSize := True; PrefForm.BorderWidth := 8; - PrefForm.Caption:='Overlay Settings'; + PrefForm.Caption:='Remove Haze Settings'; PrefForm.Position := poScreenCenter; PrefForm.BorderStyle := bsDialog; //label threshLabel:=TLabel.create(PrefForm); - threshLabel.Caption:= 'Threshold 1..5 (small..big)'; + threshLabel.Caption:= 'Threshold 1..5 (only brightest survive..dim voxels survive)'; threshLabel.AutoSize := true; threshLabel.AnchorSide[akTop].Side := asrTop; threshLabel.AnchorSide[akTop].Control := PrefForm; @@ -4593,8 +4714,9 @@ procedure TGLForm1.DrawSaveMenuClick(Sender: TObject); end; fnm := dlg.FileName; dlg.Free;*) - fnm := NiftiSaveDialogFilename(true); + fnm := NiftiSaveDialogFilename(true, vols.Drawing.Filename); if fnm = '' then exit; + vols.Drawing.Filename := fnm; niftiVol.SaveAsSourceOrient(fnm, vols.Drawing.VolRawBytes); vols.Drawing.NeedsSave := false; end; @@ -5199,24 +5321,41 @@ procedure TGLForm1.NewWindowMenuClick(Sender: TObject); {$ENDIF} -function TGLForm1.NiftiSaveDialogFilename(isVOI: boolean = false): string; +function TGLForm1.NiftiSaveDialogFilename(isVOI: boolean = false; initialFilename: string = ''): string; const - kOutFilter = 'NIfTI|*.nii|Compressed NIfTI|*.nii.gz|Volume of interest|*.voi|Blender Volume|*.bvox|OSPRay Volume|*.osp|TIF|*.tif|NRRD|*.nrrd|Compressed NRRD|*.nhdr'; + kOutFilter = 'NIfTI (.nii)|*.nii|Compressed NIfTI (.nii.gz)|*.nii.gz|Volume of interest (.voi)|*.voi|Blender Volume (.bvox)|*.bvox|OSPRay Volume (.osp)|*.osp|TIF (.tif)|*.tif|NRRD (.nrrd)|*.nrrd|Compressed NRRD (.nhdr)|*.nhdr'; kMaxExt = 8; kExt : array [1..kMaxExt] of string = ('*.nii', '*.nii.gz', '*.voi', '*.bvox', '*.osp', '*.tif', '*.nrrd', '*.nhdr'); var dlg : TSaveDialog; + ext: string = ''; + i, len : integer; + idx: integer = 0; begin result := ''; dlg := TSaveDialog.Create(self); + dlg.Options := [ofEnableSizing, ofViewDetail]; //disable ofOverwritePrompt dlg.Title := 'Save NIfTI volume'; dlg.InitialDir := extractfiledir(gPrefs.PrevBackgroundImage); + if ( initialFilename <> '') then begin + dlg.InitialDir := extractfilepath(initialFilename); + //dlg.FileName := ChangeFileExt(extractfilename(initialFilename), ''); + ext := extractfileextX(initialFilename); + dlg.FileName := extractfilename(initialFilename); + dlg.FileName := copy(dlg.FileName, 1, length(dlg.FileName)-length(ext)); + for i := 1 to kMaxExt do + if (ext = extractfileextX(kExt[i])) then + idx := i; + end; {$IFDEF Darwin} if PosEx('.app', dlg.InitialDir) > 0 then dlg.InitialDir := HomeDir(false); {$ENDIF} dlg.Filter := kOutFilter; - if isVoi then begin + if idx > 0 then begin + dlg.DefaultExt := kExt[idx]; + dlg.FilterIndex:= idx; + end else if isVoi then begin if (gPrefs.VoiSaveFormat < 1) or (gPrefs.VoiSaveFormat > kMaxExt) then gPrefs.VoiSaveFormat := 1; dlg.DefaultExt := kExt[gPrefs.VoiSaveFormat]; @@ -5229,13 +5368,33 @@ function TGLForm1.NiftiSaveDialogFilename(isVOI: boolean = false): string; dlg.FilterIndex:= gPrefs.VolumeSaveFormat; end; //niftiVol.SaveBVox('/Users/rorden/tmp/v.bvox'); exit; - if dlg.Execute then begin - result := dlg.FileName; - if isVoi then - gPrefs.VoiSaveFormat := dlg.FilterIndex - else - gPrefs.VolumeSaveFormat := dlg.FilterIndex; + if not dlg.Execute then begin + dlg.Free; + exit; + end; + result := dlg.FileName; + // + ext := extractfileextX(result); + len := length(ext); + idx := 0; + for i := 1 to kMaxExt do + if (ext = extractfileextX(kExt[i])) then + idx := i; + //i := idx; //0 if idx not found + if idx = 0 then + idx := dlg.FilterIndex; + result := copy(result, 1, length(result)-len)+LowerCase(extractfileextX(kExt[idx])); + if fileexists(result) then begin + if MessageDlg('“'+extractfilename(result)+'” already exists. Do you want to replace it?', mtConfirmation,[mbYes, mbNo], 0) = mrNo then begin + result := ''; + dlg.Free; + exit; + end; end; + if isVoi then + gPrefs.VoiSaveFormat := dlg.FilterIndex + else + gPrefs.VolumeSaveFormat := dlg.FilterIndex; dlg.Free; end; @@ -5256,7 +5415,7 @@ procedure TGLForm1.SaveNIfTIMenuClick(Sender: TObject); fnm : string; begin if not vols.Layer(0,niftiVol) then exit; - fnm := NiftiSaveDialogFilename(); + fnm := NiftiSaveDialogFilename(false, niftiVol.Filename); if fnm = '' then exit; niftiVol.SaveFormatBasedOnExt(fnm); end; @@ -5619,7 +5778,12 @@ procedure TGLForm1.ScriptingSaveMenuClick(Sender: TObject); procedure TGLForm1.ScriptingRunMenuClick(Sender: TObject); begin - {$IFDEF MYPY} + if (vols.Drawing.NeedsSave) then begin + showmessage('Please close or save your drawing before running a script.'); + exit; + end; + //DrawSaveMenuClick + {$IFDEF MYPY} if gPyRunning then begin //PyEngine.PyErr_SetString(PyExc_KeyboardInterrupt^, 'Terminated'); //PyEngine.PyExc_KeyboardInterrupt(); @@ -6581,7 +6745,7 @@ procedure TGLForm1.MatCapDropChange(Sender: TObject); {$ENDIF} end; -{$DEFINE RESIZE_FROM_DISK} +//{$DEFINE RESIZE_FROM_DISK} {$IFDEF RESIZE_FROM_DISK} (*procedure TGLForm1.ResizeMenuClick(Sender: TObject); const @@ -6710,16 +6874,36 @@ procedure TGLForm1.ResizeMenuClick(Sender: TObject); nii.Free; end; {$ELSE} //not RESIZE_FROM_DISK +function HeaderRotate(hdr: TNIFTIhdr; perm: TVec3i): TNIFTIhdr; +begin + result := hdr; + result.dim[1] := hdr.dim[abs(perm.x)]; + result.dim[2] := hdr.dim[abs(perm.y)]; + result.dim[3] := hdr.dim[abs(perm.z)]; + result.pixdim[1] := hdr.pixdim[abs(perm.x)]; + result.pixdim[2] := hdr.pixdim[abs(perm.y)]; + result.pixdim[3] := hdr.pixdim[abs(perm.z)]; +end; + procedure TGLForm1.ResizeMenuClick(Sender: TObject); var - dlg : TSaveDialog; - nii: TNIfTI; + //dlg : TSaveDialog; + isOK: boolean; + nii, niiBig: TNIfTI; hdr: TNIFTIhdr; filter, datatype: integer; scale: TVec3; + fnm: string; isAllVolumes: boolean; + backColor: TRGBA; begin - if not vols.Layer(0, nii) then exit; + + if not vols.Layer(0, nii) then exit; + + //TODO:2020 if big is reoriented + //TODO:2020 clip when big + //TODO:2020 reorient when big + (*if (nii.Header.datatype= kDT_RGB) then begin showmessage('This function does not yet support RGB images'); exit; @@ -6734,30 +6918,56 @@ procedure TGLForm1.ResizeMenuClick(Sender: TObject); nii.SaveRescaled('ax.nii', 0.5, 1, 0.5, kDT_SIGNED_SHORT, -1, true); exit; {$ENDIF} - hdr := nii.Header; + //showmessage(format('%d %d', [nii.HeaderNoRotation.Dim[1], nii.Header.Dim[1] ])); + //exit; + + //hdr := nii.Header; + //hdr := nii.HeaderNoRotation; + if (not nii.IsShrunken) then + hdr := HeaderRotate(nii.HeaderNoRotation, nii.InputReorientPermute) + else + hdr := nii.HeaderNoRotation; + //showmessage(format('%d %d %d', [nii.InputReorientPermute.x, nii.InputReorientPermute.y, nii.InputReorientPermute.z ])); + //exit; scale := ResizeForm.GetScale(hdr, nii.isLabels, nii.ShortName, datatype, filter, isAllVolumes); if scale.x <= 0 then exit; if (scale.x = 1) and (scale.y = 1) and (scale.z = 1) and (datatype = hdr.datatype) then begin showmessage('Nothing to do: no change to volume.' ); exit; end; - //ResizeForm.ShowModal; - //if not GetScale(nii.Dim, mm, scale) then exit; - dlg := TSaveDialog.Create(self); + (*dlg := TSaveDialog.Create(self); dlg.Title := 'Save NIfTI volume'; dlg.InitialDir := extractfiledir(gPrefs.PrevBackgroundImage); {$IFDEF Darwin} if PosEx('.app', dlg.InitialDir) > 0 then dlg.InitialDir := HomeDir(false); {$ENDIF} - //dlg.FileName:= 'r'+extractfilename(gPrefs.PrevBackgroundImage); dlg.Filename := 'r'+ChangeFileExtX(extractfilename(gPrefs.PrevBackgroundImage),''); dlg.Filter := 'NIfTI|*.nii|Compressed NIfTI|*.nii.gz'; dlg.DefaultExt := '*.nii'; dlg.FilterIndex := 0; - if not dlg.Execute then exit; - nii.SaveRescaled(dlg.filename, scale.x, scale.y, scale.z, datatype, filter, isAllVolumes); - dlg.Free; + if not dlg.Execute then begin + dlg.Free; + exit; + end; + fnm := dlg.filename; + dlg.Free; *) + fnm := NiftiSaveDialogFilename(); + if fnm = '' then exit; + if (not nii.IsShrunken) then begin + nii.SaveRescaled(fnm, scale.x, scale.y, scale.z, datatype, filter, isAllVolumes); + exit; + end; + //we shrank the image - load full size + backColor := setRGBA(0,0,0,0); + niiBig := TNIfTI.Create(nii.Filename, backColor, false, -1, isOK); + //nii.Create(fnm, rgba, false, 4096, ok); + if not isOK then + exit; + niiBig.SaveRescaled(fnm, scale.x, scale.y, scale.z, datatype, filter, isAllVolumes); + //saveDlg.Free; + niiBig.Free; + end; {$ENDIF} //if RESIZE_FROM_DISK else end @@ -6838,8 +7048,10 @@ procedure TGLForm1.ReorientMenuClick(Sender: TObject); var //dlg : TSaveDialog; fnm : string; - niftiVol: TNIfTI; + nii, niiBig: TNIfTI; + backColor: TRGBA; perm: TVec3i; + isOK: boolean; btn : array [1..6] of string = ('red','green','blue','purple','orange','yellow'); btnR,btnA,btnS: integer; begin @@ -6891,17 +7103,29 @@ procedure TGLForm1.ReorientMenuClick(Sender: TObject); Vol1.Slices.isOrientationTriangles := false; if (btnR=2) and (btnA=4) and (btnS=6) then begin showmessage('Image already oriented'); - exit; + goto 245; end; perm := pti( (btnR + 1) div 2, (btnA + 1) div 2, (btnS + 1) div 2); if odd(btnR) then perm.x := -perm.x; if odd(btnA) then perm.y := -perm.y; if odd(btnS) then perm.z := -perm.z; //showmessage(format('%d %d %d', [perm.x, perm.y, perm.z])); - if not vols.Layer(0, niftiVol) then exit; + if not vols.Layer(0, nii) then goto 245; fnm := NiftiSaveDialogFilename(); - if fnm = '' then exit; - niftiVol.SaveRotated(fnm, perm); + if fnm = '' then goto 245; + if (not nii.IsShrunken) then begin + nii.SaveRotated(fnm, perm); + goto 245; + end; + //we shrank the image - load full size + backColor := setRGBA(0,0,0,0); + niiBig := TNIfTI.Create(nii.Filename, backColor, false, -1, isOK); + //nii.Create(fnm, rgba, false, 4096, ok); + if not isOK then + exit; + niiBig.SaveRotated(fnm, perm); + //saveDlg.Free; + niiBig.Free; 245: Vol1.Slices.isOrientationTriangles := false; end; @@ -6940,6 +7164,19 @@ procedure UpdateMatCapDrop (var LUTdrop: TComboBox); end; {$ENDIF} +var + gHideDrawing: boolean = false; + +procedure TGLForm1.ViewGPUKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +begin + if (gHideDrawing) then begin //we can not read shift state, as released + Vols.Drawing.OpacityFraction := abs(Vols.Drawing.OpacityFraction); + ViewGPU1.Invalidate; + gHideDrawing := false; + end; +end; + procedure TGLForm1.ViewGPUKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var sliceMove: TVec3i; @@ -6947,6 +7184,12 @@ procedure TGLForm1.ViewGPUKeyDown(Sender: TObject; var Key: Word; Shift: TShiftS niftiVol: TNIfTI; begin if not vols.Layer(0,niftiVol) then exit; + if (vols.Drawing.IsOpen) and (ssAlt in Shift) then begin + Vols.Drawing.OpacityFraction := -abs(Vols.Drawing.OpacityFraction); + gHideDrawing := true; + ViewGPU1.Invalidate; + end; + (*if not vols.Layer(0,niftiVol) then begin if DisplayNextMenu.Enabled then begin if (Key = VK_LEFT) or (Key = VK_Down) then DisplayPrevMenu.Click; @@ -7063,6 +7306,7 @@ procedure TGLForm1.ResetDefaultsClick(Sender: TObject); SetColorbarPosition; Vol1.Azimuth := 110; Vol1.Elevation := 30; + Vol1.Pitch := 0; ClipDepthTrack.Position := 0; ClipAziTrack.Position := 180; ClipElevTrack.Position := 0; @@ -7846,8 +8090,10 @@ procedure TGLForm1.MouseGesturesMenu(Sender: TObject); const {$IFDEF Darwin} kAlt = 'Command'; + kAlt2 = 'Option'; {$ELSE} kAlt = 'Alt'; + kAlt2 = 'Alt'; {$ENDIF} var s: string; @@ -7873,6 +8119,7 @@ procedure TGLForm1.MouseGesturesMenu(Sender: TObject); +kEOLN+'Drawing (Draw/DrawColor selected)' +kEOLN+' Drag: draw filled region' +kEOLN+' Shift-Drag: erase filled region' + +kEOLN+' '+kAlt2+'-Down: Briefly hide drawing' +kEOLN+' '+kAlt+'-Click: Move crosshair'; //{$IFDEF NewCocoa} //ShowAlertSheet(GLForm1.Handle,'Mouse Gestures', s); @@ -8468,6 +8715,7 @@ procedure TGLForm1.SaveColorTable; gIsMouseDown: boolean = false; // https://bugs.freepascal.org/view.php?id=35480 {$ENDIF} + procedure TGLForm1.ViewGPUMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var @@ -8495,6 +8743,7 @@ procedure TGLForm1.ViewGPUMouseDown(Sender: TObject; Button: TMouseButton; exit; end; if not vols.Layer(0,niftiVol) then exit; + gMouse.Y := Y; gMouse.X := X; if (Vol1.CE.ColorEditorMouseDown(X,Y, (ssShift in Shift), (ssCtrl in Shift), niftiVol)) then begin @@ -8648,6 +8897,16 @@ procedure TGLForm1.ViewGPUMouseMove(Sender: TObject; Shift: TShiftState; X, exit; end; if gPrefs.DisplayOrient <> kRenderOrient then exit; //e.g. mosaics + if (ssCtrl in Shift) then begin //adjust pitch + // + Vol1.Pitch := Vol1.Pitch - (Y - gMouse.Y); + Vol1.Pitch := min(Vol1.Pitch, 180); + Vol1.Pitch := max(Vol1.Pitch, -180); + gMouse.X := X; + gMouse.Y := Y; + ViewGPU1.Invalidate; + exit; + end; Vol1.Azimuth := Vol1.Azimuth + (X - gMouse.X); Vol1.Elevation := Vol1.Elevation + (Y - gMouse.Y); while Vol1.Azimuth > 360 do Vol1.Azimuth := Vol1.Azimuth - 360; @@ -9141,6 +9400,7 @@ procedure TGLForm1.FormShow(Sender: TObject); GraphShow(); ViewGPU1.OnKeyPress:=@ViewGPUKeyPress; ViewGPU1.OnKeyDown := @ViewGPUKeyDown; + ViewGPU1.OnKeyUp := @ViewGPUKeyUp; ViewGPU1.OnDblClick := @ViewGPUDblClick; ViewGPU1.OnMouseDown := @ViewGPUMouseDown; ViewGPU1.OnMouseMove := @ViewGPUMouseMove; @@ -9212,6 +9472,8 @@ procedure TGLForm1.FormShow(Sender: TObject); ScriptingRunMenu.ShortCut := ShortCut(Word('R'), [ssModifier]); //ssCtrl -> ssMeta OpenMenu.ShortCut := ShortCut(Word('O'), [ssModifier]); //ssCtrl -> ssMeta AddOverlayMenu.ShortCut := ShortCut(Word('A'), [ssModifier]); //ssCtrl -> ssMeta + //SaveNIfTIMenu.ShortCut := ShortCut(Word('S'), [ssModifier]); + DrawSaveMenu.ShortCut := ShortCut(Word('S'), [ssModifier]); //ssCtrl -> ssMeta used to be meta-H but used by Apple to Hide program DrawHideMenu.ShortCut := ShortCut(Word('T'), [ssModifier]); //ssCtrl -> ssMeta used to be meta-H but used by Apple to Hide program ScriptingNewMenu.ShortCut := ShortCut(Word('N'), [ssModifier]); //ssCtrl -> ssMeta DrawUndoMenu.ShortCut := ShortCut(Word('U'), [ssModifier]); @@ -9334,7 +9596,7 @@ function TGLForm1.HasLabelLayer: boolean; v := nil; i := 0; while (i < GLForm1.LayerList.Count) do begin - if not vols.Layer(i,v) then exit; + if not vols.Layer(i,v) then exit(false); if v.IsLabels then break; i := i + 1; diff --git a/mtlvolume2.pas b/mtlvolume2.pas index 8e03d8c..29aa611 100644 --- a/mtlvolume2.pas +++ b/mtlvolume2.pas @@ -39,7 +39,7 @@ TGPUVolume = class txt: TGPUFont; drawVolTex, drawVolLut: MTLTextureProtocol; {$ENDIF} - RayCastQuality1to5, maxDim, fAzimuth,fElevation: integer; + RayCastQuality1to5, maxDim, fAzimuth, fElevation, fPitch: integer; shaderPrefs: TShaderPrefs; prefValues: array [1..kMaxUniform] of single; fDistance, overlayNum: single; @@ -79,10 +79,10 @@ TGPUVolume = class {$ENDIF} procedure UpdateOverlays(vols: TNIfTIs); property Quality1to5: integer read RayCastQuality1to5 write RayCastQuality1to5; - property ShaderSliders: TShaderPrefs read shaderPrefs write shaderPrefs; property Azimuth: integer read fAzimuth write fAzimuth; property Elevation: integer read fElevation write fElevation; + property Pitch: integer read fPitch write fPitch; property Distance: single read fDistance write fDistance; property LightPosition: TVec4 read fLightPos write fLightPos; property ClipPlane: TVec4 read fClipPlane write fClipPlane; @@ -455,6 +455,7 @@ constructor TGPUVolume.Create(fromView: TMetalControl); lineVertexBuffer := nil; fAzimuth := 110; fElevation := 30; + fPitch := 0; RaycastQuality1to5 := 5; SelectionRect := Vec4(-1,0,0,0); fLightPos := Vec4(0, 0.707, 0.707, 0); @@ -1220,6 +1221,8 @@ procedure TGPUVolume.Paint(var vol: TNIfTI); modelMatrix *= TMat4.Translate(0, 0, -fDistance); modelMatrix *= TMat4.RotateX(-DegToRad(90-fElevation)); modelMatrix *= TMat4.RotateZ(DegToRad(fAzimuth)); + modelMatrix *= TMat4.RotateX(-DegToRad(fPitch)); + modelMatrix *= TMat4.Translate(-vol.Scale.X/2, -vol.Scale.Y/2, -vol.Scale.Z/2); modelLightPos := (modelMatrix.Transpose * fLightPos); modelMatrix *= TMat4.Scale(vol.Scale.X, vol.Scale.Y, vol.Scale.Z); //for volumes that are rectangular not square @@ -1301,8 +1304,9 @@ procedure TGPUVolume.Paint(var vol: TNIfTI); {$ENDIF} {$IFDEF CUBE} if Slices.LabelOrient then begin - gCube.Azimuth:=fAzimuth; - gCube.Elevation:=-fElevation; + gCube.Azimuth := fAzimuth; + gCube.Elevation := -fElevation; + gCube.Pitch := fPitch; gCube.Draw(mtlControl.clientwidth, mtlControl.clientheight); end; {$ENDIF} diff --git a/nifti.pas b/nifti.pas index d9a18f3..11e4455 100755 --- a/nifti.pas +++ b/nifti.pas @@ -169,6 +169,7 @@ TCluster = record {$ENDIF} clusters: TClusters; clusterNotes: string[128]; //interpolated, label map, etc + property InputReorientPermute: TVec3i read fPermInOrient; property IsNativeEndian: boolean read fIsNativeEndian; property VolumeDisplayed: integer read fVolumeDisplayed; //indexed from 0 (0..VolumesLoaded-1) property VolumesLoaded: integer read fVolumesLoaded; //1 for 3D data, for 4D 1..hdr.dim[4] depending on RAM @@ -282,7 +283,7 @@ TCluster = record procedure SetLengthP(var S: TRGBAp; Len: SizeInt); {$ENDIF} {$ENDIF} - + function extractfileextX(fnm: string): string; implementation @@ -2512,47 +2513,38 @@ procedure nifti_quatern_to_mat44( out lR :TMat4; lR[2,3]:= qz ; end; -procedure CheckXForm(var lHdr: TNIfTIHdr); +procedure fixBogusHeaders(var h: TNIFTIhdr); +var + isBogus : boolean = false; + i: integer; +procedure checkSingle(var f: single); begin - if (lHdr.qform_code > kNIFTI_XFORM_OTHER_TEMPLATE) or (lHdr.qform_code < kNIFTI_XFORM_UNKNOWN) then begin - {$IFDEF UNIX} - printf('Unkown qform_code (update)'); - {$ENDIF} - lHdr.qform_code := kNIFTI_XFORM_UNKNOWN; - end; - if (lHdr.sform_code > kNIFTI_XFORM_OTHER_TEMPLATE) or (lHdr.sform_code < kNIFTI_XFORM_UNKNOWN) then begin - {$IFDEF UNIX} - printf('Unkown sform_code (update)'); - {$ENDIF} - lHdr.sform_code := kNIFTI_XFORM_UNKNOWN; + if (specialSingle(f)) or (f = 0.0) then begin + isBogus := true; + f := 1.0; end; end; - -function Quat2Mat( var lHdr: TNIfTIHdr ): boolean; -var lR :TMat4; begin - result := false; - CheckXForm(lHdr); - if (lHdr.sform_code <> kNIFTI_XFORM_UNKNOWN) or (lHdr.qform_code <= kNIFTI_XFORM_UNKNOWN) or (lHdr.qform_code > kNIFTI_XFORM_MNI_152) then - exit; - result := true; - nifti_quatern_to_mat44(lR,lHdr.quatern_b,lHdr.quatern_c,lHdr.quatern_d, - lHdr.qoffset_x,lHdr.qoffset_y,lHdr.qoffset_z, - lHdr.pixdim[1],lHdr.pixdim[2],lHdr.pixdim[3], - lHdr.pixdim[0]); - lHdr.srow_x[0] := lR[0,0]; - lHdr.srow_x[1] := lR[0,1]; - lHdr.srow_x[2] := lR[0,2]; - lHdr.srow_x[3] := lR[0,3]; - lHdr.srow_y[0] := lR[1,0]; - lHdr.srow_y[1] := lR[1,1]; - lHdr.srow_y[2] := lR[1,2]; - lHdr.srow_y[3] := lR[1,3]; - lHdr.srow_z[0] := lR[2,0]; - lHdr.srow_z[1] := lR[2,1]; - lHdr.srow_z[2] := lR[2,2]; - lHdr.srow_z[3] := lR[2,3]; - lHdr.sform_code := kNIFTI_XFORM_SCANNER_ANAT; + checkSingle(h.PixDim[1]); + checkSingle(h.PixDim[2]); + checkSingle(h.PixDim[3]); + if (isBogus) then + printf('Bogus PixDim repaired. Check dimensions and orientation.'); + isBogus := false; + for i := 0 to 3 do begin + if specialSingle(h.srow_x[i]) then isBogus := true; + if specialSingle(h.srow_y[i]) then isBogus := true; + if specialSingle(h.srow_z[i]) then isBogus := true; + end; + if (h.srow_x[0] = 0) and (h.srow_x[1] = 0) and (h.srow_x[2] = 0) then isBogus := true; + if (h.srow_y[0] = 0) and (h.srow_y[1] = 0) and (h.srow_y[2] = 0) then isBogus := true; + if (h.srow_z[0] = 0) and (h.srow_z[1] = 0) and (h.srow_z[2] = 0) then isBogus := true; + if (h.srow_x[0] = 0) and (h.srow_y[0] = 0) and (h.srow_z[0] = 0) then isBogus := true; + if (h.srow_x[1] = 0) and (h.srow_y[1] = 0) and (h.srow_z[1] = 0) then isBogus := true; + if (h.srow_x[2] = 0) and (h.srow_y[2] = 0) and (h.srow_z[2] = 0) then isBogus := true; + if (not isBogus) then exit; + NII_SetIdentityMatrix(h); + printf('Bogus header repaired. Check orientation.'); end; procedure fixHdr( var lHdr: TNIfTIHdr ); @@ -2613,6 +2605,7 @@ procedure NoMat( var lHdr: TNIfTIHdr ); if lHdr.pixdim[1] = 0 then lHdr.pixdim[1] := 1; if lHdr.pixdim[2] = 0 then lHdr.pixdim[2] := 1; if lHdr.pixdim[3] = 0 then lHdr.pixdim[3] := 1; + printf('>>>>>>><<<<<<<<<'); lHdr.srow_x[0] := lHdr.pixdim[1]; lHdr.srow_x[1] := 0; lHdr.srow_x[2] := 0; @@ -2627,6 +2620,102 @@ procedure NoMat( var lHdr: TNIfTIHdr ); lHdr.srow_z[3] := ((lHdr.dim[3]-1)*lHdr.pixdim[3]) * -0.5; end; +procedure fixAnalyze(var h: TNIfTIHdr; isNativeEndian: boolean); +var + //isBogus: boolean = false; + h2: TNIfTIHdr; + a: TAnalyzeHdrSection; + //i : integer; +begin + if (h.magic = kNIFTI_MAGIC_SEPARATE_HDR) or (h.magic = kNIFTI_MAGIC_EMBEDDED_HDR) then exit; + if (h.magic = kNIFTI2_MAGIC_SEPARATE_HDR) or (h.magic = kNIFTI2_MAGIC_EMBEDDED_HDR) then exit; + if (h.HdrSz <> 348) then exit;; + printf('Warning: this does not appear to be a valid NIfTI image. Perhaps legacy Analyze.'); + (*if (h.pixdim[1] = 0) or (h.pixdim[2] = 0) or (h.pixdim[3] = 0) then exit; + for i := 0 to 3 do begin + if specialSingle(h.srow_x[i]) then isBogus := true; + if specialSingle(h.srow_y[i]) then isBogus := true; + if specialSingle(h.srow_z[i]) then isBogus := true; + end; + if (not isBogus) then begin + isBogus := true; + for i := 0 to 2 do begin + if h.srow_x[i] <> 0 then isBogus := false; + if h.srow_y[i] <> 0 then isBogus := false; + if h.srow_z[i] <> 0 then isBogus := false; + end; + end; + if not isBogus then exit;*) + h2 := h; + if not isNativeEndian then NIFTIhdr_SwapBytes(h2); + move(h2,a,sizeof(a)); + if not isNativeEndian then begin + swap(a.originator[1]); + swap(a.originator[2]); + swap(a.originator[3]); + + end; + if (a.originator[1] < 0) or (a.originator[2] < 0) or (a.originator[3] < 0) then exit; + if (a.originator[1] > h.dim[1]) or (a.originator[2] > h.dim[2]) or (a.originator[3] > h.dim[3]) then exit; + //printf(format(' %d %d %d', [a.originator[1], a.originator[2], a.originator[3]])); + h.srow_x[0] := h.pixdim[1]; + h.srow_x[1] := 0; + h.srow_x[2] := 0; + h.srow_x[3] := -(a.originator[1]-1) *h.pixdim[1]; + h.srow_y[0] := 0; + h.srow_y[1] := h.pixdim[2]; + h.srow_y[2] := 0; + h.srow_y[3] := -(a.originator[2]-1) *h.pixdim[2]; + h.srow_z[0] := 0; + h.srow_z[1] := 0; + h.srow_z[2] := h.pixdim[3]; + h.srow_z[3] := -(a.originator[3]-1) *h.pixdim[3]; + printf('Warning: unable to determin left from right side SPM-style for Analyze images'); +end; + +procedure CheckXForm(var lHdr: TNIfTIHdr); +begin + if (lHdr.qform_code > kNIFTI_XFORM_OTHER_TEMPLATE) or (lHdr.qform_code < kNIFTI_XFORM_UNKNOWN) then begin + {$IFDEF UNIX} + printf('Unkown qform_code (update)'); + {$ENDIF} + lHdr.qform_code := kNIFTI_XFORM_UNKNOWN; + end; + if (lHdr.sform_code > kNIFTI_XFORM_OTHER_TEMPLATE) or (lHdr.sform_code < kNIFTI_XFORM_UNKNOWN) then begin + {$IFDEF UNIX} + printf('Unkown sform_code (update)'); + {$ENDIF} + lHdr.sform_code := kNIFTI_XFORM_UNKNOWN; + end; +end; + +function Quat2Mat( var lHdr: TNIfTIHdr ): boolean; +var lR :TMat4; +begin + result := false; + CheckXForm(lHdr); + if (lHdr.sform_code <> kNIFTI_XFORM_UNKNOWN) or (lHdr.qform_code <= kNIFTI_XFORM_UNKNOWN) or (lHdr.qform_code > kNIFTI_XFORM_MNI_152) then + exit; + result := true; + nifti_quatern_to_mat44(lR,lHdr.quatern_b,lHdr.quatern_c,lHdr.quatern_d, + lHdr.qoffset_x,lHdr.qoffset_y,lHdr.qoffset_z, + lHdr.pixdim[1],lHdr.pixdim[2],lHdr.pixdim[3], + lHdr.pixdim[0]); + lHdr.srow_x[0] := lR[0,0]; + lHdr.srow_x[1] := lR[0,1]; + lHdr.srow_x[2] := lR[0,2]; + lHdr.srow_x[3] := lR[0,3]; + lHdr.srow_y[0] := lR[1,0]; + lHdr.srow_y[1] := lR[1,1]; + lHdr.srow_y[2] := lR[1,2]; + lHdr.srow_y[3] := lR[1,3]; + lHdr.srow_z[0] := lR[2,0]; + lHdr.srow_z[1] := lR[2,1]; + lHdr.srow_z[2] := lR[2,2]; + lHdr.srow_z[3] := lR[2,3]; + lHdr.sform_code := kNIFTI_XFORM_SCANNER_ANAT; +end; + function Nifti2to1(h2 : TNIFTI2hdr): TNIFTIhdr; overload; type tmagic = packed record @@ -2739,6 +2828,8 @@ procedure NIFTI2hdr_SwapBytes (var lAHdr: TNIFTI2hdr); //Swap Byte order for the end; + + function Nifti2to1(Stream:TFileStream; var isNativeEndian: boolean): TNIFTIhdr; overload; var h2 : TNIFTI2hdr; @@ -3054,7 +3145,7 @@ function TNIfTI.LoadFastGZ(FileName : AnsiString; out isNativeEndian: boolean): fVolumesLoaded := max(HdrVolumes(fHdr),1); fVolumesTotal := fVolumesLoaded; DetectV1(); - if (fVolumesLoaded = 3) and (fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) and (fHdr.datatype = kDT_FLOAT32) then + if (fVolumesLoaded = 3) and ((fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) or (fHdr.intent_code = kNIFTI_INTENT_VECTOR)) and (fHdr.datatype = kDT_FLOAT32) then volBytes := volBytes * fVolumesLoaded else if (HdrVolumes(fHdr) > 1) and (not fIsOverlay) then begin if LoadFewVolumes then @@ -3131,7 +3222,7 @@ function TNIfTI.LoadGZ(FileName : AnsiString; out isNativeEndian: boolean): bool fVolumesLoaded := max(HdrVolumes(fHdr),1); fVolumesTotal := fVolumesLoaded; DetectV1(); - if (fVolumesLoaded = 3) and (fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) and (fHdr.datatype = kDT_FLOAT32) then + if (fVolumesLoaded = 3) and ((fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) or (fHdr.intent_code = kNIFTI_INTENT_VECTOR)) and (fHdr.datatype = kDT_FLOAT32) then volBytes := volBytes * fVolumesLoaded else if (HdrVolumes(fHdr) > 1) and (not fIsOverlay) then begin if LoadFewVolumes then @@ -3294,6 +3385,7 @@ function TNIfTI.LoadRaw(FileName : AnsiString; out isNativeEndian: boolean): boo exit; end; {$ENDIF} + fixAnalyze(fHdr, isNativeEndian); if fHdr.HdrSz <> SizeOf (TNIFTIHdr) then begin fHdr := Nifti2to1(Stream, isNativeEndian); if fHdr.HdrSz <> SizeOf (TNIFTIHdr) then begin @@ -3332,7 +3424,7 @@ function TNIfTI.LoadRaw(FileName : AnsiString; out isNativeEndian: boolean): boo end; fVolumesLoaded := max(HdrVolumes(fHdr),1); fVolumesTotal := fVolumesLoaded; - if (fVolumesLoaded = 3) and (fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) and (fHdr.datatype = kDT_FLOAT32) then + if (fVolumesLoaded = 3) and ((fHdr.intent_code = kNIFTI_INTENT_RGB_VECTOR) or (fHdr.intent_code = kNIFTI_INTENT_VECTOR)) and (fHdr.datatype = kDT_FLOAT32) then volBytes := volBytes * fVolumesLoaded else if (HdrVolumes(fHdr) > 1) and (not fIsOverlay) then begin if LoadFewVolumes then @@ -4336,10 +4428,10 @@ procedure TNIfTI.LoadRGBVector(); i, vx, byts, k: int64; in8: TUInt8s; in32: TFloat32s; - r,g,b: single; + r,g,b, mx, slope: single; begin //printf(format('>>>%d', [fVolumesLoaded])); - if (fHdr.intent_code <> kNIFTI_INTENT_RGB_VECTOR) or (fHdr.datatype <> kDT_FLOAT32) or (fVolumesLoaded <> 3) then exit; + if ((fHdr.intent_code <> kNIFTI_INTENT_RGB_VECTOR) and (fHdr.intent_code <> kNIFTI_INTENT_VECTOR)) or (fHdr.datatype <> kDT_FLOAT32) or (fVolumesLoaded <> 3) then exit; //printf(format('>>>??%d', [fVolumesLoaded])); vx := (fHdr.dim[1]*fHdr.dim[2]*fHdr.dim[3]); byts := vx * 3 * 4; @@ -4359,14 +4451,24 @@ procedure TNIfTI.LoadRGBVector(); fHdr.cal_min := 0; fHdr.bitpix := 24; fHdr.dim[0] := 3; + mx := abs(in32[0]); + for i := 0 to ((vx * 3) - 1) do + mx := max(mx, abs(in32[i])); + slope := 1.0; + if (mx > 0.0) then + slope := 1.0 / mx; for i := 4 to 7 do fHdr.dim[i] := 1; k := 0; for i := 0 to (vx - 1) do begin //alpha := (vol24[skipVx+i].r+vol24[skipVx+i].g+vol24[skipVx+i].g+vol24[skipVx+i].b) div 4; //favor green - r := min(abs(in32[i]),1); + (*r := min(abs(in32[i]),1); g := min(abs(in32[i+vx]),1); - b := min(abs(in32[i+vx+vx]),1); + b := min(abs(in32[i+vx+vx]),1); *) + r := abs(in32[i]) * slope; + g := abs(in32[i+vx]) * slope; + b := abs(in32[i+vx+vx]) * slope; + //a := sqrt(r*r + g*g + b * b); //{$IFDEF DYNRGBA} //outRGBA[i] := SetRGBA(round(r*255), round(g*255), round(b*255), round(a*255)); @@ -6436,41 +6538,6 @@ procedure LoadLabelsTxt(lFileName: string; var lLabels: TStringList); LoadLabels(lLUTname, lLabels,0,-1); end; -procedure fixBogusHeaders(var h: TNIFTIhdr); -var - isBogus : boolean = false; - i: integer; -procedure checkSingle(var f: single); -begin - if (specialSingle(f)) or (f = 0.0) then begin - isBogus := true; - f := 1.0; - end; -end; - -begin - checkSingle(h.PixDim[1]); - checkSingle(h.PixDim[2]); - checkSingle(h.PixDim[3]); - if (isBogus) then - printf('Bogus PixDim repaired. Check dimensions and orientation.'); - isBogus := false; - for i := 0 to 3 do begin - if specialSingle(h.srow_x[i]) then isBogus := true; - if specialSingle(h.srow_y[i]) then isBogus := true; - if specialSingle(h.srow_z[i]) then isBogus := true; - end; - if (h.srow_x[0] = 0) and (h.srow_x[1] = 0) and (h.srow_x[2] = 0) then isBogus := true; - if (h.srow_y[0] = 0) and (h.srow_y[1] = 0) and (h.srow_y[2] = 0) then isBogus := true; - if (h.srow_z[0] = 0) and (h.srow_z[1] = 0) and (h.srow_z[2] = 0) then isBogus := true; - if (h.srow_x[0] = 0) and (h.srow_y[0] = 0) and (h.srow_z[0] = 0) then isBogus := true; - if (h.srow_x[1] = 0) and (h.srow_y[1] = 0) and (h.srow_z[1] = 0) then isBogus := true; - if (h.srow_x[2] = 0) and (h.srow_y[2] = 0) and (h.srow_z[2] = 0) then isBogus := true; - if (not isBogus) then exit; - NII_SetIdentityMatrix(h); - printf('Bogus header repaired. Check orientation.'); -end; - function TNIfTI.Load(niftiFileName: string; tarMat: TMat4; tarDim: TVec3i; isInterpolate: boolean; hdr: TNIFTIhdr; img: TFloat32s): boolean; overload; var scaleMx: single; @@ -7945,7 +8012,7 @@ function TNIfTI.Load(niftiFileName: string; tarMat: TMat4; tarDim: TVec3i; isInt // else if (fHdr.cal_max > fHdr.cal_min)then begin if (fHdr.dataType > kDT_UNSIGNED_CHAR) and (fAutoBalMax > fAutoBalMin) and (((fHdr.cal_max - fHdr.cal_min)/(fAutoBalMax - fAutoBalMin)) > 5) then begin - printf('yIgnoring implausible cal_min..cal_max: FSL eddy?'); + printf('Ignoring implausible cal_min..cal_max: FSL eddy?'); end else begin printf(format('cal_min %g cal_max %g', [fHdr.cal_min, fHdr.cal_max])); fAutoBalMin := fHdr.cal_min; @@ -8188,7 +8255,7 @@ function TNIfTI.SaveCropped(fnm: string; crop: TVec6i; cropVols: TPoint): boolea oHdr.dim[5] := 1; oHdr.dim[6] := 1; oHdr.dim[7] := 1; - if (oHdr.dim[4] > 1) then oHdr.dim[1] := 4; + if (oHdr.dim[4] > 1) then oHdr.dim[0] := 4; offsetMM := NIFTIhdr_SlicesToCoord (oHdr,crop.xLo,crop.yLo,crop.zLo); oHdr.srow_x[3] := oHdr.srow_x[3] + offsetMM.x; oHdr.srow_y[3] := oHdr.srow_y[3] + offsetMM.y; diff --git a/nifti_hdr_view.lfm b/nifti_hdr_view.lfm index 648972e..d005308 100755 --- a/nifti_hdr_view.lfm +++ b/nifti_hdr_view.lfm @@ -21,14 +21,14 @@ object HdrForm: THdrForm Height = 302 Top = 6 Width = 545 - ActivePage = DimensionSheet + ActivePage = StatSheet Align = alClient BorderSpacing.Left = 2 BorderSpacing.Top = 2 BorderSpacing.Right = 2 BorderSpacing.Bottom = 2 BorderSpacing.Around = 2 - TabIndex = 0 + TabIndex = 3 TabOrder = 0 OnChange = PageControl1Change object DimensionSheet: TTabSheet @@ -1174,8 +1174,8 @@ object HdrForm: THdrForm 'NeuroN' 'Generic M' 'Symmetric Matrix' - 'Displacement Field/Vector' - 'Vectorcement Field/Vector' + 'Displacement Field' + 'Vector' 'Points' 'Triangle (mesh)' 'Quaternion' @@ -1484,15 +1484,15 @@ object HdrForm: THdrForm end object OptionalSheet: TTabSheet Caption = 'Optional' - ClientHeight = 318 - ClientWidth = 589 + ClientHeight = 272 + ClientWidth = 539 object DataLabel: TLabel AnchorSideLeft.Control = IntentStrLabel AnchorSideTop.Control = data_typeEdit AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 29 + Top = 31 Width = 61 Caption = 'Data Type' ParentColor = False @@ -1503,7 +1503,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 6 + Top = 7 Width = 53 BorderSpacing.Left = 6 Caption = 'Intention' @@ -1515,7 +1515,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 190 + Top = 195 Width = 45 Caption = 'Extents' ParentColor = False @@ -1526,7 +1526,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 167 + Top = 172 Width = 81 Caption = 'Session Error' ParentColor = False @@ -1537,7 +1537,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 213 + Top = 218 Width = 80 Caption = 'Regular [114]' ParentColor = False @@ -1548,7 +1548,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 121 + Top = 126 Width = 35 Caption = 'G Min' ParentColor = False @@ -1559,7 +1559,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 144 + Top = 149 Width = 38 Caption = 'G Max' ParentColor = False @@ -1570,7 +1570,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 98 + Top = 103 Width = 47 Caption = 'Aux File' ParentColor = False @@ -1581,7 +1581,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 75 + Top = 79 Width = 57 Caption = 'DB Name' ParentColor = False @@ -1592,7 +1592,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrCenter Left = 6 Height = 16 - Top = 52 + Top = 55 Width = 69 Caption = 'Description' ParentColor = False @@ -1602,7 +1602,7 @@ object HdrForm: THdrForm AnchorSideLeft.Side = asrBottom AnchorSideTop.Control = OptionalSheet Left = 93 - Height = 21 + Height = 22 Top = 4 Width = 360 BorderSpacing.Left = 6 @@ -1616,8 +1616,8 @@ object HdrForm: THdrForm AnchorSideTop.Control = intent_nameEdit AnchorSideTop.Side = asrBottom Left = 93 - Height = 21 - Top = 27 + Height = 22 + Top = 28 Width = 360 BorderSpacing.Top = 2 MaxLength = 10 @@ -1629,8 +1629,8 @@ object HdrForm: THdrForm AnchorSideTop.Control = data_typeEdit AnchorSideTop.Side = asrBottom Left = 93 - Height = 21 - Top = 50 + Height = 22 + Top = 52 Width = 360 BorderSpacing.Top = 2 MaxLength = 80 @@ -1642,8 +1642,8 @@ object HdrForm: THdrForm AnchorSideTop.Control = CommentEdit AnchorSideTop.Side = asrBottom Left = 93 - Height = 21 - Top = 73 + Height = 22 + Top = 76 Width = 360 BorderSpacing.Top = 2 MaxLength = 18 @@ -1655,8 +1655,8 @@ object HdrForm: THdrForm AnchorSideTop.Control = db_nameEdit AnchorSideTop.Side = asrBottom Left = 93 - Height = 21 - Top = 96 + Height = 22 + Top = 100 Width = 360 BorderSpacing.Top = 2 MaxLength = 24 @@ -1669,7 +1669,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrBottom Left = 99 Height = 21 - Top = 142 + Top = 147 Width = 66 BorderSpacing.Top = 2 TabOrder = 6 @@ -1681,7 +1681,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrBottom Left = 99 Height = 21 - Top = 119 + Top = 124 Width = 66 BorderSpacing.Left = 6 BorderSpacing.Top = 2 @@ -1694,7 +1694,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrBottom Left = 99 Height = 21 - Top = 165 + Top = 170 Width = 66 BorderSpacing.Top = 2 TabOrder = 7 @@ -1706,7 +1706,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrBottom Left = 99 Height = 21 - Top = 188 + Top = 193 Width = 66 BorderSpacing.Top = 2 TabOrder = 8 @@ -1718,7 +1718,7 @@ object HdrForm: THdrForm AnchorSideTop.Side = asrBottom Left = 99 Height = 21 - Top = 211 + Top = 216 Width = 66 BorderSpacing.Top = 2 MaxValue = 255 diff --git a/nifti_types.pas b/nifti_types.pas index 88b14df..7b37fe2 100644 --- a/nifti_types.pas +++ b/nifti_types.pas @@ -81,7 +81,6 @@ interface K_gzBytes_onlyImageCompressed= -1; K_gzBytes_headerAndImageUncompressed= 0; // -kNIFTI_INTENT_RGB_VECTOR = 2003; //DataTypes kDT_BINARY =1; // binary (1 bit/voxel) kDT_UNSIGNED_CHAR =2; // unsigned char (8 bits/voxel) @@ -188,6 +187,8 @@ interface kNIFTI_INTENT_POINTSET =1008; kNIFTI_INTENT_TRIANGLE =1009; kNIFTI_INTENT_QUATERNION =1010; +kNIFTI_INTENT_RGB_VECTOR = 2003; + procedure NIFTIhdr_SwapBytes (var lAHdr: TNIFTIhdr); //Swap Byte order for the Analyze type function Swap2(s : SmallInt): smallint; diff --git a/niftis.pas b/niftis.pas index b0195b2..0489616 100644 --- a/niftis.pas +++ b/niftis.pas @@ -330,6 +330,7 @@ function TNIfTIs.OpenDrawing(niftiFileName: string): boolean; Drawing.voiCreate(nii.Dim.x, nii.Dim.y, nii.Dim.z, nii.fRawVolBytes) else Drawing.voiCreate(nii.Dim.x, nii.Dim.y, nii.Dim.z, nii.DisplayMinMax2Uint8, true); + Drawing.filename:= niftiFileName; nii.Destroy; end;